# Get account by alias, id, or evm address
Source: https://docs.hedera.com/api-reference/accounts/get-account-by-alias-id-or-evm-address
/openapi.yaml get /api/v1/accounts/{idOrAliasOrEvmAddress}
Return the account transactions and balance information given an account alias, an account id, or an evm address. The information will be limited to at most 1000 token balances for the account as outlined in HIP-367.
When the timestamp parameter is supplied, we will return transactions and account state for the relevant timestamp query. Balance information will be accurate to within 15 minutes of the provided timestamp query.
Historical ethereum nonce information is currently not available and may not be the exact value at a provided timestamp.
# Get crypto allowances for an account info
Source: https://docs.hedera.com/api-reference/accounts/get-crypto-allowances-for-an-account-info
/openapi.yaml get /api/v1/accounts/{idOrAliasOrEvmAddress}/allowances/crypto
Returns information for all crypto allowances for an account.
# Get fungible token allowances for an account
Source: https://docs.hedera.com/api-reference/accounts/get-fungible-token-allowances-for-an-account
/openapi.yaml get /api/v1/accounts/{idOrAliasOrEvmAddress}/allowances/tokens
Returns information for fungible token allowances for an account.
## Ordering
The order is governed by a combination of the spender id and the token id values, with spender id being the parent column.
The token id value governs its order within the given spender id.
Note: The default order for this API is currently ASC
## Filtering
When filtering there are some restrictions enforced to ensure correctness and scalability.
**The table below defines the restrictions and support for the endpoint**
| Query Param | Comparison Operator | Support | Description | Example |
| ------------- | ------------------- | ------- | --------------------- | ------- |
| spender.id | eq | Y | Single occurrence only. | ?spender.id=X |
| | ne | N | | |
| | lt(e) | Y | Single occurrence only. | ?spender.id=lte:X |
| | gt(e) | Y | Single occurrence only. | ?spender.id=gte:X |
| token.id | eq | Y | Single occurrence only. Requires the presence of a **spender.id** query | ?token.id=lt:Y |
| | ne | N | | |
| | lt(e) | Y | Single occurrence only. Requires the presence of an **lte** or **eq** **spender.id** query | ?spender.id=lte:X&token.id=lt:Y |
| | gt(e) | Y | Single occurrence only. Requires the presence of an **gte** or **eq** **spender.id** query | ?spender.id=gte:X&token.id=gt:Y |
Both filters must be a single occurrence of **gt(e)** or **lt(e)** which provide a lower and or upper boundary for search.
# Get nfts for an account info
Source: https://docs.hedera.com/api-reference/accounts/get-nfts-for-an-account-info
/openapi.yaml get /api/v1/accounts/{idOrAliasOrEvmAddress}/nfts
Returns information for all non-fungible tokens for an account.
## Ordering
When considering NFTs, their order is governed by a combination of their numerical **token.Id** and **serialnumber** values, with **token.id** being the parent column.
A serialnumbers value governs its order within the given token.id
In that regard, if a user acquired a set of NFTs in the order (2-2, 2-4 1-5, 1-1, 1-3, 3-3, 3-4), the following layouts illustrate the ordering expectations for ownership listing
1. **All NFTs in ASC order**: 1-1, 1-3, 1-5, 2-2, 2-4, 3-3, 3-4
2. **All NFTs in DESC order**: 3-4, 3-3, 2-4, 2-2, 1-5, 1-3, 1-1
3. **NFTs above 1-1 in ASC order**: 1-3, 1-5, 2-2, 2-4, 3-3, 3-4
4. **NFTs below 3-3 in ASC order**: 1-1, 1-3, 1-5, 2-2, 2-4
5. **NFTs between 1-3 and 3-3 inclusive in DESC order**: 3-4, 3-3, 2-4, 2-2, 1-5, 1-3
Note: The default order for this API is currently DESC
## Filtering
When filtering there are some restrictions enforced to ensure correctness and scalability.
**The table below defines the restrictions and support for the NFT ownership endpoint**
| Query Param | Comparison Operator | Support | Description | Example |
| ------------- | ------------------- | ------- | --------------------- | ------- |
| token.id | eq | Y | Single occurrence only. | ?token.id=X |
| | ne | N | | |
| | lt(e) | Y | Single occurrence only. | ?token.id=lte:X |
| | gt(e) | Y | Single occurrence only. | ?token.id=gte:X |
| serialnumber | eq | Y | Single occurrence only. Requires the presence of a **token.id** query | ?serialnumber=Y |
| | ne | N | | |
| | lt(e) | Y | Single occurrence only. Requires the presence of an **lte** or **eq** **token.id** query | ?token.id=lte:X&serialnumber=lt:Y |
| | gt(e) | Y | Single occurrence only. Requires the presence of an **gte** or **eq** **token.id** query | ?token.id=gte:X&serialnumber=gt:Y |
| spender.id | eq | Y | | ?spender.id=Z |
| | ne | N | | |
| | lt(e) | Y | | ?spender.id=lt:Z |
| | gt(e) | Y | | ?spender.id=gt:Z |
Note: When searching across a range for individual NFTs a **serialnumber** with an additional **token.id** query filter must be provided.
Both filters must be a single occurrence of **gt(e)** or **lt(e)** which provide a lower and or upper boundary for search.
# Get non fungible token allowances for an account
Source: https://docs.hedera.com/api-reference/accounts/get-non-fungible-token-allowances-for-an-account
/openapi.yaml get /api/v1/accounts/{idOrAliasOrEvmAddress}/allowances/nfts
Returns an account's non-fungible token allowances.
## Ordering
The order is governed by a combination of the account ID and the token ID values, with account ID being the parent column.
The token ID value governs its order within the given account ID.
Note: The default order for this API is currently ascending. The account ID can be the owner or the spender ID depending upon the owner flag.
## Filtering
When filtering there are some restrictions enforced to ensure correctness and scalability.
**The table below defines the restrictions and support for the endpoint**
| Query Param | Comparison Operator | Support | Description | Example |
| ------------- | ------------------- | ------- | --------------------- | ------- |
| account.id | eq | Y | Single occurrence only. | ?account.id=X |
| | ne | N | | |
| | lt(e) | Y | Single occurrence only. | ?account.id=lte:X |
| | gt(e) | Y | Single occurrence only. | ?account.id=gte:X |
| token.id | eq | Y | Single occurrence only. Requires the presence of an **account.id** parameter | ?account.id=X&token.id=eq:Y |
| | ne | N | | |
| | lt(e) | Y | Single occurrence only. Requires the presence of an **lte** or **eq** **account.id** parameter | ?account.id=lte:X&token.id=lt:Y |
| | gt(e) | Y | Single occurrence only. Requires the presence of an **gte** or **eq** **account.id** parameter | ?account.id=gte:X&token.id=gt:Y |
Both filters must be a single occurrence of **gt(e)** or **lt(e)** which provide a lower and or upper boundary for search.
# Get past staking reward payouts for an account
Source: https://docs.hedera.com/api-reference/accounts/get-past-staking-reward-payouts-for-an-account
/openapi.yaml get /api/v1/accounts/{idOrAliasOrEvmAddress}/rewards
Returns information for all past staking reward payouts for an account.
# Get token relationships info for an account
Source: https://docs.hedera.com/api-reference/accounts/get-token-relationships-info-for-an-account
/openapi.yaml get /api/v1/accounts/{idOrAliasOrEvmAddress}/tokens
Returns information for all token relationships for an account.
# List account entities on network
Source: https://docs.hedera.com/api-reference/accounts/list-account-entities-on-network
/openapi.yaml get /api/v1/accounts
Returns a list of all account entity items on the network.
# Get outstanding token airdrops sent by an account
Source: https://docs.hedera.com/api-reference/airdrops/get-outstanding-token-airdrops-sent-by-an-account
/openapi.yaml get /api/v1/accounts/{idOrAliasOrEvmAddress}/airdrops/outstanding
Returns outstanding token airdrops that have been sent by an account.
# Get pending token airdrops received by an account
Source: https://docs.hedera.com/api-reference/airdrops/get-pending-token-airdrops-received-by-an-account
/openapi.yaml get /api/v1/accounts/{idOrAliasOrEvmAddress}/airdrops/pending
Returns pending token airdrops that have been received by an account.
# List account balances
Source: https://docs.hedera.com/api-reference/balances/list-account-balances
/openapi.yaml get /api/v1/balances
Returns a list of account and token balances on the network. The latest balance information is returned when there is no timestamp query parameter, otherwise, the information is retrieved from snapshots with 15-minute granularity. This information is limited to at most 50 token balances per account as outlined in HIP-367. As such, it's not recommended for general use and we instead recommend using either `/api/v1/accounts/{id}/tokens` or `/api/v1/tokens/{id}/balances` to obtain the current token balance information and `/api/v1/accounts/{id}` to return the current account balance.
# Get block by hash or number
Source: https://docs.hedera.com/api-reference/blocks/get-block-by-hash-or-number
/openapi.yaml get /api/v1/blocks/{hashOrNumber}
Returns the block information by given hash or number.
# List blocks
Source: https://docs.hedera.com/api-reference/blocks/list-blocks
/openapi.yaml get /api/v1/blocks
Returns a list of blocks on the network.
# Get verified contract
Source: https://docs.hedera.com/api-reference/contract-lookup/get-verified-contract
/smart-contract-verification-api.yaml get /v2/contract/{chainId}/{address}
By default returns minimal information about the contract: `match`, `creation_match`, `runtime_match`, `chainId`, `address`, and `verifiedAt`
To get other details one can either list the fields requested in the `fields` query param or ask all fields but omit several with `omit`. To get everything just pass `fields=all`.
# Get verified contract at an address on all chains
Source: https://docs.hedera.com/api-reference/contract-lookup/get-verified-contract-at-an-address-on-all-chains
/smart-contract-verification-api.yaml get /v2/contract/all-chains/{address}
Returns all verified deployments at an address on all Sourcify chains (including deprecated ones).
Success returns an array of VerifiedContractMinimal objects under `results` field.
If not verified on any chain, the `results` array will be empty.
# List of verified contracts per chain
Source: https://docs.hedera.com/api-reference/contract-lookup/list-of-verified-contracts-per-chain
/smart-contract-verification-api.yaml get /v2/contracts/{chainId}
Retrieve the verified contracts on a chain
# Get contract by id
Source: https://docs.hedera.com/api-reference/contracts/get-contract-by-id
/openapi.yaml get /api/v1/contracts/{contractIdOrAddress}
Return the contract information given an id
# Get the contract actions from a contract on the network for a given transactionId or ethereum transaction hash
Source: https://docs.hedera.com/api-reference/contracts/get-the-contract-actions-from-a-contract-on-the-network-for-a-given-transactionid-or-ethereum-transaction-hash
/openapi.yaml get /api/v1/contracts/results/{transactionIdOrHash}/actions
Returns a list of ContractActions for a contract's function executions for a given transactionId or ethereum transaction hash.
# Get the contract result from a contract on the network executed at a given timestamp
Source: https://docs.hedera.com/api-reference/contracts/get-the-contract-result-from-a-contract-on-the-network-executed-at-a-given-timestamp
/openapi.yaml get /api/v1/contracts/{contractIdOrAddress}/results/{timestamp}
Returns a single ContractResult for a contract's function executions at a specific timestamp.
# Get the contract result from a contract on the network for a given transactionId or ethereum transaction hash
Source: https://docs.hedera.com/api-reference/contracts/get-the-contract-result-from-a-contract-on-the-network-for-a-given-transactionid-or-ethereum-transaction-hash
/openapi.yaml get /api/v1/contracts/results/{transactionIdOrHash}
Returns a single ContractResult for a contract's function executions for a given transactionId or ethereum transaction hash.
# Get the opcode traces for a historical transaction on the network with the given transaction ID or hash
Source: https://docs.hedera.com/api-reference/contracts/get-the-opcode-traces-for-a-historical-transaction-on-the-network-with-the-given-transaction-id-or-hash
/openapi.yaml get /api/v1/contracts/results/{transactionIdOrHash}/opcodes
Re-executes a transaction and returns a result containing detailed information for the execution,
including all values from the {@code stack}, {@code memory} and {@code storage}
and the entire trace of opcodes that were executed during the replay.
Note that to provide the output, the transaction needs to be re-executed on the EVM,
which may take a significant amount of time to complete if stack and memory information is requested.
# Invoke a smart contract
Source: https://docs.hedera.com/api-reference/contracts/invoke-a-smart-contract
/openapi.yaml post /api/v1/contracts/call
Returns a result from EVM execution such as cost-free execution of read-only smart contract queries, gas estimation, and transient simulation of read-write operations. If the `estimate` field is set to true gas estimation is executed. This API can process calls against the `latest` block or specific historical blocks when a hexadecimal or decimal block number is provided in the `block` field.
# List contract entities on network
Source: https://docs.hedera.com/api-reference/contracts/list-contract-entities-on-network
/openapi.yaml get /api/v1/contracts
Returns a list of all contract entity items on the network.
# List contract logs from a contract on the network
Source: https://docs.hedera.com/api-reference/contracts/list-contract-logs-from-a-contract-on-the-network
/openapi.yaml get /api/v1/contracts/{contractIdOrAddress}/results/logs
Search the logs of a specific contract across multiple contract calls. Chained logs are not
included but can be found by calling `/api/v1/contracts/{contractId}/results/{timestamp}`
or `/api/v1/contracts/results/{transactionId}`. When searching by topic a timestamp parameter must be supplied
and span a time range of at most seven days.
## Ordering
The order is governed by the combination of timestamp and index values. If the index param is omitted, the order is determined by the timestamp only.
Note: The default order for this API is currently DESC
## Filtering
When filtering there are some restrictions enforced to ensure correctness and scalability.
**The table below defines the restrictions and support for the endpoint**
| Query Param | Comparison Operator | Support | Description | Example |
| ------------- | ------------------- | ------- | --------------------- | ------- |
| index | eq | Y | Single occurrence only. Requires the presence of timestamp | ?index=X |
| | ne | N | | |
| | lt(e) | Y | Single occurrence only. Requires the presence of timestamp | ?index=lte:X |
| | gt(e) | Y | Single occurrence only. Requires the presence of timestamp | ?index=gte:X |
| timestamp | eq | Y | Single occurrence only. | ?timestamp=Y
| | ne | N | | |
| | lt(e) | Y | Single occurrence only. Optional second timestamp **gt(e)** | ?timestamp=lte:Y
| | gt(e) | Y | Single occurrence only. Optional second timestamp **lt(e)** | ?timestamp=gte:Y
Both filters must be a single occurrence of **gt(e)** or **lt(e)** which provide a lower and or upper boundary for search.
# List contract results from a contract on the network
Source: https://docs.hedera.com/api-reference/contracts/list-contract-results-from-a-contract-on-the-network
/openapi.yaml get /api/v1/contracts/{contractIdOrAddress}/results
Returns a list of all ContractResults for a contract's function executions.
# List contract results from all contracts on the network
Source: https://docs.hedera.com/api-reference/contracts/list-contract-results-from-all-contracts-on-the-network
/openapi.yaml get /api/v1/contracts/results
Returns a list of all ContractResults for all contract's function executions.
# List contracts logs across many contracts on the network
Source: https://docs.hedera.com/api-reference/contracts/list-contracts-logs-across-many-contracts-on-the-network
/openapi.yaml get /api/v1/contracts/results/logs
Search the logs across many contracts with multiple contract calls. Chained logs are not
included but can be found by calling `/api/v1/contracts/{contractId}/results/{timestamp}`
or `/api/v1/contracts/results/{transactionId}`. When searching by topic a timestamp parameter must be supplied
and span a time range of at most seven days.
## Ordering
The order is governed by the combination of timestamp and index values. If the index param is omitted, the order is determined by the timestamp only.
Note: The default order for this API is currently DESC
## Filtering
When filtering there are some restrictions enforced to ensure correctness and scalability.
**The table below defines the restrictions and support for the endpoint**
| Query Param | Comparison Operator | Support | Description | Example |
| ------------- | ------------------- | ------- | --------------------- | ------- |
| index | eq | Y | Single occurrence only. Requires the presence of timestamp | ?index=X |
| | ne | N | | |
| | lt(e) | Y | Single occurrence only. Requires the presence of timestamp | ?index=lte:X |
| | gt(e) | Y | Single occurrence only. Requires the presence of timestamp | ?index=gte:X |
| timestamp | eq | Y | Single occurrence only. | ?timestamp=Y
| | ne | N | | |
| | lt(e) | Y | Single occurrence only. Optional second timestamp **gt(e)** | ?timestamp=lte:Y
| | gt(e) | Y | Single occurrence only. Optional second timestamp **lt(e)** | ?timestamp=gte:Y
Both filters must be a single occurrence of **gt(e)** or **lt(e)** which provide a lower and or upper boundary for search.
# The contract state from a contract on the network
Source: https://docs.hedera.com/api-reference/contracts/the-contract-state-from-a-contract-on-the-network
/openapi.yaml get /api/v1/contracts/{contractIdOrAddress}/state
Returns a list of all contract's slots. If no timestamp is provided, returns the current state.
# List all components
Source: https://docs.hedera.com/api-reference/list-all-components
/hedera-status-api.yaml get /components.json
Provides detailed information about each component of the Hedera network.
# Estimate network fees
Source: https://docs.hedera.com/api-reference/network/estimate-network-fees
/openapi.yaml post /api/v1/network/fees
Given a protobuf-encoded HAPI transaction, returns an itemized fee estimate in tinycents.
The response is broken down into `node`, `network`, and `service` components, each with a
`base` price and a list of `extras` (e.g., signatures, memo bytes). The `total` field is
the sum of all subtotals.
When the request transaction sets the [HIP-1313](https://hips.hedera.com/hip/hip-1313)
`high_volume` flag, the response includes a `high_volume_multiplier`. The totals in the
response are **not** pre-multiplied — multiply `total` by `high_volume_multiplier / 1000`
to obtain the high-volume price.
# Get network stake information
Source: https://docs.hedera.com/api-reference/network/get-network-stake-information
/openapi.yaml get /api/v1/network/stake
Returns the network's current stake information.
# Get registered nodes
Source: https://docs.hedera.com/api-reference/network/get-registered-nodes
/openapi.yaml get /api/v1/network/registered-nodes
Returns the list of registered nodes
# Get the network address book nodes
Source: https://docs.hedera.com/api-reference/network/get-the-network-address-book-nodes
/openapi.yaml get /api/v1/network/nodes
Returns the network's list of nodes used in consensus
# Get the network exchange rate to estimate costs
Source: https://docs.hedera.com/api-reference/network/get-the-network-exchange-rate-to-estimate-costs
/openapi.yaml get /api/v1/network/exchangerate
Returns the network's exchange rate, current and next.
# Get the network fees
Source: https://docs.hedera.com/api-reference/network/get-the-network-fees
/openapi.yaml get /api/v1/network/fees
Returns the estimated gas in tinybars per each transaction type. Default order is ASC. Currently only `ContractCall`, `ContractCreate` and `EthereumTransaction` transaction types are supported.
# Get the network supply
Source: https://docs.hedera.com/api-reference/network/get-the-network-supply
/openapi.yaml get /api/v1/network/supply
Returns the network's released supply of hbars
# Get the list of supported and deprecated chains
Source: https://docs.hedera.com/api-reference/other/get-the-list-of-supported-and-deprecated-chains
/smart-contract-verification-api.yaml get /chains
# Get version information
Source: https://docs.hedera.com/api-reference/other/get-version-information
/smart-contract-verification-api.yaml get /version
Returns version information for the server and its dependencies, along with the git commit hash
# Health check endpoint
Source: https://docs.hedera.com/api-reference/other/health-check-endpoint
/smart-contract-verification-api.yaml get /health
Returns server health status
# Retrieve a summary of the status page.
Source: https://docs.hedera.com/api-reference/retrieve-a-summary-of-the-status-page
/hedera-status-api.yaml get /summary.json
Get a summary of the status page, including a status indicator, component statuses, unresolved incidents, and any upcoming or in-progress scheduled maintenances.
# Retrieve active scheduled maintenances
Source: https://docs.hedera.com/api-reference/retrieve-active-scheduled-maintenances
/hedera-status-api.yaml get /scheduled-maintenances/active.json
Fetches details about all currently active scheduled maintenance events for the Hedera network.
# Retrieve all incidents
Source: https://docs.hedera.com/api-reference/retrieve-all-incidents
/hedera-status-api.yaml get /incidents.json
Fetches a list of all incidents, both resolved and unresolved, affecting the Hedera network.
# Retrieve all scheduled maintenances
Source: https://docs.hedera.com/api-reference/retrieve-all-scheduled-maintenances
/hedera-status-api.yaml get /scheduled-maintenances.json
Fetches details about all scheduled maintenance events for the Hedera network.
# Retrieve the current status of the Hedera network
Source: https://docs.hedera.com/api-reference/retrieve-the-current-status-of-the-hedera-network
/hedera-status-api.yaml get /status.json
Fetches the overall status and health indicators of the Hedera network.
# Retrieve unresolved incidents
Source: https://docs.hedera.com/api-reference/retrieve-unresolved-incidents
/hedera-status-api.yaml get /incidents/unresolved.json
Provides information about all currently unresolved incidents affecting the Hedera network.
# Retrieve upcoming scheduled maintenances
Source: https://docs.hedera.com/api-reference/retrieve-upcoming-scheduled-maintenances
/hedera-status-api.yaml get /scheduled-maintenances/upcoming.json
Provides information about all upcoming scheduled maintenance events for the Hedera network.
# Get schedule by id
Source: https://docs.hedera.com/api-reference/schedules/get-schedule-by-id
/openapi.yaml get /api/v1/schedules/{scheduleId}
Returns schedule information based on the given schedule id
# List schedules entities
Source: https://docs.hedera.com/api-reference/schedules/list-schedules-entities
/openapi.yaml get /api/v1/schedules
Lists schedules on the network that govern the execution logic of scheduled transactions. This includes executed and non executed schedules.
# Get an nfts transction history
Source: https://docs.hedera.com/api-reference/tokens/get-an-nfts-transction-history
/openapi.yaml get /api/v1/tokens/{tokenId}/nfts/{serialNumber}/transactions
Returns a list of transactions for a given non-fungible token
# Get nft info
Source: https://docs.hedera.com/api-reference/tokens/get-nft-info
/openapi.yaml get /api/v1/tokens/{tokenId}/nfts/{serialNumber}
Returns information for a non-fungible token
# Get token by id
Source: https://docs.hedera.com/api-reference/tokens/get-token-by-id
/openapi.yaml get /api/v1/tokens/{tokenId}
Returns token entity information given the id
# List nfts
Source: https://docs.hedera.com/api-reference/tokens/list-nfts
/openapi.yaml get /api/v1/tokens/{tokenId}/nfts
Returns a list of non-fungible tokens
# List token balances
Source: https://docs.hedera.com/api-reference/tokens/list-token-balances
/openapi.yaml get /api/v1/tokens/{tokenId}/balances
Returns a list of token balances given the id. This represents the Token supply distribution across the network
# List tokens
Source: https://docs.hedera.com/api-reference/tokens/list-tokens
/openapi.yaml get /api/v1/tokens
Returns a list of tokens on the network.
# Get topic by ID
Source: https://docs.hedera.com/api-reference/topics/get-topic-by-id
/openapi.yaml get /api/v1/topics/{topicId}
Returns the topic details for the given topic ID.
# Get topic message by consensusTimestamp
Source: https://docs.hedera.com/api-reference/topics/get-topic-message-by-consensustimestamp
/openapi.yaml get /api/v1/topics/messages/{timestamp}
Returns a topic message the given the consensusTimestamp.
# Get topic message by id and sequence number
Source: https://docs.hedera.com/api-reference/topics/get-topic-message-by-id-and-sequence-number
/openapi.yaml get /api/v1/topics/{topicId}/messages/{sequenceNumber}
Returns a single topic message for the given topic id and sequence number.
# List topic messages by id
Source: https://docs.hedera.com/api-reference/topics/list-topic-messages-by-id
/openapi.yaml get /api/v1/topics/{topicId}/messages
Returns the list of topic messages for the given topic id.
# Get transaction by id
Source: https://docs.hedera.com/api-reference/transactions/get-transaction-by-id
/openapi.yaml get /api/v1/transactions/{transactionId}
Returns transaction information based on the given transaction id
# List transactions
Source: https://docs.hedera.com/api-reference/transactions/list-transactions
/openapi.yaml get /api/v1/transactions
Lists transactions on the network. This includes successful and unsuccessful transactions.
# Check verification job status
Source: https://docs.hedera.com/api-reference/verification-jobs/check-verification-job-status
/smart-contract-verification-api.yaml get /v2/verify/{verificationId}
Endpoint to get the status of a verification job.
Alternatively you can directly check the verification status of a contract with with chainId+address at `GET /v2/contract/{chainId}/{address}`
# Import from Etherscan
Source: https://docs.hedera.com/api-reference/verify-contracts/import-from-etherscan
/smart-contract-verification-api.yaml post /v2/verify/etherscan/{chainId}/{address}
Import a contract verified on an Etherscan instance or a service with Etherscan-alike API
# Verify Contract (Standard JSON)
Source: https://docs.hedera.com/api-reference/verify-contracts/verify-contract-standard-json
/smart-contract-verification-api.yaml post /v2/verify/{chainId}/{address}
Submit a contract for verification via the [Solidity standard JSON input](https://docs.soliditylang.org/en/latest/using-the-compiler.html#input-description), [Vyper JSON input](https://docs.vyperlang.org/en/stable/compiling-a-contract.html#input-json-description), or Fe JSON input (a Sourcify-defined format — Fe has no official compiler JSON interface).
There are no "single file" or "multi-part" verification endpoints because those are essentially wrappers around the Solidity compiler's JSON interface. The verification frontend can provide files and settings options to resemble these.
You can optionally pass the `creationTransactionHash` to make Sourcify reliably fetching the creation bytecode. Otherwise, it will try to fetch it itself which is dependent on external services.
**Note**: The `outputSelection` field in the `stdJsonInput.settings` will be overridden during verification to ensure all necessary artifacts are generated.
# Verify Contract (using Solidity metadata.json)
Source: https://docs.hedera.com/api-reference/verify-contracts/verify-contract-using-solidity-metadatajson
/smart-contract-verification-api.yaml post /v2/verify/metadata/{chainId}/{address}
Endpoint to submit a verification with the Solidity [metadata.json](https://docs.soliditylang.org/en/latest/metadata.html)
# Verify contract via similarity search
Source: https://docs.hedera.com/api-reference/verify-contracts/verify-contract-via-similarity-search
/smart-contract-verification-api.yaml post /v2/verify/similarity/{chainId}/{address}
Starts a verification job that searches the Sourcify database for contracts whose
runtime bytecode is similar to the contract deployed at the given address. The job will
attempt to verify against each candidate until a match is found or the candidate list is exhausted.
# Account Model for EVM Developers
Source: https://docs.hedera.com/evm/development/accounts
How Hedera accounts work in EVM contexts: EVM addresses map to native account entities, covering hollow accounts, long-zero accounts, and token association.
On Hedera, every active EVM address corresponds to a registered account entity with a native **Account ID** (e.g., `0.0.1234`). Unlike Ethereum, where any address can exist implicitly without ever being registered, a Hedera account entity is only created when HBAR or tokens are first sent to an address, or when an account is explicitly created. Understanding this model is important when building dApps that interact with users holding different account types.
There are two account types EVM developers encounter in practice:
* [**Hollow accounts**](#hollow-accounts) - created automatically when HBAR or tokens are sent to an EVM address for the first time
* [**Long-zero accounts**](#long-zero-accounts) - existing Hedera accounts created without an ECDSA key, represented in the EVM by an address padded with leading zeros
***
## Hollow Accounts
### What They Are
A hollow account is created automatically by the network when HBAR or tokens are first sent to an EVM address that has no corresponding account yet. This process is called [auto account creation](/learn/core-concepts/accounts/auto-account-creation).
The resulting account has:
* a native Account ID (e.g., `0.0.5678`)
* an EVM address (e.g., `0xabc...def`) stored as the account alias
* no signing key on record
Because the network has not yet verified which private key controls the address, the account is hollow until completion.
### What Hollow Accounts Can Do
EVM tooling works normally with hollow accounts. JSON-RPC calls, Hardhat scripts, Foundry tests, and smart contract interactions all function as expected because the EVM layer does not require a signing key on record. The account can:
* receive and hold HBAR and tokens
* be the target of contract calls
* receive ERC-20 and ERC-721 tokens (Solidity-based token transfers require no Hedera-level association and behave the same as on Ethereum)
HTS (native Hedera Token Service) tokens require token association. Hollow accounts are created with `maxAutoAssociations = -1` (unlimited) by default, so they accept HTS token transfers automatically from the moment they are created — no completion required for this. See [HIP-904](https://hips.hedera.com/hip/hip-904).
### What Hollow Accounts Cannot Do
A hollow account cannot perform actions that require an authorized key signature until it is completed:
* it cannot transfer tokens or HBAR out of the account via HAPI
* it cannot modify its own account properties (keys, memo, staking)
* it cannot explicitly manage token associations via HAPI
### How Completion Works
A hollow account is completed when a transaction is submitted that requires its signature and includes the matching ECDSA key. This most commonly happens in two ways:
**Via EVM wallet (automatic):** When the user sends their first outbound transaction from MetaMask or a similar EVM wallet, the wallet signs it with their ECDSA private key. The relay submits it to the network as an `EthereumTransaction`. The network extracts the ECDSA public key from the transaction signature, derives the EVM address from it, matches it against the hollow account's alias, and sets the key on the account to complete it.
**Via SDK (explicit):** Build any transaction, set the hollow account as the fee payer, and sign with the ECDSA key that corresponds to the EVM address.
```javascript theme={null}
const tx = new TransferTransaction()
.addHbarTransfer(hollowAccountId, new Hbar(-1))
.addHbarTransfer(recipientId, new Hbar(1))
.setTransactionId(TransactionId.generate(hollowAccountId))
.freezeWith(client);
const signedTx = await tx.sign(ecdsaPrivateKey);
await signedTx.execute(client);
```
After completion, the account behaves like any standard Hedera account.
### Looking Up an Account ID from an EVM Address
To find the Account ID for a given EVM address, use either of these methods:
**Mirror Node REST API:**
```
GET https://mainnet-public.mirrornode.hedera.com/api/v1/accounts/{evmAddress}
```
The response includes the `account` field with the Account ID.
**HashScan:** Paste the EVM address into the search bar on [hashscan.io](https://hashscan.io) to see the account details, including its Account ID.
***
## Long-Zero Accounts
### What They Are
Many Hedera users hold accounts created through the native HAPI flow, typically with an ED25519 key and no ECDSA alias. These accounts have no EVM Address from Public Key set. When the EVM needs to represent such an account, it constructs a synthetic address by left-padding the account number with zeros to fill 20 bytes. For example, account `0.0.77` becomes `0x000000000000000000000000000000000000004d`.
This is called the **EVM Address from Account ID** or the "long-zero" form.
| Account ID | Long-Zero EVM Address |
| ---------- | -------------------------------------------- |
| `0.0.77` | `0x000000000000000000000000000000000000004d` |
| `0.0.1000` | `0x00000000000000000000000000000000000003e8` |
### Limitations for EVM Workflows
Long-zero accounts cannot participate in standard EVM developer workflows:
| Capability | Long-Zero Account |
| ---------------------------------------------------- | ---------------------------- |
| Sign `EthereumTransaction` (MetaMask, Hardhat, etc.) | No — no ECDSA key |
| Connect via EVM wallet | No |
| Call smart contracts using EVM tooling | No |
| Pass `ECRECOVER`-based signature checks | No — no ECDSA key to recover |
| Receive HBAR via EVM transfer | Yes |
### Why This Matters for dApp Developers
When building dApps, some of your users will have long-zero accounts. Common failure modes:
* **Wallet connection fails silently.** EVM wallets require an ECDSA key. A long-zero account user has no compatible key and cannot connect.
* **Permit and off-chain signing flows break.** EIP-2612 and similar patterns rely on `ECRECOVER` to verify signatures. Long-zero accounts cannot produce a valid ECDSA signature, so these checks will fail.
* **Access control based on address matching fails.** If your contract stores an expected signer address and validates with `ECRECOVER`, it will never match a long-zero address because recovery requires a valid ECDSA signature.
Do not assume all Hedera users can sign EVM transactions. Design fallback flows or clearly communicate wallet requirements to users who may hold long-zero accounts.
### Distinguishing Address Types
Both address forms are 20 bytes. You can tell them apart by inspecting the prefix:
* **Long-zero:** first 12 bytes are all zeros (`0x000000000000000000000000...`)
* **EVM Address from Public Key:** no zero prefix, derived from a Keccak-256 hash
```javascript theme={null}
function isLongZero(address) {
return address.startsWith("0x000000000000000000000000");
}
```
***
## Reference
| Topic | Link |
| ------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
| Auto account creation flow | [Auto Account Creation](/learn/core-concepts/accounts/auto-account-creation) |
| Account ID and alias properties | [Account Properties](/learn/core-concepts/accounts/account-properties) |
| EVM address vs. Account ID differences | [Accounts, Signature Verification & Keys](/evm/differences/accounts-and-keys) |
| HIP-32 (auto account creation) | [HIP-32](https://hips.hedera.com/hip/hip-32) |
| HIP-542 (ECDSA EVM Address support in CryptoCreate/CryptoTransfer) | [HIP-542](https://hips.hedera.com/hip/hip-542) |
| HIP-583 (hollow account completion) | [HIP-583](https://hips.hedera.com/hip/hip-583) |
| HIP-904 (frictionless token associations) | [HIP-904](https://hips.hedera.com/hip/hip-904) |
# Smart Contract Addresses
Source: https://docs.hedera.com/evm/development/addresses
After a smart contract is deployed on Hedera, it is associated with a unique smart contract address. There are two types of addresses a smart contract can be referenced by in the system:
**➡** [**Smart Contract EVM Address**](#evm-address)
**➡** [**Smart Contract ID**](#contract-id)
***
### EVM Address
The standard smart contract EVM address is the address that is compatible with EVM. The EVM contract address is returned by the system once the contract is deployed. This is the address format that is commonly used in the Ethereum ecosystem. You can use the smart contract EVM address to reference smart contracts in Ethereum Ecosystem tools like [Hardhat](/support/glossary#hardhat) and [MetaMask](/support/glossary#metamask).
Example Contract EVM Address hex encoded contract ID: `0x00000000000000000000000000000000002cd37f`
***Note:** Contracts deployed using the `ContractCreate` Hedera API transactions will have this form (For example, using ContractCreateTransaction in the SDKs). All other deployment cases will be in the standard EVM address, post* [*HIP-729*](https://hips.hedera.com/hip/hip-729)*.*
Example Contract EVM Address: [`0x86ecca95fecdb515d068975b75eac4357contractd6e86c5`](https://hashscan.io/mainnet/contract/0.0.2958097?p=1\&k=1685819177.474035003)
***
### Contract ID
In the Hedera Network, smart contracts can also be identified by a smart contract ID. A smart contract ID is a contract identifier native to the Hedera network. Both the smart contract EVM address and smart contract ID are accepted identifiers for a smart contract when interacting with the contract on Hedera using the Hedera transactions.
Example Contract ID: `0.0.123`
In some cases, the EVM address is the hex-encoded format of the contract ID.
The smart contract ID is **not a compatible** address format accepted or known in the Ethereum ecosystem. For example, if you use MetaMask, you will not specify the contract by its contract ID and instead use its EVM address.
When viewing the contract information, you may see both types of addresses noted in Hedera Network Explorers like [HashScan](https://hashscan.io/).
***
### Smart Contract Accounts
Similar to [Ethereum](/support/glossary#ethereum), Smart Contract entities are also a type of account. A smart contract deployed on Hedera can hold [HBAR](/support/glossary#hbar), [fungible](/support/glossary#fungible-token), and [non-fungible tokens](/support/glossary#non-fungible-token-nft).
# EVM Archive Node Queries
Source: https://docs.hedera.com/evm/development/archive-queries
## Introduction to EVM Archive Node Queries
[HIP-584](https://hips.hedera.com/hip/hip-584) enhances Hedera Mirror Nodes with extended EVM execution capabilities, allowing developers to perform gas-free smart contract queries, estimate gas usage, and simulate EVM transactions without committing state changes. These enhancements empower developers to:
* **Perform Gas-Free Smart Contract Queries:** Retrieve data from smart contracts without incurring gas costs.
* **Estimate Gas Usage:** Determine the gas required for executing specific contract functions.
* **Simulate EVM Transactions:** Test transactions that involve state changes without committing those changes to the blockchain.
This guide uses real examples of interactions with smart contracts, including [SaucerSwap](https://www.saucerswap.finance/)'s DeFi contracts on the Hedera network:
* SaucerSwap's HashScan verified DeFi smart contract:
* `0x00000000000000000000000000000000002e7a5d`
* The HashScan link to the verified smart contract can be found [here](https://hashscan.io/mainnet/contract/0.0.3045981?pf=1\&kf=0.0.1456986).
***
## API Endpoint Overview
The API endpoint and parameters used for all operations described in this guide is:
```bash theme={null}
POST /api/v1/contracts/call
```
### Key Parameters
* `estimate` (boolean): Determines the operation type.
* `true`: Performs gas estimation.
* `false`: Executes a query or simulation.
* `block` (string): Specifies the block for the operation (e.g., "latest" or a specific block number).
* `data` (string): Encoded function call data in hexadecimal format following the ABI specifications.
* `from` (string, optional): Address initiating the call. Required for simulations involving state changes.
* `to` (string): Target smart contract address (SaucerSwap's DeFi contract for this guide).
* `value` (number, optional): Amount of tinybars to send with the transaction. Relevant for simulations involving value transfers.
For detailed specifications, refer to the [Swagger documentation](https://testnet.mirrornode.hedera.com/api/v1/docs/#/contracts/contractCall).
***
## Prerequisites
Before proceeding, ensure you have:
* Basic knowledge of EVM smart contracts:
* Understanding of [ABI (Application Binary Interface)](/evm/development/compiling#smart-contract-application-binary-interface-abi) and function encoding.
* Essential tools installed and configured:
* cURL for making HTTP requests.
* Ethers.js (v6 or later or equivalent libraries) for interacting with the EVM-compatible networks.
* Function data encoding:
* Familiarity with encoding function data into the required EVM-compatible hexadecimal format.
***
## Gas Estimation
Estimating gas usage helps determine the cost required to execute a smart contract function without actually performing the transaction.
### **Example Request**
Here's how to estimate gas usage using the `/api/v1/contracts/call` endpoint:
```bash wrap theme={null}
curl -X POST https://mainnet.mirrornode.hedera.com/api/v1/contracts/call \
-H "Content-Type: application/json" \
-d '{
"block": "latest",
"data": "0x1f00ca74000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000163b5a00000000000000000000000000000000000000000000000000000000000b2ad5",
"estimate": true,
"to": "0x00000000000000000000000000000000002e7a5d"
}'
```
### **Expected Response**
The API returns an estimated gas value in hexadecimal format:
```json theme={null}
{
"result": "0x0000000000007f0d"
}
```
### **Decoding the Result:**
To interpret the hexadecimal gas estimate, follow these steps:
1. **Extract the `result`**: the field from the API response containing the gas estimate in hexadecimal format.
2. **Convert Hexadecimal to BigInt**:
Use JavaScript's `BigInt` to convert the hexadecimal string to a numerical value.
```javascript theme={null}
// Assuming you have the result from the API response
const hexString = "0x0000000000007f0d";
const gasEstimate = BigInt(hexString);
console.log("Gas Estimate:", gasEstimate.toString());
```
**Output:**
```bash theme={null}
Gas Estimate: 32525
```
The gas estimate result represents the value in tinybars.
**Understanding the Logic**
* **Hexadecimal Representation:** Smart contracts and blockchain APIs often use hexadecimal strings to represent numerical values, ensuring precise and compact data transmission.
* **Conversion to BigInt:** JavaScript's `BigInt` is used to handle large integers that exceed the safe integer limit of the standard `Number` type, ensuring accuracy in calculations.
* **Interpretation:** The numerical value (`32525` in this case) represents the estimated gas required to execute the specified smart contract function.
***
## Contract Queries
Contract queries allow developers to retrieve data from smart contracts without altering the blockchain state. This is particularly useful for reading data such as token balances, contract states, and more.
### **Example Request**
Retrieve the token balance using a contract’s view function:
```bash wrap theme={null}
curl -X POST https://mainnet.mirrornode.hedera.com/api/v1/contracts/call \
-H "Content-Type: application/json" \
-d '{
"block": "latest",
"data": "0x1f00ca74000000000000000000000000000000000000000000000000000000003b9aca00000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000163b5a00000000000000000000000000000000000000000000000000000000000b2ad5",
"to": "0x00000000000000000000000000000000002e7a5d"
}'
```
### **Expected Response**
The API returns the result of the contract’s view function in hexadecimal format:
```json wrap theme={null}
{
"result": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000efd80e2d6000000000000000000000000000000000000000000000000000000003b9aca00"
}
```
### Decoding the Result
To interpret the hexadecimal result (e.g., token balance), follow these steps:
1. **Extract the** `result`**:**
The `result` field contains the data returned by the smart contract function.
2. **Convert Hexadecimal to BigInt:**
```javascript theme={null}
const hexString = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000efd80e2d6000000000000000000000000000000000000000000000000000000003b9aca00";
const balance = BigInt(hexString);
console.log("Token Balance:", balance.toString());
```
**Output:**
```bash theme={null}
Token Balance: 32 # value represented in decimal
```
**Understanding the Logic**
* **Hexadecimal Representation:** The smart contract's `balanceOf` function returns the token balance in hexadecimal format.
* **Conversion to BigInt:** Using `BigInt` ensures accurate representation of potentially large token balances.
* **Interpretation:** The numerical value (`32` in this case) represents the token balance of the specified address
***
## EVM Transaction Simulations
Simulate EVM transactions (non-view functions) that involve state changes to test contract interactions without committing them. This can be useful for testing token transfers, approvals, and other state-altering functions without changing the blockchain state.
### **Example Request**
This example simulates a token transfer by testing contract interactions without altering the state.
```bash wrap theme={null}
curl -X POST https://mainnet.mirrornode.hedera.com/api/v1/contracts/call \
-H "Content-Type: application/json" \
-d '{
"block": "latest",
"data": "0x",
"estimate": false,
"from": "0x00000000000000000000000000000000000004e2",
"to": "0x00000000000000000000000000000000000004e4",
"value": 1000
}'
```
### **Expected Response**
The empty result indicates that the simulation ran successfully without errors:
```json theme={null}
{
"result": "0x"
}
```
***
## Decoding the Results with Ethers.js
To decode the data returned by the Mirror Node, you need the [ABI](/support/glossary#application-binary-interface-abi) of the smart contract you are interacting with. The ABI defines the structure of inputs and outputs for the contract's functions.
### Step 1: Install Ethers.js
If you haven't already, install Ethers.js using npm:
```bash theme={null}
npm install ethers
```
### Step 2: Decode the Hexadecimal Result
Below is a detailed explanation of how to extract and interpret the `result` from the API response.
**Example Scenario**
You have made an API request to retrieve a token balance, and received the following response:
```json theme={null}
{
"result": "0x0000000000007f0d"
}
```
You want to convert this hexadecimal result to a human-readable token balance.
**JavaScript Code Example**
```javascript theme={null}
const { ethers } = require('ethers');
// Example API response
const response = {
data: {
result: "0x0000000000007f0d" // Replace with actual API result
}
};
// Step 1: Extract the hex string from the API response
const hexString = response.data.result;
// Step 2: Convert the hexadecimal string to a BigInt
const tokenBalance = BigInt(hexString);
// Step 3: Display the token balance
console.log("Token Balance:", tokenBalance.toString());
```
**Output:**
```yaml theme={null}
Token Balance: 32525 # value represented in decimals
```
**Understanding the Logic**
1. **Extracting the** `result`**:**
```javascript theme={null}
const hexString = response.data.result;
```
* **Purpose:** Assign the `result` from the API response to the variable `hexString`.
* **Content:** The `hexString` contains the ABI-encoded data returned by the smart contract function.
2. **Converting Hexadecimal to BigInt:**
```javascript theme={null}
const tokenBalance = BigInt(hexString);
```
* **Purpose:** Convert the hexadecimal string to a `BigInt` for numerical operations.
* **Explanation:**
* `BigInt`**:** A JavaScript data type that can represent integers with arbitrary precision, suitable for handling large numbers often used in blockchain applications.
* **Conversion:** The `BigInt` constructor automatically parses the hexadecimal string (prefixed with `0x`) and converts it to its numerical equivalent.
3. **Displaying the Token Balance:**
```javascript theme={null}
console.log("Token Balance:", tokenBalance.toString());
```
* **Purpose:** Output the numerical value of the token balance to the console.
* **Explanation:**
* **`.toString()`:** Converts the `BigInt` to a string for readable output.
**Practical Example with Ethers.js for Complex Decoding**
For more complex return types (e.g., multiple values, arrays), Ethers.js can be used to decode the `result` based on the contract's ABI.
```javascript wrap theme={null}
const { ethers } = require('ethers');
// Example API response
const response = {
data: {
result: "0x000000000000002000000000000000000000000000000000000000000000000020000000efd80e2d6000000000000000000000000000000000000000000000000000000003b9aca00"
}
};
// Step 1: Extract the hex string from the API response
const hexString = response.data.result;
// Step 2: Define the ABI for the function you want to decode
const abi = [
'function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts)'
];
// Step 3: Create an interface using the ABI
const abiInterface = new ethers.Interface(abi);
// Step 4: Decode the result using decodeFunctionResult
const decodedResult = abiInterface.decodeFunctionResult('getAmountsIn', hexString);
// Step 5: Access the decoded data
console.log("Decoded Amounts:", decodedResult[0].toString());
```
**Output:**
```yaml theme={null}
Decoded Amounts: 32525 # value represented in tinybars
```
**Explanation**
1. **Define the ABI:**
```javascript theme={null}
const abi = [
'function getAmountsIn(uint amountOut, address[] calldata path) external view returns (uint[] memory amounts)'
];
```
* The ABI specifies the `getAmountsIn` function which returns an array of `uint256` values.
2. **Create an Interface:**
```javascript theme={null}
const abiInterface = new ethers.Interface(abi);
```
* `ethers.Interface` uses the ABI to understand how to decode the data.
3. **Decode the Result:**
```javascript theme={null}
const decodedResult = abiInterface.decodeFunctionResult('getAmountsIn', hexString);
```
* `decodeFunctionResult` interprets the `result` based on the function's return type.
4. **Access the Decoded Data:**
```javascript theme={null}
console.log("Decoded Amounts:", decodedResult[0].toString());
```
* The decoded result is accessed as `decodedResult[0]` and converted to a string for readability.
***
## Reference
* [HIP-584 Proposal](https://hips.hedera.com/hip/hip-584)
* [REST API Documentation](/reference/rest-api)
* [Swagger Documentation](https://testnet.mirrornode.hedera.com/api/v1/docs/#/contracts/contractCall)
* [Ethers.js Documentation](https://docs.ethers.org/v5/)
# Compiling Smart Contracts
Source: https://docs.hedera.com/evm/development/compiling
Compiling a smart contract involves using the contract's source code to generate its [**bytecode**](/support/glossary#bytecode) and the contract [**Application** **Binary Interface (ABI)**](/support/glossary#application-binary-interface-abi). The Ethereum Virtual Machine (EVM) executes the bytecode to understand and execute the smart contract. Meanwhile, other smart contracts use the ABI to understand how to interact with the deployed contracts on the Hedera network.
**Compiling Solidity**
The compiler for the Solidity programming language is [solc](https://docs.soliditylang.org/en/v0.8.17/installing-solidity.html) ([Solidity](/support/glossary#solidity) Compiler). You can use the compiler directly or embedded in IDEs like [Remix IDE](https://remix.ethereum.org/#lang=en\&optimize=false\&runs=200\&evmVersion=null\&version=soljson-v0.8.18+commit.87f61d96.js) or tools like Hardhat and Truffle.
***
## **Smart Contract Bytecode**
Bytecode is the machine-readable language that the EVM uses to execute smart contracts. The compiler analyzes the code, checks for syntax errors, enforces language-specific rules, and generates the corresponding bytecode.
**Example:**
This is the example bytecode output, produced in hexadecimal format, when the HelloHedera smart contract source code is compiled.
```json theme={null}
608060405234801561001057600080fd5b506040516105583803806105588339818101604052602081101561003357600080fd5b810190808051604051939291908464010000000082111561005357600080fd5b8382019150602082018581111561006957600080fd5b825186600182028301116401000000008211171561008657600080fd5b8083526020830192505050908051906020019080838360005b838110156100ba57808201518184015260208101905061009f565b50505050905090810190601f1680156100e75780820380516001836020036101000a031916815260200191505b50604052505050336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550806001908051906020019061014492919061014b565b50506101e8565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f1061018c57805160ff19168380011785556101ba565b828001600101855582156101ba579182015b828111156101b957825182559160200191906001019061019e565b5b5090506101c791906101cb565b5090565b5b808211156101e45760008160009055506001016101cc565b5090565b610361806101f76000396000f3fe608060405234801561001057600080fd5b50600436106100365760003560e01c80632e9826021461003b57806332af2edb146100f6575b600080fd5b6100f46004803603602081101561005157600080fd5b810190808035906020019064010000000081111561006e57600080fd5b82018360208201111561008057600080fd5b803590602001918460018302840111640100000000831117156100a257600080fd5b91908080601f016020809104026020016040519081016040528093929190818152602001838380828437600081840152601f19601f820116905080830192505050505050509192919290505050610179565b005b6100fe6101ec565b6040518080602001828103825283818151815260200191508051906020019080838360005b8381101561013e578082015181840152602081019050610123565b50505050905090810190601f16801561016b5780820380516001836020036101000a031916815260200191505b509250505060405180910390f35b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146101d1576101e9565b80600190805190602001906101e792919061028e565b505b50565b606060018054600181600116156101000203166002900480601f0160208091040260200160405190810160405280929190818152602001828054600181600116156101000203166002900480156102845780601f1061025957610100808354040283529160200191610284565b820191906000526020600020905b81548152906001019060200180831161026757829003601f168201915b5050505050905090565b828054600181600116156101000203166002900490600052602060002090601f016020900481019282601f106102cf57805160ff19168380011785556102fd565b828001600101855582156102fd579182015b828111156102fc5782518255916020019190600101906102e1565b5b50905061030a919061030e565b5090565b5b8082111561032757600081600090555060010161030f565b509056fea26469706673582212201644465f5f73dfd73a518b57770f5adb27f025842235980d7a0f4e15b1acb18e64736f6c63430007000033
```
***
## **Smart Contract Application Binary Interface (ABI)**
The ABI is a JSON (JavaScript Object Notation) file that represents the interface definition for the smart contract. It specifies function signatures, input parameters, return types, and other relevant details of the contract's interface. The ABI helps developers understand how to interact with the smart contract in their distributed applications.
**Example:**
This is the example ABI output produced when the HelloHedera smart contract is compiled.
```json theme={null}
"abi": [
{
"inputs": [
{
"internalType": "string",
"name": "message_",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "get_message",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "message_",
"type": "string"
}
],
"name": "set_message",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
}
```
#### **Additional Resources:**
* [Ethereum: Compiling Smart Contracts](https://ethereum.org/en/developers/docs/smart-contracts/compiling/)
***
## Compiling Smart Contract Example
**➡** [**Hardhat Tutorial**](/evm/tools/hardhat)
## Additional Resources
**➡** [**HTS Precompile Methods**](https://github.com/hiero-ledger/hiero-contracts/blob/main/contracts/token-service/README.md)
# Creating Smart Contracts
Source: https://docs.hedera.com/evm/development/creating
A [smart contract](/support/glossary#smart-contract) is an immutable program consisting of a set of logic (state variables, functions, event handlers, etc.) or rules that can be deployed, stored, and accessed on a [distributed ledger technology](/support/glossary#distributed-ledger-technology-dlt) such as Hedera. The functions contained within a smart contract can update and manage the state of the contract and read data from the deployed contract. They may also create and call other smart contracts functions on the network. Smart contracts are secure, tamper-proof, and transparent, offering a new level of trust and efficiency.
Hedera supports any language that compiles to the Ethereum Mainnet. This includes [Solidity](/support/glossary#solidity) and [Vyper](/support/glossary#vyper). These programming languages compile code and produce [bytecode](/support/glossary#bytecode) that the [Ethereum Virtual Machine (EVM)](/support/glossary#ethereum-virtual-machine-evm) can interpret and understand.
* To learn more about the Solidity programming language, check out the documentation maintained by the Solidity team [here](https://docs.soliditylang.org/en/v0.8.19/).
* To learn more about Vyper, check out the documentation maintained by the Vyper team [here](https://docs.vyperlang.org/en/stable/).
In addition, many tools are available to write and compile smart contracts, including the popular [Remix IDE](/support/glossary#remix-ide) and [Hardhat](/support/glossary#hardhat). The Remix IDE is a user-friendly platform that allows you to easily write and compile your smart contracts and perform other tasks such as debugging and testing. Using these tools, you can create powerful and secure smart contracts that can be used for various purposes, from simple token transfers to complex financial instruments.
**Example**
The following is a very simple example of a smart contract written in the Solidity programming language. The smart contract defines the `owner` and `message` state variables, along with functions like `set_message` (which modifies state details by writing) and `get_message`(which reads state details).
```solidity theme={null}
pragma solidity >=0.7.0 <0.8.9;
contract HelloHedera {
// the contract's owner, set in the constructor
address owner;
// the message we're storing
string message;
constructor(string memory message_) {
// set the owner of the contract for `kill()`
owner = msg.sender;
message = message_;
}
function set_message(string memory message_) public {
// only allow the owner to update the message
require(msg.sender == owner);
message = message_;
}
// return a string
function get_message() public view returns (string memory) {
return message;
}
}
```
***
## Things you should consider when creating a contract
#### **Automatic Token Associations**
An auto association slot is one or more slots you approve that allow tokens to be sent to your contract without explicit authorization for each token type. If this property is not set, you must associate each token before it is transferred to the contract for the transfer to be successful via the `TokenAssociateTransaction` in the SDKs. Learn more about auto-token associations [here](/learn/core-concepts/accounts/account-properties#automatic-token-associations).
This functionality is exclusively accessible when configuring a `ContractCreateTransaction` API through the Hedera SDKs. If you are deploying a contract on Hedera using EVM tools such as Hardhat and the Hedera JSON RPC Relay, please note that this property cannot be configured, as EVM tools lack compatibility with Hedera's unique features.
#### **Admin Key**
Contracts have the option to have an [admin key](https://github.com/hashgraph/hedera-protobufs/blob/main/services/contract_create.proto#L117). This concept is native to Hedera contracts and allows the contract account properties to be updated. Note that this does not impact the contract [bytecode](/support/glossary#bytecode) and does not relate to upgradability. If the admin key is not set, you will not be able to update the following Hedera native properties (noted in [ContractUpdateTransactionBody](https://github.com/hashgraph/hedera-protobufs/blob/main/services/contract_update.proto) protobuf) for your contract once it is deployed:
* [`autoRenewPeriod`](https://github.com/hashgraph/hedera-protobufs/blob/main/services/contract_update.proto#L78)
* [`memoField`](https://github.com/hashgraph/hedera-protobufs/blob/main/services/contract_update.proto#L88)
* [`max_automatic_token_associations`](https://github.com/hashgraph/hedera-protobufs/blob/main/services/contract_update.proto#L105)
* [`auto_renew_account_id`](https://github.com/hashgraph/hedera-protobufs/blob/main/services/contract_update.proto#L111)
* [`staked_id`](https://github.com/hashgraph/hedera-protobufs/blob/main/services/contract_update.proto#L116)
* [`decline_reward`](https://github.com/hashgraph/hedera-protobufs/blob/main/services/contract_update.proto#L134)
You cannot set the admin key field if you deploy a contract via tools like Hardhat. This field can be set if desired by deploying a contract using one of the Hedera [SDKs](/native/fundamentals).
#### **Note**
Need to deploy a contract with large bytecode? Hedera supports **jumbo ethereum transactions** ([HIP-1086](https://hips.hedera.com/hip/hip-1086)) to handle big payloads directly, no file uploads required for most cases.
*📣 Learn more about jumbo transactions on the* [*Understanding Hedera's EVM Differences and Compatibility*](/evm/differences#jumbo-ethereum-transactions) *and on the* [*`EthereumTransaction` SDK page*](/native/smart-contracts/ethereum-transaction)*.*
#### **Max Contract Storage Size**
Each contract on Hedera has a storage size limit of 16,384,000 key value pairs (\~500MB).
#### **Rent**
While rent is not enabled for contracts deployed on Hedera today, you will want to be familiar with the concept of rent, as it may potentially impact the costs of maintaining your contract state on the network. Please refer to the Smart Contract Rent documentation [here](/evm/development/rent).
#### **Transaction and Gas Fees**
There are Hedera transaction fees and EVM fees associated with deploying a contract. To view the list of base fees, check out the fees page [here](/networks/fees) and the fee estimator calculator [here](https://hedera.com/fees).
***
## Smart Contract FAQs
A smart contract is a program that is written in a language that can be interpreted by the EVM. Please refer to the [glossary](/support/glossary) for more keywords and definitions.
Hedera supports the official [Ethereum Virtual Machine](https://ethereum.org/en/developers/docs/evm/) and therefore any smart contract language that conforms to standard EVM code, such as Solidity or Vyper.
You can use Remix IDE or other Ethereum ecosystem tools to write, compile, and deploy your smart contract on Hedera. Check out our EVM-compatible tools [here](/learn#evm-compatible-tools).
On your favorite trusted Block Explorer (also called Mirror Node Explorer on Hedera). To view community-hosted explorers check out the network explorer tools page [here](/networks/community-mirror-nodes-explorers).
Hedera supports ERC-20 and ERC-721 token standards and can find the full list of supported standards [here](/evm/tokens).
# Deploying Smart Contracts
Source: https://docs.hedera.com/evm/development/deploying
After compiling your smart contract, you can deploy it to the Hedera network. The constructor's "*init code*" includes the contract's entire bytecode. When deploying, the EVM is expected to be supplied with both the smart contract [bytecode](/support/glossary#bytecode) and the gas required to execute and deploy the contract. Post-deployment, the constructor is removed, leaving only the `runtime_bytecode` for future contract interactions.
**➡** [**Hyperledger Besu EVM**](#hyperledger-besu-evm-on-hedera)
**➡** [**Cancun Hard Fork**](#cancun-hard-fork)
**➡** [**Solidity Variables and Opcodes**](#solidity-variables-and-opcodes)
***
## Ethereum Virtual Machine (EVM)
The [Ethereum Virtual Machine (EVM)](/support/glossary#ethereum-virtual-machine-evm) is a run-time environment for executing smart contracts written in EVM native programming languages, like Solidity. The source code must be compiled into bytecode for the EVM to execute a given smart contract.
On Hedera, users can interact with the EVM-compatible environment in several ways. They can submit `ContractCreate`, `EthereumTransaction`, or make `eth_sendRawTransaction` RPC calls with the contract bytecode directly. These various paths allow developers to deploy and manage smart contracts efficiently.
When the EVM receives the bytecode, it will be further broken down into operation codes ([opcodes](/support/glossary#opcodes)). The EVM opcodes represent the specific instructions it can perform. Each opcode is one byte and has its own gas cost associated with it. The cost per opcode for the Ethereum Cancun hard fork can be found [here](https://www.evm.codes/?fork=cancun).
#### Smart Contract Opcode Example
```solidity theme={null}
PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x40 MLOAD PUSH2 0x558 CODESIZE SUB DUP1 PUSH2 0x558 DUP4 CODECOPY DUP2 DUP2 ADD PUSH1 0x40 MSTORE PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x33 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 MLOAD PUSH1 0x40 MLOAD SWAP4 SWAP3 SWAP2 SWAP1 DUP5 PUSH5 0x100000000 DUP3 GT ISZERO PUSH2 0x53 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP4 DUP3 ADD SWAP2 POP PUSH1 0x20 DUP3 ADD DUP6 DUP2 GT ISZERO PUSH2 0x69 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 MLOAD DUP7 PUSH1 0x1 DUP3 MUL DUP4 ADD GT PUSH5 0x100000000 DUP3 GT OR ISZERO PUSH2 0x86 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 DUP4 MSTORE PUSH1 0x20 DUP4 ADD SWAP3 POP POP POP SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0xBA JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x9F JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0xE7 JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP PUSH1 0x40 MSTORE POP POP POP CALLER PUSH1 0x0 DUP1 PUSH2 0x100 EXP DUP2 SLOAD DUP2 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF MUL NOT AND SWAP1 DUP4 PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND MUL OR SWAP1 SSTORE POP DUP1 PUSH1 0x1 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH2 0x144 SWAP3 SWAP2 SWAP1 PUSH2 0x14B JUMP JUMPDEST POP POP PUSH2 0x1E8 JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH1 0x1F LT PUSH2 0x18C JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH2 0x1BA JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH2 0x1BA JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH2 0x1B9 JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH2 0x19E JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH2 0x1C7 SWAP2 SWAP1 PUSH2 0x1CB JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x1E4 JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH2 0x1CC JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST PUSH2 0x361 DUP1 PUSH2 0x1F7 PUSH1 0x0 CODECOPY PUSH1 0x0 RETURN INVALID PUSH1 0x80 PUSH1 0x40 MSTORE CALLVALUE DUP1 ISZERO PUSH2 0x10 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST POP PUSH1 0x4 CALLDATASIZE LT PUSH2 0x36 JUMPI PUSH1 0x0 CALLDATALOAD PUSH1 0xE0 SHR DUP1 PUSH4 0x2E982602 EQ PUSH2 0x3B JUMPI DUP1 PUSH4 0x32AF2EDB EQ PUSH2 0xF6 JUMPI JUMPDEST PUSH1 0x0 DUP1 REVERT JUMPDEST PUSH2 0xF4 PUSH1 0x4 DUP1 CALLDATASIZE SUB PUSH1 0x20 DUP2 LT ISZERO PUSH2 0x51 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP2 ADD SWAP1 DUP1 DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH5 0x100000000 DUP2 GT ISZERO PUSH2 0x6E JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP3 ADD DUP4 PUSH1 0x20 DUP3 ADD GT ISZERO PUSH2 0x80 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST DUP1 CALLDATALOAD SWAP1 PUSH1 0x20 ADD SWAP2 DUP5 PUSH1 0x1 DUP4 MUL DUP5 ADD GT PUSH5 0x100000000 DUP4 GT OR ISZERO PUSH2 0xA2 JUMPI PUSH1 0x0 DUP1 REVERT JUMPDEST SWAP2 SWAP1 DUP1 DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP4 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP4 DUP4 DUP1 DUP3 DUP5 CALLDATACOPY PUSH1 0x0 DUP2 DUP5 ADD MSTORE PUSH1 0x1F NOT PUSH1 0x1F DUP3 ADD AND SWAP1 POP DUP1 DUP4 ADD SWAP3 POP POP POP POP POP POP POP SWAP2 SWAP3 SWAP2 SWAP3 SWAP1 POP POP POP PUSH2 0x179 JUMP JUMPDEST STOP JUMPDEST PUSH2 0xFE PUSH2 0x1EC JUMP JUMPDEST PUSH1 0x40 MLOAD DUP1 DUP1 PUSH1 0x20 ADD DUP3 DUP2 SUB DUP3 MSTORE DUP4 DUP2 DUP2 MLOAD DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 DUP1 DUP4 DUP4 PUSH1 0x0 JUMPDEST DUP4 DUP2 LT ISZERO PUSH2 0x13E JUMPI DUP1 DUP3 ADD MLOAD DUP2 DUP5 ADD MSTORE PUSH1 0x20 DUP2 ADD SWAP1 POP PUSH2 0x123 JUMP JUMPDEST POP POP POP POP SWAP1 POP SWAP1 DUP2 ADD SWAP1 PUSH1 0x1F AND DUP1 ISZERO PUSH2 0x16B JUMPI DUP1 DUP3 SUB DUP1 MLOAD PUSH1 0x1 DUP4 PUSH1 0x20 SUB PUSH2 0x100 EXP SUB NOT AND DUP2 MSTORE PUSH1 0x20 ADD SWAP2 POP JUMPDEST POP SWAP3 POP POP POP PUSH1 0x40 MLOAD DUP1 SWAP2 SUB SWAP1 RETURN JUMPDEST PUSH1 0x0 DUP1 SLOAD SWAP1 PUSH2 0x100 EXP SWAP1 DIV PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND CALLER PUSH20 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF AND EQ PUSH2 0x1D1 JUMPI PUSH2 0x1E9 JUMP JUMPDEST DUP1 PUSH1 0x1 SWAP1 DUP1 MLOAD SWAP1 PUSH1 0x20 ADD SWAP1 PUSH2 0x1E7 SWAP3 SWAP2 SWAP1 PUSH2 0x28E JUMP JUMPDEST POP JUMPDEST POP JUMP JUMPDEST PUSH1 0x60 PUSH1 0x1 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV DUP1 PUSH1 0x1F ADD PUSH1 0x20 DUP1 SWAP2 DIV MUL PUSH1 0x20 ADD PUSH1 0x40 MLOAD SWAP1 DUP2 ADD PUSH1 0x40 MSTORE DUP1 SWAP3 SWAP2 SWAP1 DUP2 DUP2 MSTORE PUSH1 0x20 ADD DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV DUP1 ISZERO PUSH2 0x284 JUMPI DUP1 PUSH1 0x1F LT PUSH2 0x259 JUMPI PUSH2 0x100 DUP1 DUP4 SLOAD DIV MUL DUP4 MSTORE SWAP2 PUSH1 0x20 ADD SWAP2 PUSH2 0x284 JUMP JUMPDEST DUP3 ADD SWAP2 SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 JUMPDEST DUP2 SLOAD DUP2 MSTORE SWAP1 PUSH1 0x1 ADD SWAP1 PUSH1 0x20 ADD DUP1 DUP4 GT PUSH2 0x267 JUMPI DUP3 SWAP1 SUB PUSH1 0x1F AND DUP3 ADD SWAP2 JUMPDEST POP POP POP POP POP SWAP1 POP SWAP1 JUMP JUMPDEST DUP3 DUP1 SLOAD PUSH1 0x1 DUP2 PUSH1 0x1 AND ISZERO PUSH2 0x100 MUL SUB AND PUSH1 0x2 SWAP1 DIV SWAP1 PUSH1 0x0 MSTORE PUSH1 0x20 PUSH1 0x0 KECCAK256 SWAP1 PUSH1 0x1F ADD PUSH1 0x20 SWAP1 DIV DUP2 ADD SWAP3 DUP3 PUSH1 0x1F LT PUSH2 0x2CF JUMPI DUP1 MLOAD PUSH1 0xFF NOT AND DUP4 DUP1 ADD OR DUP6 SSTORE PUSH2 0x2FD JUMP JUMPDEST DUP3 DUP1 ADD PUSH1 0x1 ADD DUP6 SSTORE DUP3 ISZERO PUSH2 0x2FD JUMPI SWAP2 DUP3 ADD JUMPDEST DUP3 DUP2 GT ISZERO PUSH2 0x2FC JUMPI DUP3 MLOAD DUP3 SSTORE SWAP2 PUSH1 0x20 ADD SWAP2 SWAP1 PUSH1 0x1 ADD SWAP1 PUSH2 0x2E1 JUMP JUMPDEST JUMPDEST POP SWAP1 POP PUSH2 0x30A SWAP2 SWAP1 PUSH2 0x30E JUMP JUMPDEST POP SWAP1 JUMP JUMPDEST JUMPDEST DUP1 DUP3 GT ISZERO PUSH2 0x327 JUMPI PUSH1 0x0 DUP2 PUSH1 0x0 SWAP1 SSTORE POP PUSH1 0x1 ADD PUSH2 0x30F JUMP JUMPDEST POP SWAP1 JUMP INVALID LOG2 PUSH5 0x6970667358 0x22 SLT KECCAK256 AND DIFFICULTY CHAINID 0x5F 0x5F PUSH20 0xDFD73A518B57770F5ADB27F025842235980D7A0F 0x4E ISZERO 0xB1 0xAC 0xB1 DUP15 PUSH5 0x736F6C6343 STOP SMOD STOP STOP CALLER
```
Reference: [https://ethervm.io/](https://ethervm.io/)
***
## Deployment Options
**SDK**
You can use a [Hedera SDK](/native/fundamentals) to deploy your smart contract bytecode to the network. This approach does not require using any EVM tools like Hardhat or an instance of the Hedera JSON-RPC Relay.
**Hardhat**
Hardhat can be used to deploy your smart contract by pointing to a community-hosted [JSON-RPC Relay](/evm/development/json-rpc). However, EVM tools do not support features that are native to Hiero Contracts like:
* Admin Key
* Contract Memo
* Automatic Token Associations
* Auto Renew Account ID
* Staking Node ID or Account ID
* Decline Staking Rewards
If you need to set any of the above properties for your contract, you will have to call the `ContractCreateTransaction` API using one of the [Hedera SDKs.](/native/fundamentals)
### Deploying Large Contracts
Hedera supports **jumbo Ethereum transactions (**[**HIP-1086**](https://hips.hedera.com/hip/hip-1086)**)** for large bytecode payloads. You can include up to **24KB for contract creation** and **128KB for contract calls** directly in `ethereumData`, without using the File Service (`callDataFileId`).
However, jumbo transactions:
* Can’t be included in batch transactions (`TransactionList`).
* Are subject to network throttling based on bytes per second and per-node limits.
#### Bytecode and Gas Essentials
When deploying contracts, gas must cover both intrinsic gas and the cost of executing deployment code. Intrinsic gas includes a base fee (21,000) plus a per-byte cost for `callData`:
* Intrinsic gas includes a base fee (21,000) plus a per-byte cost for `callData`:
* 4 gas per zero byte
* 16 gas per non-zero byte
#### Example
If your contract bytecode is 10KB, with 20% (2KB) as zero bytes and 80% (8KB) as non-zero bytes:
* **gas for zero bytes**: 4 × 2,048 = 8,192
* **gas for non-zero bytes**: 16 × 8,192 = 131,072
* **total intrinsic gas** = 21,000 + 8,192 + 131,072 = 160,264
Ensure you adjust `gasLimit` (RLP) and `maxGasAllowance` (wrapper) to cover this total gas.
📣 Learn more on the [Gas and Fees page,](/evm/development/gas-fees) [EthereumTransaction SDK page](/native/smart-contracts/ethereum-transaction), and the [Understanding Hedera's EVM Differences and Compatibility page](/evm/differences).
***
## Hyperledger Besu EVM on Hedera
The Hedera network nodes utilize the [HyperLedger Besu EVM ](/support/glossary#hyperledger-besu-evm)Client written in Java as an execution layer for Ethereum-type transactions. The codebase is up to date with the current Ethereum Mainnet hard forks. The Besu EVM client library is used without hooks for Ethereum's consensus, networking, and storage features. Instead, Hedera hooks into its own Hashgraph consensus, Gossip communication, and [Virtual Merkle Trees](/support/glossary#virtual-merkle-tree) components for greater fault tolerance, finality, and scalability.
As of the Hedera Mainnet release [`0.50.0`](/networks/release-notes/services#v0.50), the Besu EVM client is configured to support the Cancun hard fork of the Ethereum Mainnet, with some modifications.
### **Cancun Hard Fork**
The smart contract platform has been upgraded to support the visible EVM changes introduced in the [Cancun](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/cancun.md) hard fork. This includes adding new opcodes for transient storage and memory copy, semantic updates for opcodes introduced certain operations introduced in the [Shanghai](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/shanghai.md), [London](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/london.md), [Istanbul](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/istanbul.md), and [Berlin](https://github.com/ethereum/execution-specs/blob/master/network-upgrades/mainnet-upgrades/berlin.md) hard forks, except those with changes in block production, data serialization, and the double fee market.
As of the Consensus Node [0.22](/networks/release-notes/services#v0.22) release, gas and input data costs are charged. The amount of intrinsic gas consumed is a constant charge that occurs before any code executes. The intrinsic gas cost is 21,000. The associated cost of input data is 16 gas for each byte of data that is not zero and 4 gas for each byte of data that is zero. The amount of intrinsic gas consumed is charged in relation to the data supplied when making a contract call to the function parameters of external contracts. The gas schedule and the fees table can be found in the gas section of this documentation page.
#### Proto-Danksharding
As an interim solution to full sharding, introduced in the Cancun hard fork, the proto-danksharding offers some of the advantages of sharding with reduced complexity and infrastructure changes that are part of a sharding implementation. This, in turn, opens the gates for adding "blobs" of data to append to blocks to increase data availability further and allow more processing efficiency.
Blobs are big data objects within blocks. These can be utilized to store rollups (Layer 2 solutions) and different kinds of apps requiring big data objects to be stored in an efficient way. This is data off-chain for the validators and requires minimal processing on their part. It reduces the computational load on the network and hence reduces the transaction gas fee.
#### ❌ Blobs supported on Hedera?
Hedera does not provide blobs under [EIP-4844](https://eips.ethereum.org/EIPS/eip-4844). [HIP-866](https://hips.hedera.com/hip/hip-866) defines how Hedera behaves without blob support. To preserve compatibility and future design space, Hedera will act as if blobs are not being added. This allows existing contracts dependent on blob behavior to function without blobs. Blobs will be prevented from entering the system by prohibiting "Type 3" transactions, which enable blobs. This will keep blobs out of the EVM's concern without affecting other desirable interactions on Hedera.
### Solidity Variables and Opcodes
The table below defines the mapping of Solidity variables and operation codes to Hedera. The full list of supported Opcodes for the Cancun hard fork can be found [here](https://www.evm.codes/).
Solidity
Opcode
Hedera
address
The address is a mapping of shard.realm.number (0.0.10) into a 20 byte Solidity address. The address can be a Hedera account ID or contract ID in Solidity format.
block.basefee
BASEFEE
The BASEFEE opcode will return zero. Hedera does not use the Fee Market mechanism this is designed to support.
block.chainId
CHAINID
The CHAINID opcode will return 295(hex 0x0127) for mainnet, 296( hex 0x0128) for testnet, 297( hex 0x0129) for previewnet, and 298 (0x12A) for development networks.
block.coinbase
COINBASE
The COINBASE operation will return the funding account (Hedera transaction fee collecting account 0.0.98).
block.number
The index of the record file (not recommended, use block.timestamp).
block.timestamp
The transaction consensus timestamp.
block.difficulty
Always zero.
block.gaslimit
GASLIMIT
The GASLIMIT operation will return the gasLimit of the transaction. The transaction gasLimit will be the lowest of the gas limit requested in the transaction or a global upper gas limit configured for all smart contracts.
msg.sender
The address of the Hedera contract ID or account ID in Solidity format that called this contract. For the root level or for delegate chains that go to the root, it is the account ID paying for the transaction.
msg.value
The value associated to the transaction associated in tinybar.
tx.origin
The account ID paying for the transaction, regardless of depth.
tx.gasprice
Fixed (varies with the global fee schedule and exchange rate).
selfdestruct
(address payable recipient)
SELFDESTRUCT
Address will not be reusable due to Hedera’s account numbering policies. On SELFDESTRUCT the contracts HBAR and HTS tokens are transferred to the recipients. If the recipient does not exist or does not have an allowance for any of the HTS tokens, this opcode will fail.
\.code
Precompile contract addresses will report no code, including HTS System contract.
\.codehash
Precompile contract addresses will report the empty code hash.
PRNGSEED
This opcode returns a random number based on the n-3 record running hash.
delegateCall
Contracts may no longer use delegateCall() to invoke system contracts. Contracts should instead use the call() method.
blobVersionedHashesAtIndex
BLOBHASH
The BLOBHASH operation will return all zeros at all times.
blobBaseFee
BLOBBASEFEE
The BLOBBASEFEE operation will return
1 at all times.
Reference: [HIP-866](https://hips.hedera.com/hip/hip-866), [HIP-868](https://hips.hedera.com/hip/hip-868)
***
### Limitation on `fallback()` / `receive()` Functions in Hiero Contracts
When developing smart contracts on Hedera, it's important to understand that the `fallback()` and `receive()` functions **do not** get triggered when a contract receives HBAR via a crypto transfer.
In Ethereum, these functions act as "catch-all" mechanisms when a contract receives Ether. In Hedera, however, contract balances may change through native HAPI operations, independent of EVM message calls, making it impossible to maintain balance-related invariants with just the `fallback()` or `receive()` methods.
#### Impacted Variables
* **`msg.sender`:** The address initiating the contract call.
* **`msg.value`:** The amount of HBAR sent along with the call.
#### Key Points
* Developers should implement explicit functions to handle HBAR transfers.
* To disable native operations entirely, consider submitting a [Hedera Improvement Proposal (HIP)](https://hips.hedera.com/).
Understanding these differences is crucial for anyone developing smart contracts on Hedera, particularly those familiar with Ethereum.
***
## FAQs
Yes, you can use Solidity functions directly with the Hedera EVM. However, refer to the [Solidity Variables and Opcodes](#solidity-variables-and-opcodes) table to understand any modifications to opcode descriptions that better reflect their behavior on the Hedera network.
Yes, hedera supports jumbo ethereum transactions (HIP-1086), allowing up to **24kb for contract creation** and **128kb for contract calls**. this eliminates the need for uploading bytecode to the file service in most cases. [Learn more](/evm/differences#jumbo-ethereum-transactions).
No, jumbo ethereum transactions cannot be included in a `TransactionList` (batch). each jumbo transaction must be submitted individually.
Gas covers intrinsic costs (base + per-byte of `callData`) and execution costs (opcodes run by the EVM). Ensure your `gasLimit` and `maxGasAllowance` cover the total. See the [gas and fees page](/evm/development/gas-fees) for details.
Hedera does not trigger `fallback()` or `receive()` functions on HBAR transfers. Balances may change through native operations, so use explicit functions to handle HBAR. [Learn more](#limitation-on-fallback-receive-functions-in-hedera-smart-contracts).
Yes, but Hardhat cannot set Hedera-native properties like admin key or token associations. For these, use the [Hedera SDK](/native/fundamentals).
If your contract relies on blob-related opcodes introduced in the Cancun hard fork, you can still deploy it on Hedera. The blob-related opcodes **will** **not** fail. They'll return default values as [specified by the EVM](https://www.evm.codes/?fork=cancun).
Yes, while the Hedera EVM supports the updated opcodes from the Cancun hard fork, you should know the intrinsic gas costs and input data charges specific to Hedera. Refer to the [gas schedule and fees](/evm/development/gas-fees) table for more information.
# Forking Hedera Network for Local Testing
Source: https://docs.hedera.com/evm/development/forking
This guide explains how fork testing works on Hedera, how it differs from traditional EVM chains, and how the [hedera-forking](https://github.com/hashgraph/hedera-forking) library enables local development with Hedera System Contracts.
***
## What is Fork Testing?
**Fork Testing** (also known as **Fixtures**) is an Ethereum Development Environment feature that optimizes test execution for smart contracts. It enables:
* **Snapshotting blockchain state** - Avoiding recreation of the entire blockchain state for each test
* **Using remote state locally** - Any modifications only affect the local (forked) network
* **No private key requirements** - Test against remote network state without managing keys
* **Debugging tools** - Use `console.log` and other debugging features during testing
Popular Ethereum development environments that support fork testing include:
* [Foundry](https://book.getfoundry.sh/forge/fork-testing) (using Anvil)
* [Hardhat](https://hardhat.org/hardhat-network/docs/overview#mainnet-forking) (using EDR/EthereumJS)
***
## Why is Hedera Different?
### Standard EVM Contracts Work Out-of-the-Box
Fork testing works seamlessly for standard EVM smart contracts that don't involve Hedera-specific services. The local test networks provided by development environments are replicas of the Ethereum network.
### Hedera System Contracts Require Emulation
Fork testing does not work out-of-the-box for contracts that use Hedera-specific services like:
* **Hedera Token Service (HTS)** at address `0x167`
* **Exchange Rate** at address `0x168`
* **PRNG** at address `0x169`
* **Hedera Account Service** at address `0x16a`
This is because when the development environment tries to fetch the code at these addresses, the JSON-RPC Relay returns `0xfe` (invalid opcode):
```console theme={null}
$ cast code --rpc-url https://mainnet.hashio.io/api 0x0000000000000000000000000000000000000167
0xfe
```
This leads to the error `EvmError: InvalidFEOpcode` when running tests. This is precisely where the hedera-forking library comes in to provide an emulation layer for Hedera System Contracts. For example, with Hardhat, the plugin intercepts JSON-RPC calls to return the appropriate bytecode and state for HTS and for Foundry, the library uses `ffi` to fetch state from the Mirror Node. This is explained in detail below.
***
## How Hedera Forking Works
The [hedera-forking](https://github.com/hashgraph/hedera-forking) project provides an emulation layer for Hedera System Contracts written in Solidity. Since it's written in Solidity, it can be executed in any development network environment.
### Architecture Overview
The project consists of two main components:
1. **Solidity Contracts** - Provide HTS emulation designed for forked networks
2. **JS Package** - Hooks into JSON-RPC layer to fetch appropriate data when HTS or Hedera Tokens are invoked (used by Hardhat)
Both the Foundry library and the Hardhat plugin use the main `HtsSystemContract` implementation. This contract provides the behavior of HTS, but it is state agnostic - meaning the HTS and token state must be provided elsewhere.
**Given Foundry and Hardhat provide different capabilities, they differ significantly in how the state is provided to HTS.**
***
## How Token State is Retrieved
Foundry and Hardhat use different mechanisms to retrieve token state from the
Mirror Node. Understanding these differences is important for troubleshooting
and optimizing your fork testing workflow.
### Foundry Library Approach
The Foundry library uses a proactive prefetch approach where token state is fetched within Solidity contracts before it's needed.
**Key Components:**
* `HtsSystemContractJson` - Extends `HtsSystemContract` with JSON data source support
* `MirrorNodeFFI` - Fetches data from Mirror Node using Foundry's `ffi` cheatcode
**How it works:**
1. When your test calls `Hsc.htsSetup()`, the library deploys `HtsSystemContractJson` at address `0x167`
2. When you access token state (e.g., `balanceOf`), `HtsSystemContractJson` overrides the slot access
3. The contract calls `MirrorNodeFFI` which uses `ffi` to execute `curl` (or PowerShell on Windows) to fetch data from the Mirror Node
4. The fetched data is written to storage using `vm.store` cheatcode (not `sstore`) to avoid `StateChangeDuringStaticCall` errors
5. The data is then returned to your test
```mermaid theme={null}
sequenceDiagram
autonumber
box Local (Foundry)
actor user as User Test
participant anvil as Anvil (Local Network)
participant hts as HtsSystemContractJson (at 0x167)
participant ffi as MirrorNodeFFI (via ffi/curl)
end
box Remote
participant mirror as Mirror Node
end
user->>+anvil: address(Token).balanceOf(account)
Note over anvil: Token Proxy delegates to 0x167
anvil->>+hts: balanceOf(account)
hts->>+ffi: fetchBalance(token, accountNum)
ffi->>+mirror: curl: GET /api/v1/tokens/{id}/balances
mirror-->>-ffi: { balances: [... ] }
ffi-->>-hts: JSON response
Note over hts: Parse JSON and store via vm.store
hts-->>-anvil: balance value
anvil-->>-user: balance value
```
**Why FFI is Required:**
* Foundry does not allow creating a JSON-RPC forwarder like Hardhat
* However, Foundry allows hooking into internal contract calls via cheatcodes
* The `ffi` cheatcode enables executing external commands (like `curl`) from Solidity
### Hardhat Plugin Approach
The Hardhat plugin uses a reactive interception approach where a Worker thread intercepts JSON-RPC calls made by Hardhat.
**Key Components:**
* **JSON-RPC Forwarder** - A Worker thread that intercepts `eth_getCode` and `eth_getStorageAt` calls
* **MirrorNodeClient** (JavaScript) - Fetches data from Mirror Node using the `fetch` API
**How it works:**
1. When your test runs, Hardhat makes JSON-RPC calls to fetch remote state
2. The Hardhat plugin's Worker intercepts `eth_getCode` and `eth_getStorageAt` calls
3. For `eth_getCode(0x167)`: Returns the compiled `HtsSystemContract` bytecode
4. For `eth_getCode(tokenAddress)`: Returns the HIP-719 Token Proxy bytecode
5. For `eth_getStorageAt(token, slot)`: Uses the storage layout to map the slot to a field, then fetches the value from Mirror Node
6. The fetched data is returned to Hardhat's local network
```mermaid theme={null}
sequenceDiagram
autonumber
box Local (Hardhat)
actor user as User Test
participant edr as EDR (Local Network)
participant plugin as Hardhat Forking Plugin (JSON-RPC Forwarder)
end
box Remote
participant mirror as Mirror Node
end
user->>+edr: address(Token).totalSupply()
edr->>+plugin: eth_getCode(Token)
plugin->>+mirror: GET /api/v1/tokens/{tokenId}
mirror-->>-plugin: Token {}
plugin-->>-edr: HIP-719 Token Proxy bytecode (delegate calls to 0x167)
edr->>+plugin: eth_getCode(0x167)
plugin-->>-edr: HtsSystemContract bytecode
edr->>+plugin: eth_getStorageAt(Token, slot)
Note over plugin: Map slot to field using storage layout
plugin->>+mirror: GET /api/v1/tokens/{tokenId}
mirror-->>-plugin: Token{}
plugin-->>-edr: Token{}. totalSupply
edr-->>-user: Token{}.totalSupply
```
**Why a Worker Thread is Required:**
* Hardhat does not allow hooking into internal contract calls (see [issue #56](https://github.com/hashgraph/hedera-forking/issues/56))
* The plugin must intercept at the JSON-RPC level before Hardhat processes the requests
* The Worker thread runs asynchronously to handle the interception
### Comparison: Foundry vs Hardhat Approaches
| Aspect | Foundry Library | Hardhat Plugin |
| ------------------ | -------------------------------------- | --------------------------------------- |
| **State Fetching** | Proactive (prefetch in Solidity) | Reactive (intercept JSON-RPC) |
| **Data Fetcher** | `MirrorNodeFFI` (Solidity + curl) | `MirrorNodeClient` (JavaScript + fetch) |
| **Hook Point** | Internal contract calls via cheatcodes | JSON-RPC layer via Worker thread |
| **Requirement** | `ffi = true` in foundry. toml | `chainId` and `workerPort` in config |
| **OS Dependency** | curl (Unix) or PowerShell (Windows) | Node.js fetch API |
| **Storage Writes** | `vm.store` cheatcode | Returned via JSON-RPC response |
***
## HTS Supported Methods
The emulation layer supports a subset of HTS functionality. Refer to [https://github.com/hashgraph/hedera-forking#hedera-token-service-supported-methods](https://github.com/hashgraph/hedera-forking#hedera-token-service-supported-methods) for the latest list.
***
## Limitations and Important Notes
The HTS emulation contract **SHOULD ONLY** be used to ease development workflow when working with Hedera Tokens. The HTS emulation contract **DOES NOT** replicate Hedera Token Services fully. Behavior might differ when switching from local development to a real Hedera network.
**Always test your contracts against a real Hedera network before launching your contracts.**
### Key Limitations
1. **Behavior differences** - Some edge cases may behave differently in emulation vs. the real Hedera network.
2. **Block number considerations** - When forking from a specific block, ensure your deployed contracts exist at that block number.
3. **Rate limiting** - When running many tests (especially fuzz tests), you may hit RPC rate limits. Consider lowering fuzz run counts.
4. **Storage layout constraints** - Solidity `mapping`s compute storage slots that are not reversible, which required special handling in the emulation layer.
5. **Foundry `ffi` requirement** - The Foundry library requires `ffi = true` which allows executing external commands. This is necessary for `curl` calls to the Mirror Node.
6. **Hardhat async limitations** - The Hardhat plugin requires manual configuration of `chainId` and `workerPort` because Hardhat plugin loading is synchronous.
***
## Development Framework Support
### Foundry
The Foundry library uses `ffi` (Foreign Function Interface) to fetch remote state from the Mirror Node using `curl` (or PowerShell on Windows).
**Key setup:**
* Enable `ffi = true` in `foundry.toml`
* Call `Hsc.htsSetup()` in your test setup
**How it fetches data:**
```solidity theme={null}
import {Hsc} from "hedera-forking/Hsc.sol";
function setUp() public {
Hsc.htsSetup(); // Deploys HtsSystemContractJson at 0x167
}
```
When you access token state, the library:
1. Intercepts the storage slot access
2. Uses `MirrorNodeFFI` to call `curl` via `ffi`
3. Parses the JSON response
4. Writes data using `vm.store` cheatcode
### Hardhat
The Hardhat plugin intercepts JSON-RPC calls (`eth_getCode` and `eth_getStorageAt`) to provide HTS emulation.
**Key setup:**
* Install `@hashgraph/system-contracts-forking`
* Import the plugin in `hardhat.config.ts`
* Configure `chainId` and `workerPort` in forking config
**Configuration example:**
```typescript theme={null}
import "@hashgraph/system-contracts-forking/plugin";
// In your hardhat.config.ts
hardhat: {
forking: {
url: "https://mainnet.hashio.io/api",
blockNumber: 70531900,
// @ts-ignore - custom properties for hedera-forking plugin
chainId: 295, // Required: 295 (mainnet), 296 (testnet), 297 (previewnet)
// @ts-ignore
workerPort: 1235 // Required: Any free port
}
}
```
***
## Learn How to Fork the Hedera Network for Local Testing
***
## Further Resources
* [hedera-forking GitHub Repository](https://github.com/hashgraph/hedera-forking)
* [Internals Documentation](https://github.com/hashgraph/hedera-forking/blob/main/INTERNALS.md)
* [FAQ](https://github.com/hashgraph/hedera-forking/blob/main/FAQ.md)
* [HIP-719: Token Proxy Contract](https://hips.hedera.com/hip/hip-719)
* [Foundry Fork Testing Documentation](https://book.getfoundry.sh/forge/fork-testing)
* [Hardhat Mainnet Forking Documentation](https://hardhat.org/hardhat-network/docs/overview#mainnet-forking)
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# Gas and Fees
Source: https://docs.hedera.com/evm/development/gas-fees
Understanding gas costs, throttling, and fee calculation for Hiero Contracts
## Gas
When executing smart contracts, the **EVM** requires the amount of work paid in **gas**. The "work" includes computation, state transitions, and storage. Gas is the unit of measurement used to charge a fee per opcode executed by the EVM. Each opcode has a defined gas cost. Gas reflects the cost necessary to pay for the computational resources used to process transactions.
Following **[HIP-1249](https://hips.hedera.com/hip/hip-1249)**, Hedera has implemented **operational-based throttling** and eliminated **minimum gas charges**, providing more predictable resource management and fairer billing for smart contract operations.
## Weibar
Gas information for EVM operations is returned in **weibar** (introduced in [HIP-410](https://hips.hedera.com/hip/hip-410)).
* `1 weibar = 10^-18 HBAR`
* `1 tinybar = 10^10 weibar`
As noted in [HIP-410](https://hips.hedera.com/hip/hip-410), this maximizes compatibility with third-party tools that expect ether units to be operated on in fractions of `10^18`, also known as a **Wei**.
## Gas Schedule and Fee Calculation
Gas charges apply to `ContractCall`, `ContractCreate`, and `EthereumTransaction`. Other smart contract-related transactions (e.g., `ContractDelete`, `ContractGetInfo`) use the standard [Fee Model](/learn/core-concepts/fee-model), a base fee plus extras for node, network, and service components, paid in HBAR.
For gas-consuming transactions (`ContractCall`, `ContractCreate`, `EthereumTransaction`), gas is an "extra" in the service fee component. The gas extra covers EVM execution costs. All other fee components (node fee, network fee, and the non-gas portion of the service fee) follow the base-fee-plus-extras model.
Gas fees for EVM transactions consist of:
* **Intrinsic Gas**: The minimum amount of gas required to execute a transaction
* **EVM Opcode Gas**: The gas required to execute the defined opcodes for the smart contract call
* **Hedera System Contract Gas**: The required gas associated with Hedera-defined transactions, such as using the Hedera Token Service system contract
**High-volume contract creation.** `ContractCreateTransaction` supports the
`high_volume` flag ([HIP-1313](https://hips.hedera.com/hip/hip-1313)), which routes
the transaction through dedicated high-volume throttle capacity with variable-rate
pricing. This applies to **HAPI-based contract creation only** — contract deployments
via EVM `CREATE` / `CREATE2` opcodes are not included. See the
[High-Volume Entity Creation](/learn/core-concepts/high-volume-entity-creation) guide
for details.
### Intrinsic Gas
A transaction submitted to the smart contract service must be sent with enough gas to cover **intrinsic gas**. With the **Cancun fork** of the EVM update, intrinsic gas is calculated as:
```bash theme={null}
21000 + 4 × (number of zero bytes) + 16 × (number of non-zero bytes) = intrinsic gas
```
* **21,000**: The base gas cost for any transaction
* **4 × (zero bytes)**: The cost of each zero byte in the transaction payload
* **16 × (non-zero bytes)**: The cost for each non-zero byte in the transaction payload
If insufficient gas is submitted, the transaction will **fail during precheck** and no record will be created.
This applies to both standard transactions and **jumbo EthereumTransactions** introduced by **[HIP-1086](https://hips.hedera.com/hip/hip-1086)**, which allow larger `callData` payloads.
### EVM Opcode Gas
Execution costs in the EVM include both **fixed** and **dynamic** costs:
* **Fixed Cost**: Base cost per opcode execution
* **Dynamic Cost**: Varies by parameters (e.g., cold vs warm storage access)
**Example**: For the `SLOAD` opcode, which loads data from storage:
* **Fixed Cost**: `100 gas` units (base cost per execution)
* **Dynamic Cost (Cold Access)**: `2,100 gas` units (first-time access to the storage slot)
* **Dynamic Cost (Warm Access)**: `100 gas` units (subsequent access within the transaction)
If `SLOAD` accesses a storage slot twice within the same transaction, the total gas cost would be calculated as follows:
* **First Access (Cold)** = `100 + 2,100 = 2,200 gas`
* **Second Access (Warm)** = `100 + 100 = 200 gas`
* **Final Gas Cost Total** = `2,400 gas`
📣 *Explore [opcodes in Cancun fork](https://www.evm.codes/).*
### Hedera System Contract Gas
Hedera system contract gas fees apply only when using a native Hedera service. They are calculated by converting the transaction cost in **USD** to gas using a set conversion rate, then adding a **20% surcharge** for overhead and variations in gas usage.
**Example**: For a **\$0.10 transaction** with a conversion rate of `1,000,000 gas per USD`:
* **Base Gas Cost** = `0.10 × 1,000,000 = 100,000 gas`
* **Total Gas Cost** = `100,000 × 1.2 = 120,000 gas`
* **Final gas cost total** = `120,000 gas`
Following **[HIP-1249](https://hips.hedera.com/hip/hip-1249)**, system contract operations also contribute to **operational throttling** through measured ops costs, providing layered resource protection alongside gas-based billing.
#### System Contract View Functions
The gas requirements for **HTS view functions** can be calculated in a slightly modified manner. The transaction type of `getTokenInfo` can be used and a nominal price need not be calculated. This implies that converting the fee into HBAR is not necessary as the canonical price (`$0.0001`) can be directly converted into gas by using the conversion factor of **852 tinycents**. Add **20% markup**. Thus gas cost is:
* **Base gas cost** = `(1000000 + 852000 - 1) × 1000 / 852000 = 2173 gas`
* **Total Gas Cost** = `2173 × 1.2 = 2607 gas`
**Final gas cost total** = `2607 gas`
**Example System Contracts:**
* **[Hedera Token Service (HTS)](https://github.com/hiero-ledger/hiero-contracts/blob/main/contracts/token-service/HederaTokenService.sol)**
* **[Pseudo Random Number Generator (PRNG)](https://github.com/hiero-ledger/hiero-contracts/blob/main/contracts/prng/PrngSystemContract.sol)**
* **[Exchange Rate](https://github.com/hiero-ledger/hiero-contracts/blob/main/contracts/exchange-rate/ExchangeRateSystemContract.sol)**
**Learn More**: Our detailed gas calculation [reference](https://github.com/hashgraph/hedera-services/blob/develop/hedera-node/docs/design/services/smart-contract-service/system-contract-gas-calc.md#system-contracts) explains the precise steps for calculating gas fees on Hedera.
## Gas for Jumbo Transactions
**Jumbo EthereumTransactions** that include large `callData` under **[HIP-1086](https://hips.hedera.com/hip/hip-1086)** follow the same gas model as standard EVM transactions. This gas pricing applies only to [EthereumTransaction](/native/smart-contracts/ethereum-transaction) type; standard HAPI transactions are unaffected.
### Formula
The gas cost for `callData` is based on byte content:
```
callData gas = (4 × zero bytes) + (16 × non-zero bytes)
```
This is added to the base gas and execution gas to calculate the total gas required.
*📣 [Learn more about Ethereum jumbo transactions](/native/smart-contracts/ethereum-transaction#handling-large-calldata-payloads)*
### Example Calculation
For **100KB of `callData`** with `10,000 zero bytes` and `90,000 non-zero bytes`:
* **Zero byte gas**: `4 × 10,000 = 40,000`
* **Non-zero byte gas**: `16 × 90,000 = 1,440,000`
* **Total callData gas** = `1,480,000`
Ensure both `gasLimit` (RLP) and `maxGasAllowance` (wrapper) are set high enough to cover the total.
🔹 **Size Caps**: Jumbo EthereumTransactions are capped at **24KB** (creation) and **128KB** (call). Larger payloads require `callDataFileId`.\
🔹 **Throttling**: Jumbo transactions are subject to dedicated **operational throttling** based on transaction type and complexity.
## Gas Limit
The **gas limit** is the maximum amount of gas you are willing to pay for an operation.
The current opcode gas fees are reflective as of the **[0.22 Hedera Service release](/networks/release-notes/services#v0.22)**.
| Operation | Cancun Cost (Gas) | Current Hedera (Gas) |
| ------------------------------------------------------------------------- | ---------------------------------------------- | ---------------------------------------------- |
| Code deposit | 200 \* bytes | 200 \* bytes |
|
| As specified by the EVM | As specified by the EVM |
|
CALLet al. (cold recipient)
| 2,600 | 2,600 |
|
CALLet al. (warm recipient)
| 100 | 100 |
|
CALLet al. HBAR/ETH Transfer Surcharge
| 9,000 | 9,000 |
|
SELFDESTRUCT (cold beneficiary)
| 2600 | 2600 |
|
SELFDESTRUCT (warm beneficiary)
| 0 | 0 |
| `TSTORE` | 100 | 100 |
| `TLOAD` | 100 | 100 |
| `MCOPY` | 3 + 3\*words\_copied + memory\_expansion\_cost | 3 + 3\*words\_copied + memory\_expansion\_cost |
The terms **'warm'** and **'cold'** in the above table correspond with whether the account or storage slot has been read or written to within the current smart contract transaction, even within a child call frame.
**'CALL et al.'** includes with limitation: `CALL`, `CALLCODE`, `DELEGATECALL`, and `STATICCALL`
Reference: [HIP-206](https://hips.hedera.com/hip/hip-206), [HIP-865](https://hips.hedera.com/hip/hip-865)
## Operational-Based Throttling
While most **EVM-compatible networks** use per-block gas limits for resource control, Hedera uses **time-based throttling**. Following **[HIP-1249](https://hips.hedera.com/hip/hip-1249)**, Hedera has transitioned from **gas-per-second** to **operations-per-second (ops/sec) throttling**, controlling network throughput based on actual computational demands rather than gas estimates. This potentially supports **significantly higher throughput** while maintaining EVM compatibility.
**Ops costs** are derived from **nanosecond performance benchmarks** with safety margins, covering **EVM opcodes**, **precompiles**, and **system contracts**. Gas continues for user billing and per-transaction limits, separating cost calculation from throttling.
**Performance**: Real-world testing shows substantial improvements, with **Uniswap** achieving over **150 million gas/sec** compared to the previous **15 million gas/sec** limit.
### Transaction Execution Outcomes
With **operational-based throttling**, transaction processing follows specific patterns based on resource availability:
**Ops Throttle Exhausted**: When the operations-per-second throttle is exhausted either before execution begins or during execution, transactions fail with a `THROTTLED_AT_CONSENSUS` error and are charged only the **[intrinsic gas fee](/evm/development/gas-fees#intrinsic-gas)**.
**Gas Limit Exhausted**: If a transaction's gas limit is exhausted before the ops throttle, it fails with an **out-of-gas error** and users are charged for the full gas used, with ops deducted for work completed.
**Successful Execution**: For successful transactions, users are charged for the **exact gas used** and the corresponding ops units are deducted from the throttle bucket.
## Gas Reservation and Unused Gas Refund
Hedera throttles transactions **before consensus**, and nodes limit the number of transactions they can submit to the network. At **consensus time**, if the maximum number of transactions is exceeded, the excess transactions are not evaluated and are canceled with a **busy state**. Throttling by variable gas amounts provides challenges to this system, where the nodes only submit a share of their transaction limit.
To address this, Hedera now uses **operational-based throttling** that applies only at **consensus**. The system operates with:
* **Frontend (ingest/precheck)**: Uses **TPS limits** only with no gas-based throttling
* **Backend (consensus)**: Applies **operations-per-second (ops) throttling** based on actual computational work performed
It is impossible to know the actual evaluated gas pre-consensus because the network state can directly impact the flow of the transaction, which is why pre-consensus uses the `gasLimit` field and will be referred to as the **gas reservation**.
**Contract query requests** are unique and bypass the consensus stage altogether. These requests are executed solely on the local node that receives them and only influence that specific node's precheck throttle.
To ensure transactions can execute properly, setting a **higher gas reservation** than will be used by execution is common. On **Ethereum mainnet**, the entire reservation is charged to the account before execution, and the unused portion is credited back. However, Ethereum utilizes a **[memory pool (mempool)](/support/glossary#mempool)** and does transaction ordering at block production time, allowing the block limit to be based only on used and not reserved gas.
Users are charged only for the **actual gas used** during transaction execution, with **unused gas being fully refunded**. This aligns with Ethereum's billing model and eliminates the previous minimum charge requirements.
## Maximum Gas Per Transaction
Each transaction on Hedera is capped by a **per-transaction gas limit**. If a transaction's `gasLimit` exceeds this cap, it is rejected during precheck with the `INDIVIDUAL_TX_GAS_LIMIT_EXCEEDED` error and does not proceed to consensus. This gas metering approach ensures efficient resource use, preventing excessive consumption while allowing flexibility for larger, more complex smart contracts.
Per-transaction gas limits remain unchanged (e.g., **15 million gas per transaction**), while network throughput is now managed through **operational-based throttling**. Refer to [HIP‑1249](https://hips.hedera.com/hip/hip-1249) for implementation details.
**Reference**: [HIP-185](https://hips.hedera.com/hip/hip-185)
# JSON-RPC Relay
Source: https://docs.hedera.com/evm/development/json-rpc/index
The [Hiero JSON-RPC Relay](https://github.com/hiero-ledger/hiero-json-rpc-relay) is an open-source project implementing the EVM JSON-RPC standard. It allows developers to interact with Hedera nodes using familiar EVM tools, allowing developers and users to deploy, query, and execute contracts as they usually would. Check out the interactive[ OpenRPC Specification](https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/hashgraph/hedera-json-rpc-relay/main/docs/openrpc.json\&uiSchema%5BappBar%5D%5Bui:splitView%5D=false\&uiSchema%5BappBar%5D%5Bui:input%5D=false\&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false) and a simple [list of endpoints](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rpc-api.md).
## HBAR decimal places
The Hiero JSON RPC Relay **`msg.value`** uses `18 decimals` when it returns HBAR. As a result, the **`gasPrice`** value returns 18 decimal places since it is only utilized from the JSON RPC Relay. Refer to the [HBAR page](/native/fundamentals/hbars) for a list of Hiero APIs and the decimal places they return.
## JSON RPC Relay Options for the Hedera Network
When interacting with smart contracts on Hedera, developers have several options for setting up a JSON RPC Relay. Each choice comes with unique advantages and trade-offs based on your project's needs, scalability, and operational preferences.
1. [**Hiero Local Node**](https://github.com/hiero-ledger/hiero-local-node)**:** It provides a built-in JSON RPC Relay and simulates the Hedera network environment locally. This option is ideal for quick prototyping, debugging, and isolated testing without external dependencies. It's ideal if you want to run extensive and repeated tests without having to worry about running out of testnet HBAR.
2. [**Self-hosted JSON RPC Relay**](https://github.com/hiero-ledger/hiero-json-rpc-relay/tree/main)**:** Running your own JSON RPC Relay offers complete control over configurations and network selection (testnet, previewnet, mainnet). It is best suited for projects requiring flexibility, high reliability, and scalability, especially in production environments.
3. [**Third-party JSON RPC Relay Services**](#community-hosted-json-rpc-relays)**:** Several third-party providers offer managed JSON RPC Relay services with different levels of reliability, service-level agreements (SLAs), and fee structures. These services remove infrastructure maintenance overhead, allowing teams to focus more on development (*you can find the list of supported services* [*below*](#community-hosted-json-rpc-relays)*).*
| Feature | Hiero Local Node | Self-hosted RPC Relay | Third-party RPC Relay |
| ----------------------------- | :-------------------: | :-------------------: | :------------------------------------------: |
| **Infrastructure Management** | Minimal | Required | None |
| **Reliability and Stability** | High (local) | High | Variable by SLA |
| **Scalability** | / (local) | Flexible | Variable by provider |
| **Setup Complexity** | Low to Medium | Medium to High | Low |
| **Ideal Use Case** | Testing & Development | Testing & Production | Builders who prefer convenience & Production |
Read the [**JSON RPC Relay Comparison blog**](https://hedera.com/blog/selecting-a-json-rpc-relay-for-your-project) post to learn more about the different options!
## Community Hosted JSON-RPC Relays
Anyone in the community can set up their own JSON RPC relay that applications can use to deploy, query, and execute smart contracts. The list of community-hosted Hiero JSON RPC relays and endpoints for previewnet, testnet, and mainnet can be found in the table below, as well as their associated docs or websites.
#### JSON-RPC Relay Endpoints
### 🚨 **PLEASE NOTE**
**Hashio** is for development and **testing purposes only**. Production use cases are strongly encouraged to use [commercial-grade JSON-RPC relays](#community-hosted-json-rpc-relays) or host their own instance of the [Hiero JSON-RPC Relay](https://github.com/hiero-ledger/hiero-json-rpc-relay).
### **Note**
If you want to add your hosted JSON-RPC relay to this list, please open an issue in the [Hedera docs GitHub repository](https://github.com/hashgraph/hedera-docs). Please visit the community-hosted websites to review any limitations specific to their instance.
## FAQ
* [**Hashio**](https://www.hashgraph.com/hashio/)
* [**Arkhia**](https://www.arkhia.io/features/#api-services)
* [**Validation Cloud**](https://docs.validationcloud.io/about/hedera/json-rpc-relay-api)
* [**QuickNode**](https://www.quicknode.com/docs/hedera)
* [**Hgraph**](https://docs.hgraph.com/category/json-rpc)
The configuration guide to connect to the Hedera Network over RPC can be found [here](/evm/tutorials/intermediate/json-rpc-connections).
The endpoints for previewnet, testnet, and mainnet can be found on [Hashio](https://www.hashgraph.com/hashio/), accessible through the [Hashgraph](https://www.hashgraph.com) website.
The JSON-RPC Relay `msg.value` uses 18 decimals when it returns HBAR. The `gasPrice` value also returns 18 decimal places. *Check out the* [*HBAR page*](/native/fundamentals/hbars) *for the full list of Hedera APIs and their decimal representation.*
To contribute or log errors, please refer to the [Contributing Guide](/support/contributing) and submit them as issues in the [GitHub repository](https://github.com/hiero-ledger/hiero-json-rpc-relay).
# Smart Contract Rent
Source: https://docs.hedera.com/evm/development/rent
🚨 **HEDERA COUNCIL HAS NOT ENABLED RENTS ON SMART CONTRACTS YET. RENTS PAY FOR THE ONGOING USAGE OF RESOURCES USED BY THE SMART CONTRACT. HEDERA INTENDS TO ENABLE THE RENTS IN THE FUTURE, AS DESCRIBED IN THIS SECTION. MORE DETAILS COMING SOON... 🚨**
Smart contract rent is a recurring payment mechanism designed to maintain resource allocation and is required for contracts to remain active on the network. For contracts, rent is comprised of two primary components:
**➡** [**Auto-Renewal**](#contract-auto-renewal)
**➡** [**Storage Payments**](#storage-payment)
***
## Contract Auto-Renewal
Auto-renewal is a feature that automatically renews the life of non-deleted smart contracts by a minimum of 90 days. Contract authors are encouraged to establish an auto-renew account specifically for this purpose.
The network will attempt to automatically charge the **renewal payment** to the expired contract's auto-renew account. The network will attempt to charge the contract if an auto-renew account has zero balance.
If the account lacks sufficient funds for renewal, the contract goes into a one-week grace period. During this time, the contract is inoperable unless funds are added, its expiry is extended (via `ContractUpdate`), or it receives HBAR. Failing to renew will result in the contract being purged from the state.
***
## Storage Payments
Contract storage payments on Hedera will activate once **100 million key-value pairs** are stored cumulatively across the network. The Hedera Coin Economics Committee is expected to set a rate of **\$0.02 per key-value pair per year**. This applies to all contracts on Hedera, regardless of the contract being created before or after the rent payments go live.
Once storage payments are enabled on Hedera, each contract has **100 free key-value pairs** of storage available. Then, once a contract exceeds the first 100 free key-value pairs, it must pay storage fees.
> *Storage fees will be part of the rent payment collected when a contract is auto-renewed. Valid renewal windows are between \~30 and \~92 days (see* [*HIP-372*](https://hips.hedera.com/hip/hip-372)*).*
If a high enough utilization threshold is reached, **congestion pricing applies.** In this case, prices charged will be inversely proportional to the remaining system capacity of the network (lower remaining capacity means higher pricing). This applies to all transactions.
***
## Smart Contract Rent - Frequently Asked Questions (FAQ)
Distributed networks like Hedera have a finite amount of computational resources. When entities like smart contracts are deployed on a decentralized network, a portion of those resources are consumed. Thus, it is unfeasible to maintain an unlimited number of entities for an infinite amount of time on finite resources. Solving this problem is necessary, and it’s a key topic of discussion by Leemon and [others](https://www.coindesk.com/markets/2018/03/27/vitalik-wants-you-to-pay-to-slow-ethereums-growth/) in the layer 1 network space.
Contract rent is an economically and technically viable approach to manage smart contract entities and state storage.
All other network entities (e.g., Tokens, accounts, topics, and files) will also pay rent. However, the timeline for the rent is not yet defined. Sufficient time and notice will be provided to the community before enabling rent for other entities.
Rent is defined as the recurring payment required for contracts (and, eventually, all other Hedera entities) to remain active on the network. For contracts, rent is comprised of **auto-renewal** and **storage** payments:
* **Auto-renewal payments** The auto-renewal fee for a contract is \$0.026 USD per 90 days.
* **Storage payments** will start once a total of **100 million key-value pairs** are stored cumulatively across the network. These storage fees will be part of the rent payment collected when a contract is auto-renewed. The storage fee rate is \$0.02 per key-value pair per year.
Every entity on Hedera has the fields `expirationTime`, `autorenewPeriod`, and `autorenewAccount`.
1. When the `expirationTime` for a contract is reached, the network will first try to charge rent to the contract’s `autoRenewAccount`
* If renewal is successful, then the contract remains active on the network
* If renewal fails, then the contract is marked as `expired`
2. An `expired` entity is given a grace period before it is removed from the network. During the grace period, the entity (contract) is inactive, and all transactions involving it will fail, except for an update transaction to extend the `expirationTime`
* A contract in the grace period can be immediately "re-activated" by either sending it some HBAR or manually extending its `expirationTime` via a contract update transaction
3. At the end of the grace period, the contract is permanently removed from the ledger if:
* The contract and its `autoRenewAccount` still have a zero HBAR balance at the end of the grace period, OR
* The contract is not manually extended during the grace period
Note that the ID number of a removed entity is not reused going forward. In addition, if an entity was marked as `deleted`, then it cannot have its `expirationTime` extended. Neither an update transaction nor an auto-renew will be able to extend it.
See the diagram below and [HIP-16](https://hips.hedera.com/hip/hip-16) for more details.
The grace period between entity expiration and deletion is 30 days.
Smart contracts on Hedera can pay for rent in two ways: external funds or contract funds.
When the `expirationTime` for a contract is reached, the network will first try to charge rent to the contract’s `autoRenewAccount`:
* If the `autoRenewAccount` has sufficient HBAR to pay for the `autoRenewPeriod`, then the contract is successfully renewed
* If the `autoRenewAccount` has some HBAR but not enough to afford the full `autoRenewPeriod`, then the contract is extended for as long as possible (say, 1 week instead of 90 days). Once that extension (1 week) elapses, if the `autoRenewAccount` hasn't been re-funded to cover the `autoRenewPeriod`, then the contract account itself will be charged for rent
* If the `autoRenewAccount` has a zero HBAR balance, then the contract itself is charged
* If the `autoRenewAccount` and the contract both have a zero HBAR balance at the time that renewal fees are due, the contract is marked as `expired`
Calling an `expired` contract will resolve to `CONTRACT_EXPIRED_AND_AWAITING_REMOVAL`.
If an expired contract that holds native Hedera Token Service (HTS) tokens reaches the deletion stage, then the assets held by that contract are returned to their respective treasury accounts.
If the deleted contract is being used as a specific key for an HTS token, then that key field will refer to a contract that no longer exists. That specific key can be changed, as long as an admin key was specified during token creation. If the token is immutable (no admin key), the specific key cannot be changed.
Contracts that are the treasury for HTS tokens do not expire at this moment (subject to change in the future).
The minimum renewal period possible is 2,592,000 seconds (\~30 days) and the maximum is 8,000,001 seconds (\~92 days).
See details in [HIP-372: Entity Auto-Renewals and Expiry Window](https://hips.hedera.com/hip/hip-372).
The cost of rent scales just about linearly with the length of the renewal period. So a renewal that pays for 90 days will cost \~3 times as much as a renewal that pays for 30 days.
Mirror nodes provide the expiration time for contracts. You can obtain this information using the mirror node REST API (show it as `expiration_time`) and network explorers like HashScan (shows it as `Expires at`).
According to [HIP-16: Entity Auto-Renewal](https://hips.hedera.com/hip/hip-16), records of auto-renew charges will appear as `actions` in the record stream, and will be available via mirror nodes. In addition, the fee breakdown is provided in network explorers like HashScan for the contract update transaction. No receipts or records for auto-renewal actions will be available via HAPI queries.
[HIP-449](https://hips.hedera.com/hip/hip-449) provides technical details on how information for expiring contracts is included in the record stream.
Yes, that is possible for contracts.
* Storage payments for contracts will only start being charged once **100 million key-value pairs** are reached cumulatively across the network
* After than, each contract has **100 free key-value pairs** of storage available. Once a contract exceeds the first 100 free key-value pairs, it must pay storage fees
Contracts created via `CREATE2` inside the EVM will inherit the `autorenewaccount` and `autorenewPeriod`of the `sender` address.
For example, if you call contract `0xab...cd` which has `autorenewAccount` `0.0.X` and `autorenewPeriod` of 45 days, and this contract deploys a new contract `0xcd...ef`, then the new contract will also have `autorenewAccount` `0.0.X`and `autorenewPeriod` of 45 days.
Also, remember that rent can be covered by the HBAR balance of a contract. Thus, developers can send HBAR to the contract or configure the contract to charge users a specific HBAR amount when executing operations.
# Smart Contract Security
Source: https://docs.hedera.com/evm/development/security
The [Hedera Smart Contract Service (HSCS)](/support/glossary#hedera-smart-contract-service-hscs) integrates the features of Hedera's third-generation native entity functionality—high throughput, fast finality, predictable and affordable fees, and fair transaction ordering—with a highly optimized and performant second-generation [Ethereum Virtual Machine (EVM)](/support/glossary#ethereum-virtual-machine-evm). We aim to offer comprehensive support for smart contracts originally written for other EVM-compatible chains and to enable their seamless deployment on Hedera.
***
## EVM Equivalence
We strive to ensure that developers can conveniently point to a Hedera-supported RPC endpoint and perform smart contract executions and queries using the same code and similar tools to achieve EVM equivalence. All smart contract transactions are executed using the [Besu EVM](/support/glossary#hyperledger-besu-evm) to realize this objective, and the resulting changes are stored in the Hedera-optimized [Virtual Merkle Tree](/support/glossary#virtual-merkle-tree) state. Users are thus guaranteed deterministic finality (as opposed to probabilistic finality) of smart contract executions within 2-3 seconds while ensuring that state changes are entirely encompassed within smart contract functionality.
🔔 A Comprehensive breakdown of Hedera's EVM equivalence goals and exceptions can be found [**here**](/evm/differences).
***
## Security Model
### Old model (v1) boundaries
The old security model (pre [0.35.2](https://github.com/hashgraph/hedera-services/releases/tag/v0.35.2)) supported account key signatures provided at transaction time for authorization. Some of the key characteristics of this model included:
* [Smart contracts](/support/glossary#smart-contract) could only change their own storage or the storage they were [delegate called](https://docs.soliditylang.org/en/v0.8.19/introduction-to-smart-contracts.html#delegatecall-and-libraries) with.
* System smart contracts could be delegate called to carry out [Hedera Token Service (HTS)](/support/glossary#hedera-token-service-hts) operations on behalf of another account - Externally Owned Account (EOA) or contract account.
* Smart Contracts could change an EOA’s storage with the appropriate signature in the transaction.
* Smart Contracts could change an EOA’s balance with the appropriate signature in the transaction or with prior addition to an allowance approval list.
This greatly improved user experience as contracts could combine transactions in an attempt at atomicity. For instance, a contract could associate, transfer and approve transactions on a user's behalf with one signature. While focusing on usability, this approach did not address cases in which bad actors could carry out an unsanctioned transaction on behalf of a user, e.g., [https://hedera.com/blog/analysis-remediation-of-the-precompile-attack-on-the-hedera-network](https://hedera.com/blog/analysis-remediation-of-the-precompile-attack-on-the-hedera-network)
To address this, the core Hedera engineers thoroughly analyzed the Smart Contract Service and the HTS system contracts, aiming to secure the state and token assets of users and the network during Smart Contract executions. The results of this effort are the guidelines in [Consensus Node release v0.35.2](https://github.com/hashgraph/hedera-services/releases/tag/v0.35.2).
### New model (v2) boundaries
In the new security model, account key signatures cannot provide authorization for contract actions. Its key characteristics include:
* Smart contracts can only change their own storage or the storage they were [delegate called](https://docs.soliditylang.org/en/v0.8.19/introduction-to-smart-contracts.html#delegatecall-and-libraries) with.
* System smart contracts may **not** be delegate called, except from the Token proxy/facade flow, e.g., [HIP 719](https://hips.hedera.com/hip/hip-719). In such cases, HTS tokens are represented as smart contracts (see [HIP 218](https://hips.hedera.com/hip/hip-218)) for common ERC methods.
* Smart contracts can change an EOAs storage only if the contract ID is contained in the EOAs key.
* Smart contracts can change an EOAs balance if approved for a token allowance for a specific token held by the EOA.
#### Boundary comparison table
Boundary Spec
v1 Model
v2 Model
Change
Storage Changes
Smart Contracts could only change their own storage or the storage they were delegate called with
Smart contracts can only change their own storage or the storage they were delegate called with
N
System Contract Call Types
System smart contracts could be delegate called in order to carry out Hedera Token Service operations on behalf of another account (EOA) or contract.
System smart contracts may not be delegate called, except from the Token facade flow, which presents HTS tokens as smart contracts for common ERC methods.
Y
Permissioned Account Storage Changes
Smart Contracts could change an EOA’s storage with the appropriate signature in the transaction.
Smart contracts can change an EOAs storage if the contract ID is contained in the EOAs key.
Y
Permissioned Account Balance Changes
Smart Contracts could change an accounts (EOA or contract) balance with the appropriate signature in the transaction or with prior addition to an allowance approval list
Smart contracts can change an EOAs balance if they have been approved a token allowance.
Y
In summary, HSCS utilizes a three-level security approach:
1. **Level 0 - EVM Security Model:** Entities may only modify their own state and balance.
2. **Level 1 - ERC Account Value Security Models:** Transfer and access to account value will follow tested web3 interface standards, e.g., ERC20, ERC721.
3. **Level 2 - Hedera Advanced Security Features:** Unique Hedera features may utilize contract-compatible permissions, e.g., ContractID keys.
To achieve state change or value transfer, executions must adhere to the rules of each level. Transactions that don’t satisfy the appropriate authorization will fail with response codes such as `INVALID_FULL_PREFIX_SIGNATURE_FOR_PRECOMPILE` when a sender is not authorized to carry out an operation. More operational-specific response codes will be returned where applicable e.g. `SPENDER_DOES_NOT_HAVE_ALLOWANCE`.
***
## Impact on Developers
### As a developer on Hedera, what should I do?
Developers are strongly encouraged to test their applications with new contracts and UX using the new security model to avoid unintended consequences.
* The new security model has been applied to contracts created from the mainnet [0.35.2 release](/networks/release-notes/services#0.35.2-hedera-smart-contract-service-security-model-changes) and onwards.
* Existing contracts deployed before this upgrade will continue to use the previous security model for a limited time to allow for application/UX modifications.
* The previous security model will only be maintained for approximately three months. The current target is for the network to remove the previous security model and for all contracts to follow the new model by the mainnet release of July 2023.
* See a comprehensive list of the security updates made [here](#0.35.2).
### What does the change in the security model mean for smart contract developers?
The security update involves changes to entity permissions during contract executions when modifying the state. In short, system contract calls (smart contract calls to the Hedera Token Service) are no longer executed with all upper caller privileges, even if the authorized user provides a signature.
Understanding the process of contract executions for both externally owned accounts (EOAs) and contracts during regular and delegate calls is crucial. This process involves tracking how accounts, state (storage and value balance), and code may change as you progress through the chain of calls.
#### Before (v1 model)
In a regular call scenario, when a call is made to contract B, B’s code is executed in the context of its own state. This allows B to modify only its own state. The sender value also differs between the calls to highlight that the EOA made the first call and contract A made the second.
#### After (v2 model)
On the other hand, in a delegate call scenario, the call to contract B sees B’s code executed in the context of A’s state. This allows B to modify A’s state. The sender and recipient values are preserved from the first call as if the EOA initiated the call.
In summary, a delegate call executes the calling contract's code in the context of the previous account, giving the code access to the previous account's state and blurring the lines of authorized state management.
Applying this to the security model changes, the following table summarizes the authorization check changes.
Scenario
Authorization check
Old Model
New Model
Smart contract A can change its own state using a call
sender = Contract A
Y
Y
Smart contract A can change EOA’s state via call
sender = EOA
N
N
Smart contract B can change contract A’s state via call
sender = A
N
N
Smart contract A can change EOA’s state via delegate call
sender = EOA
Y
Y
Smart contract B can change contract A’s state via delegate call
sender = Contract A
Y
Y
System smart contracts can change another accounts (EOA or contract A or contract B) state via call
sender = account
N
N
System smart contract can change another accounts EOA or contract A or contract B) state via delegate call
sender = account
N
N
System contracts can change an accounts (EOA or contact A or contract B) state via call with the appropriate signature
signature map contains signature of accounts (EOA or contact A or contract B respectively)
Y
N
System smart contract can change another accounts (EOA or contact A or contract B) state via delegate call with the appropriate signature
signature map contains signature of accounts (EOA or contact A or contract B)
Y
N
Contract A or B can call a system contract via a call
-
Y
Y
Contract A or B can call a system contract via a delegate call
-
Y
N
At the time of the change, the [HTS system contract](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/token-service) was the only pathway to expose Hedera API functionality through Smart Contracts. As such, it’s fair to consider the differences between pre and post-security model updates when observing HTS system contract state-changing functions.
#### Existing HTS system contract impacts summary
IHederaTokenService System Smart Contract Function
Token admin must set desired contract in admin key
wipeTokenAccount, wipeTokenAccountNFT
signature map contains token wipe key signature
or
Contract Id satisfies Token.wipeKey requirements
Contract Id satisfies Token.wipeKey requirements
Y
Token admin must set desired contract in Wipe key
unfreezeToken
signature map contains token freeze key signature
or
Contract Id satisfies Token.freezeKey requirements
Contract Id satisfies Token.freezeKey requirements
Y
Token admin must set desired contract in freeze key
unpauseToken
signature map contains token pause key signature
or
Contract Id satisfies Token.pauseKey requirements
Contract Id satisfies Token.pauseKey requirements
Y
Token admin must set desired contract in pause key
**Note:** While the changes impact user experience, requiring more explicit steps, they more than proportionately increase user and network security across the board. The team continues to push diligently to provide the community with secure and scalable API solutions to enable them to build creative dApps and carve out their own shared world on the ledger.
***
## Security Upgrades
* After the security incident on March 9th, the engineers conducted a thorough analysis of the Smart Contract Service and the Hedera Token Service system contracts.
* As part of this exercise, we did not find any additional vulnerabilities that could result in an attack that that which we witnessed on March 9th.
* The team also looked for any disparities between the expectations of a typical smart contract developer who is used to working with the Ethereum Virtual Machine (EVM) or ERC token APIs and the behaviors of the Hedera Token Service system contract APIs. Such differences in behavior could be used by a malicious smart contract developer in unexpected ways.
* In order to eliminate the possibility of these behavioral differences being utilized as attack vectors in the future, the consensus node software will align the behaviors of the Hedera Smart Contract Service token system contracts with those of EVM and typical token APIs such as ERC 20 and ERC 721.
* As a result, the following changes are made as of the mainnet 0.35.2 release on March 31st:
* An EOA (externally owned account) will have to provide explicit approval/allowance to a contract if they want the contract to transfer value from their account balance.
* The behavior of `transferFrom` system contract will be exactly the same as that of the ERC 20 and ERC 721 spec `transferFrom` function.
* For HTS specific token functionality (e.g. Pause, Freeze, or Grant KYC), a contract will be authorized to perform the associated token management function only if the ContractId is listed as a key on the token (i.e. Pause Key, Freeze Key, KYC Key respectively).
* The `transferToken` and `transferNFT` APIs will behave as `transfer` in ERC20/721 if the caller owns the value being transferred, otherwise it will rely on approve spender allowances from the token owner.
* The above model will dictate entity (EOA and contracts) permissions during contract executions when modifying state. Contracts will no longer rely on Hedera transaction signature presence, but will instead be in accordance with EVM, ERC and ContractId key models noted.
* As part of this release, the network will include logic to grandfather in previous contracts.
* Any contracts created from this release onwards will utilize the stricter security model and as such will not have considerations for top-level signatures on transactions to provide permissions.
* Existing contracts deployed prior to this upgrade will be automatically grandfathered in and continue to use the old model that was in place prior to this release for a limited time to allow for DApp/UX modification to work with the new security model.
* The grandfather logic will be maintained for an approximate period of 3 months from this release. In a future release in July 2023, the network will remove the grandfather logic, and all contracts will follow the new security model.
* Developers are encouraged to test their DApps with new contracts and UX using the new security model to avoid unintended consequences. If any DApp developers fail to modify their applications or upgrade their contracts (as applicable) to adhere to the new security model, they may experience issues in their applications.
# Smart Contract Traceability
Source: https://docs.hedera.com/evm/development/traceability
After contracts have been deployed, you may want to further investigate the execution of a smart contract function call. Traces provide a comprehensive view of the sequence of operations and their effects, allowing for analysis, debugging, and auditing of smart contract behavior. The two types of useful traces:
**➡** [**Call Trace**](#call-trace)
**➡** [**State Trace**](#state-trace)
***
## Call Trace
Contract **call trace** information captures the input, output, and gas details of all the nested smart contracts functions executed in a transaction. On Ethereum, these are occasionally called inner transactions but they simply capture snapshots of the message frame consideration the EVM encounters when processing a smart contract execution at each depth for all involved functions.
Input Data
It records the input data or parameters provided when calling a particular function within a smart contract. This input data is essentially the encoded form of the function signature and its arguments.
Output Data
After executing the function, the trace information includes the output data returned by that function. This can be the result of the function's computation or any data it generates as part of its execution.
Gas Details
Logs information about the gas consumed by each function call. Each operation within a function consumes a certain amount of gas, and this information is tracked to calculate the overall transaction cost.
This information can be queried using the transaction ID or Ethereum transaction hash.
ℹ️ Detailed information for call trace can be found in the Hedera [protobuf](https://github.com/hashgraph/hedera-protobufs/blob/main/streams/contract_action.proto) and includes:
Call Trace Data
Description
Call Operation Type
Specific type of operation performed during the execution of a smart contract or a transaction in the EVM. Example: “CALL” is an operation type use when a transaction invokes a function within a smart contract. It executes the function and can potentially modify the state of the contract.
The result data is the output or return values generated by the execution of a smart contract function or action. When a function call is executed, it may produce data as a result, such as computed values, status indicators, contract revert reason if any and the error if the transaction itself failed without an explicit
REVERT
Result Data Type
The "result data type" refers to the data type of the value returned by the function or method. For example, if you have a function add(a, b) that adds two numbers and returns the result, the result data type might be an integer if it returns the sum of the numbers.
Call Depth
The level or depth of the current function call within the call stack. It provides information about the nested nature of function calls and helps track the sequence and hierarchy of function invocations during the execution of a smart contract.
The caller depth indicates how many functions have been called before the current function in the call stack. It starts at 0 for the initial function invocation and increments by 1 for each subsequent function call.
For example, the parent transaction would be represented as call depth 1 and first child would be at call depth 1.1 and child transaction 2 would be at call depth 1.2. Child transaction at depth 1.2 has two parents.
Caller
The caller can be the ID of the account calling the contract or the ID of another smart contract calling the contract.
The first action in the tree can only come from an account. The rest of the actions in the call tree come from the contract.
When a smart contract function is invoked, either by an external account or by another contract, the caller address is recorded in the trace to identify the source of the function call. The caller address can be useful in understanding the context of the execution and determining the origin of the transaction or message that triggered the function call.
Recipient
The address of the smart contract or account that receives a specific call or transaction. It represents the destination or target of the interaction within the EVM. The contract action can be directed to one of the following:
• Account: The account ID of the recipient if the recipient is an account. Only HBARs will be transferred. • Contract: The contract ID if the recipient is a smart contract • EVM address : If the contract action was directed to an invalid solidity address, what that address was
From
The from Hedera contract calling the next contract.
To
The contract receiving the call or being created.
Value/Amount
The amount of hbars transferred within this call.
Gas Limit
The gas is defined as the upper limit gas this contract call can spend.
Gas Used
The amount of gas that was used for the contract call.
Input
Bytes passed as an input data to this contract call
**Example**:
***
## State Trace
Smart Contract state changes will now be tracked whenever a smart contract transaction modifies the state of the contract. This will enable developers to have a paper trail of the state changes that occurred for a contract from the time the contract was created. The state changes that will be tracked include each time a value is read or written to the smart contract. The storage slot represents the order in which the smart contract state is read or written.
The value read reflects the storage value prior to the execution of the smart contract transaction. The value written, if present, represents the final updated value of the storage slot after the completion of the smart contract call. Transient states between the start and finish of the contract are not stored in the transaction record.
ℹ️ Detailed information on state trace can be found in the [protobuf](https://github.com/hashgraph/hedera-protobufs/blob/main/streams/contract_state_change.proto) and includes:
State Trace Data
Description
Address
The smart contract EVM address.
Ex: 0000000000000000000000000000000000001f41
Contract ID
The smart contract ID.
Ex: 0.0.1234
Slot
Refers to a storage location where data is stored within the contract's state. It can also be thought of as a variable or a storage unit that holds a specific value.
Value Read
The current values of variables or data structures before making modifications. These values can be used to validate conditions, perform calculations, or trigger specific actions within the contract's code.
Value Written
The written or changed variables or data structures after the modification.
### Consensus Node
Consensus nodes store sidecar records called `ContractStateChanges`. Each time a smart contract state changes, a new record will be produced that commemorates the state changes for the contract that took place.
### Mirror Node
The Hedera [mirror node](/support/glossary#mirror-nodes) supports two rest APIs that return information about the smart contract’s state changes. This includes:
* `/api/v1/contracts/{id}/results/{timestamp}`
* `/api/v1/contracts/results/{transactionIdOrHash}`
**Example:**
```
"state_changes": [
{
"address": "0000000000000000000000000000000000001f41",
"contract_id": "0.1.2",
"slot": "0x00000000000000000000000000000000000000000000000000000000000000fa",
"value_read": "0x97c1fc0a6ed5551bc831571325e9bdb365d06803100dc20648640ba24ce69750",
"value_written": "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925"
}
]
```
### Hedera Mirror Node Explorer
State trace can be viewed on a supported Hedera Network Explorer.
# Troubleshooting
Source: https://docs.hedera.com/evm/development/troubleshooting
Diagnose and fix common issues when developing EVM smart contracts on Hedera.
Most failures on Hedera fall into one of a few buckets: gas problems, reverts you can't decode, HBAR transfers that don't trigger your contract, decimal mismatches between SDK and EVM, or RPC issues that look like contract bugs but aren't. The sections below cover the patterns that account for most of them.
## Differences from Ethereum that bite
Hedera is EVM-compatible, but a few things behave differently than Ethereum, and they account for most "this should work" tickets.
### Decimal handling: 8 vs 18
The native Hedera ledger uses 8 decimals for HBAR (1 ℏ = 10⁸ tinybars). The JSON-RPC relay scales values up to 18 decimals so they match Ethereum's `wei` convention. Inside an EVM contract, `msg.value`, `balance`, and `gasPrice` all use 18 decimals; the relay handles the conversion. The trap is when you mix native SDK calls and EVM contract calls in the same flow. You have to do the conversion yourself there, and the off-by-`10**10` bug is easy to write.
```solidity theme={null}
// Inside a contract, msg.value is in 18-decimal wei (as on Ethereum).
function deposit() external payable {
require(msg.value >= 1 ether, "send at least 1 HBAR");
}
```
### HBAR transfers don't always trigger `receive()`
On Ethereum, sending ETH to a contract address triggers `receive()` or `fallback()`. On Hedera, a native HAPI `CryptoTransfer` (from an SDK or wallet operating at the Hedera level rather than the EVM level) credits the contract's underlying Hedera account directly. No EVM frame opens, so `receive()` doesn't run. EVM-native paths still behave normally: `call{value: ...}`, `transfer`, and internal CALL frames all invoke `receive()` / `fallback()` as expected. If you want contract logic to execute on the HAPI path, route the deposit through a `payable` call instead:
```solidity theme={null}
// This runs the contract's payable function and fires events.
(bool ok, ) = contractAddr.call{value: 1 ether}(
abi.encodeWithSignature("deposit()")
);
require(ok, "deposit failed");
```
The full pattern is on the [Creating Smart Contracts](/evm/development/creating) page.
### ECDSA vs ED25519 keys
Hedera supports both ECDSA (secp256k1) and ED25519. The EVM toolchain only handles ECDSA. Accounts created through MetaMask or via the JSON-RPC relay get ECDSA by default; accounts created through the native SDK can get either. ED25519 accounts can still hold HBAR and HTS tokens, but they can't sign EVM transactions. If you plan to interact through the EVM, pick ECDSA at account creation.
### Gas refund behavior
Per [HIP-1249](https://hips.hedera.com/#hip-1249) (mirror node [v0.140.0 release notes](/networks/release-notes/mirror-node#v0-140-0)), unused gas is refunded in full, and only the gas you actually consumed is charged. The per-transaction limit remains 15M ([HIP-185](https://hips.hedera.com/#hip-185)). Hedera previously capped gas refunds at 20% of the limit, so setting a generous gas limit could quietly cost you. If you find guidance referencing the 20% cap, it's pre-HIP-1249 and no longer applies. See [Gas and Fees](/evm/development/gas-fees) for the full pricing model, intrinsic gas costs, and how to estimate fees.
### Historical queries have a retention window
The relay supports `eth_call` and `eth_getStorageAt` at historical blocks, but only as far back as the mirror node behind it has data. Query a block older than that and the relay returns an error. For deep history, retry against a provider with longer retention (Arkhia, Validation Cloud, Hgraph, QuickNode, thirdweb).
## Where to ask for help
`#developer-general` and `#smart-contracts`. Live answers from engineers and the community.
JSON-RPC relay bugs and feature requests.
Look up your transaction by hash to see the consensus-level result.
Before filing an issue, look the transaction up on the mirror node. The SDK transaction ID format is `0.0.1234@1700000000.123456789`, but the REST API needs it in URL-safe form: `0.0.1234-1700000000-123456789`. So the full URL is `https://testnet.mirrornode.hedera.com/api/v1/transactions/0.0.1234-1700000000-123456789`. The mirror node tells you the consensus-level result, which is usually more specific than what the relay returns.
# Verifying Smart Contracts
Source: https://docs.hedera.com/evm/development/verifying
Smart contract verification is the process of verifying that the smart contract bytecode uploaded to the network matches the expected smart contract source files. Verification is *not* required for contracts deployed on the Hedera network, but it is best practice and essential to maintaining the contract's security and integrity by identifying vulnerabilities that could be exploited, as smart contracts are immutable once deployed. It also enables transparency and builds trust within the user community by proving that the deployed bytecode matches the contract's original source code.
Hedera Mainnet and Testnet are natively supported by [Sourcify](/support/glossary#sourcify), the open-source Solidity source code and metadata verification service hosted at [sourcify.dev](https://sourcify.dev). To verify a contract, submit your source files and metadata to Sourcify (directly via the web UI, the [v2 API](https://docs.sourcify.dev/docs/api/), or through your build tooling). Sourcify recompiles the submitted sources and compares them to the deployed bytecode. If a match is found, the contract's verification status is updated to either a [*Full (Perfect) Match*](https://docs.sourcify.dev/docs/full-vs-partial-match/#full-perfect-matches) or a [*Partial Match*](https://docs.sourcify.dev/docs/full-vs-partial-match/#partial-matches)*.*
Once a contract is verified on Sourcify, [HashScan](https://hashscan.io/) and other community-hosted Hedera Mirror Node Explorers read its verification status directly from Sourcify and surface it to users. To learn what differentiates a *Full (Perfect) Match* from a *Partial Match*, check out the Sourcify documentation [here](https://docs.sourcify.dev/docs/full-vs-partial-match/).
**Note**: Manual HashScan verification is temporarily disabled. Verify your contracts directly at [sourcify.dev](https://sourcify.dev) or through Foundry/Hardhat. Once verified there, the status will appear on HashScan automatically.
For verification, you will need the following items:
**➡** [**Smart Contract Source Code**](#smart-contract-source-code)
**➡** [**The Metadata File**](#the-metadata-file)
**➡** [**Deployed Smart Contract Address**](#deployed-smart-contract-address)
***
## Smart Contract Source Code
This is the actual code for your smart contract written in Solidity. The source code includes all the contract's functions, variables, and logic. It's crucial for the verification process, where the deployed bytecode is compared to the compiled bytecode of this source code.
#### Example:
A simple `HelloWorld` Solidity smart contract:
```solidity theme={null}
pragma solidity ^0.8.17;
contract HelloWorld {
// the contract's owner, set in the constructor
address owner;
// the message we're storing, set in the constructor
string message;
constructor(string memory message_) {
// set the owner of the contract for 'kill()'
owner = msg.sender;
message = message_;
}
function set_message(string memory message_) public {
// only allow the owner to update the message
if (msg.sender != owner) return;
message = message_;
}
// return a string
function get_message() public view returns (string memory) {
return message;
}
}
```
***
## The Metadata File
When you compile a Solidity smart contract, it generates a JSON metadata file. This file contains settings used when the smart contract was originally compiled. These settings can include the compiler version, optimization details, and more. The metadata file is crucial for ensuring that the bytecode generated during verification matches the deployed bytecode.
> *Metadata is not part of the EVM spec because it's handled externally by compilers and tools like Sourcify. See Sourcify's Metadata documentation* [*here*](https://docs.sourcify.dev/docs/metadata/#metadata)*.*
You have options for generating the metadata file. The recommended skill levels for each option are in parentheses. Choose the option that best fits your experience with smart contracts:
To create a metadata file in Remix, compile your smart contract and the compiled artifacts will be saved in the `artifacts/` directory and the `.json` metadata file will be under `artifacts/build-info` and used for verification. Alternatively, you can copy and paste it from the Solidity compiler tab. Please see the image below.
See the Remix IDE docs for more detailed documentation [here](https://remix-ide.readthedocs.io/en/latest/contract_metadata.html).
**Note:** Taking the bytecode and metadata from Remix and then deploying that on Hedera results in a ***full (perfect) match***. Taking the bytecode and metadata from Remix *after* deploying the contract on Hedera results in a ***partial match*** or ***The deployed and recompiled bytecode don't match*** error. *The requirement for verification with a contract compiled in Remix is just the smart contract's Solidity file.*
To create the `.json` metadata file with Hardhat, compile the contract using the `npx hardhat compile` command. The compiled artifacts will be saved in the `artifacts/` directory and the `.json` metadata file will be under `artifacts/build-info` and used for verification. See Sourcify Hardhat metadata documentation [here](https://docs.sourcify.dev/docs/metadata/#hardhat).
**Note**: The requirement for verification with a contract compiled with Hardhat is only the `build-info` JSON file.
To create the metadata file with Foundry, compile the contract using the `forge build` command. The compilation outputs to `out/CONTRACT_NAME` folder. The `.json` file contains the metadata of the contract under `"rawMetadata"` and `"metadata"` fields. However, you don't need to extract the metadata manually for verification. See Sourcify Foundry metadata documentation [here](https://docs.sourcify.dev/docs/metadata/#foundry).
**Note**: The requirements for verification with a contract compiled with Foundry are both the `.json` metadata and the Solidity source file.
You can pass the `--metadata` flag to the Solidity command line compiler to get the metadata output printed.
```
solc --metadata contracts/HelloWorld.sol
```
Write the metadata into a file with
```
solc --metadata contracts/HelloWorld.sol > metadata.json
```
**Note:`solc` vs. `solcjs`**
**📣** `solcjs` will not generate the metadata using the `--metadata` flag. The option is only supported in `solc`.
An example metadata file for the `HelloWorld` smart contract:
```json theme={null}
{
"compiler": "0.8.17",
"language": "Solidity",
"abi": [
{
"inputs": [
{
"internalType": "string",
"name": "message_",
"type": "string"
}
],
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"inputs": [],
"name": "get_message",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "string",
"name": "message_",
"type": "string"
}
],
"name": "set_message",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]
}
```
***
## Deployed Smart Contract Address
Even though Hedera uses the `0.0.XXXXXXX` account ID format, it accommodates Ethereum's address format for EVM compatibility. Once your smart contract is deployed on Hedera's network, you'll receive an address like the one below. This serves as your deployed smart contract address.
#### Example:
An example deployed EVM smart contract address:
```
0x403925982ef5a6461daba0a103bd6be20b9c4216
```
***Note**: The `0.0.XXXXXXX` smart contract address format can not be used in the verification process.*
***
## Verify Your Smart Contract
Verify your contract directly at [sourcify.dev](https://sourcify.dev), via the [Sourcify v2 API](/reference/verification-api), or through Foundry / Hardhat tooling:
***
## Additional Resources
**➡** [**Sourcify Docs**](https://docs.sourcify.dev/docs/intro)
**➡** [**Smart Contract Verification API**](/reference/verification-api)
**➡** [**HashScan Network Explorer**](https://hashscan.io/)
**➡** [**Sourcify Verification UI**](https://sourcify.dev)
**➡** [**Full vs Partial Match Docs**](https://docs.sourcify.dev/docs/full-vs-partial-match/)
**➡** [**Hardhat Documentation**](https://hardhat.org/hardhat-runner/docs/guides/compile-contracts)
**➡** [**Solidity Documentation**](https://docs.soliditylang.org/en/v0.8.23/)
# Accounts, Signature Verification & Keys (ECDSA vs. ED25519)
Source: https://docs.hedera.com/evm/differences/accounts-and-keys
Compare Hedera and Ethereum account models, ECDSA vs ED25519 keys, alias handling, and ECRECOVER/isAuthorized signature verification on the EVM.
## Overview
Migrating to Hedera’s EVM implementation involves understanding key differences in account models, signature verification, and key types. On Ethereum, addresses are derived from ECDSA public keys, and `ECRECOVER` is commonly used to validate signatures. Hedera, however, supports both ECDSA and ED25519 (Hedera-native account) keys, with dynamic key rotation and aliases that may not directly align with Ethereum’s static address model.
This section helps you navigate ECDSA and ED25519 signature workflows, introduces the `isAuthorized` function from the system contract from [HIP-632](https://hips.hedera.com/hip/hip-632), and clarify how different account key scenarios map onto Hedera’s environment.
## Understanding Account Models and Aliases
Hedera’s account model supports both ED25519 and ECDSA keys by identifying accounts by aliases instead of static addresses. This allows dynamic key rotation without changing an account’s ID, unlike EVM's static ECDSA-only approach. Signature validation varies accordingly: ED25519 keys use `isAuthorized` or `isAuthorizedRaw`, while Hedera's ECDSA accounts with aliases can still rely on `ECRECOVER`.
### **Clarifying Account ID vs. EVM Address**
Hedera accounts have a native **Account ID** (e.g., `0.0.xxxx`) and can also have an **EVM Address from Public Key** (a 20-byte address like Ethereum's, derived from the ECDSA public key). The EVM Address from Public Key makes the account compatible with `ECRECOVER` and other familiar EVM tools. For more details, see the [Smart Contract Addresses](/evm/development/addresses) page.
For a full explanation of how Hedera accounts work in EVM context — including hollow accounts (auto-created with no signing key) and accounts without an ECDSA key — see [Account Model for EVM Developers](/evm/development/accounts).
### **Working With ECDSA Accounts in Testing**
If you are writing an Ethereum smart contract and need to interact with an ECDSA Hedera account identified by its **Account ID**, reference it by its **EVM Address** so that functions like `ECRECOVER` resolve the correct account, as long as the EVM Address is correctly set from the account's public key. Here are some considerations:
* During testing, use the **EVM Address** returned by the account object, not the Account ID (e.g., `0.0.xxxx`). If the account was created with `setECDSAKeyWithAlias()`, that value is the **EVM Address from Public Key** (the 20-byte `Keccak-256(publicKey)` form) and works with EVM tools like Hardhat and Truffle and with `ECRECOVER`. If no alias was set at creation, the account falls back to the **EVM Address from Account ID** (the long-zero form), which is **not** compatible with `ECRECOVER`. Confirm which form your account uses before deploying.
* Including this conversion step in your test setup saves time and confusion, ensuring your EVM-compatible smart contracts can reliably work with Hedera’s ECDSA accounts.
### **Key Permutations & Scenarios on Hedera**
* **ECDSA Accounts:**
* Behave similarly to standard EVM accounts.
* Allow validation of ECDSA signatures with `ECRECOVER`.
* Provide smooth interoperability with EVM tools and dApps.
* **ED25519 Accounts:**
* Require `isAuthorized` or `isAuthorizedRaw` for signature validation.
* Support complex configurations like multi-key or threshold-based approval.
* Enhance security and adaptability, but differ from EVM's static address.
This flexibility enables interoperability and more robust security models than standard EVM environments.
***
## Using ECRECOVER for ECDSA Accounts on Hedera
Hedera supports ECDSA accounts, allowing EVM developers to validate ECDSA signatures using familiar tools like `ECRECOVER`. ECDSA accounts on Hedera use aliases derived from `Keccak-256(publicKey)`, ensuring compatibility with Ethereum’s signature workflows.
**Example: Verifying ECDSA Signatures Using ECRECOVER**
```solidity highlight={2} theme={null}
function verifyECDSASignature(bytes32 messageHash, uint8 v, bytes32 r, bytes32 s) public pure returns (address) {
return ecrecover(messageHash, v, r, s);
}
```
**Key Considerations:**
* ECDSA accounts on Hedera behave just like EVM accounts when validating signatures with `ECRECOVER`.
* **Use ECDSA accounts when interacting with EVM-compatible dApps, wallets, or bridges for minimal friction.**
***
## Using System Contract Functions for ED25519 Accounts
Hedera’s native key type is ED25519, which is not compatible with `ECRECOVER`. To accommodate ED25519 (Hedera-native) accounts and more complex configurations, HIP-632 introduces Hedera Account Service system contract functions:
* [**isAuthorized**](/evm/hedera-services/system-contracts/account-service#isauthorized-address-message-signatureblob)**:** Validates multiple signatures, supporting threshold or multi-key accounts.
* [**isAuthorizedRaw**](/evm/hedera-services/system-contracts/account-service#isauthorizedraw-address-messagehash-signatureblob)**:** Validates a single raw ED25519 signature, analogous to `ECRECOVER` but for ED25519 keys.
**Example: Validating ED25519 Signatures**
```solidity highlight={4} theme={null}
function verifyED25519Signature(address accountAlias, bytes32 messageHash, bytes memory signatureBlob) public returns (bool) {
(bool success, ) = address(0x167).call(
abi.encodeWithSignature(
"isAuthorizedRaw(address,bytes32,bytes)",
accountAlias,
messageHash,
signatureBlob
)
);
return success;
}
```
**Why This Matters for EVM Developers:**
* **Hedera-Native Accounts:**\
Most Hedera accounts use ED25519 keys, so `isAuthorizedRaw` is essential for verifying their signatures.
* **Multi-Key and Threshold Accounts:**\
Use `isAuthorized` for scenarios requiring multiple signatures, ensuring only properly authorized actions occur.
***Note**:* For detailed parameter formats, consult the [HIP-632](https://hips.hedera.com/hip/hip-632) specification and the Hedera Account Service [documentation](/evm/hedera-services/system-contracts/account-service). Ensure that `accountAlias`, `messageHash`, and `signatureBlob` adhere to the required formats outlined there.
***
## **Practical Use Case: Multi-Key Verification**
Hedera supports advanced account configurations like multi-sig and threshold accounts, which may include both ECDSA and ED25519 keys. Using `isAuthorized`, you can enforce complex signing requirements, such as requiring multiple parties to sign before executing a contract operation.
**Example: DAO Governance Using Multi-Sig**
```solidity highlight={3} theme={null}
function validateDAOProposal(address accountAlias, bytes memory proposalData, bytes memory signatureBlob) public returns (bool) {
(bool success, ) = address(0x167).call(
abi.encodeWithSignature("isAuthorized(address,bytes,bytes)", accountAlias, proposalData, signatureBlob)
);
return success;
}
```
This example demonstrates how you might require multiple signatures to approve a DAO proposal, enhancing the security and trustworthiness of your governance mechanisms.
***
## Key Rotation: Adapting to Hedera’s Dynamic Model
Standard EVM addresses are static since they are derived from a public key hash. Hedera, by contrast, supports dynamic key rotation, letting you update the keys controlling an account without changing the account’s address.
**Why This Matters for EVM Developers:**
* Your applications must dynamically validate the current set of keys each time rather than relying on a static key-to-address mapping.
* By rotating keys, you can enhance security without migrating to a new address.
**Example: Key Rotation in Smart Contracts**
```solidity theme={null}
contract KeyRotationHandler {
address public trustedSigner;
constructor(address initialSigner) {
trustedSigner = initialSigner;
}
function updateTrustedSigner(address newSigner) public {
require(msg.sender == trustedSigner, "Not authorized");
trustedSigner = newSigner;
}
}
```
This simple pattern allows you to change the trusted signer as needed, reflecting real-world operational needs such as periodic key rotation to mitigate security risks.
***
### Additional References and Resources
* [**Hedera SDKs**](/native/fundamentals)
* [**Hedera Account Service**](/evm/hedera-services/system-contracts/account-service)
* [**HIP-632 Specification**](https://hips.hedera.com/hip/hip-632)
* [**Hedera Account Service System Contract**](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/account-service)
# Decimal Handling (8 vs. 18 Decimals)
Source: https://docs.hedera.com/evm/differences/hbar-decimals
## **Overview**
Managing token decimals is critical when working with HBAR, HTS tokens, and ERC tokens on Hedera, as each system has distinct precision standards. These differences impact how token balances are calculated, displayed, and transferred across various tools and environments.
***
## Token Decimal Comparison and API Context
The table below compares the decimal handling of HBAR, HTS tokens, and ERC tokens on Hedera, incorporating details about their representation across APIs and services. This overview highlights differences in precision and context.
API/Service
Decimals
Explanation
Hedera API (HAPI)
8 decimals
HBAR is represented with 8 decimal places, aligning with its native smallest unit tinybar.
Hedera Smart Contract Service
8 decimals
Within the EVM environment, HBAR maintains 8 decimal places, consistent with its native representation.
JSON-RPC Relay (Arguments)
8 decimals
When HBAR values are passed as arguments in JSON-RPC calls, they are represented with 8 decimal places.
JSON-RPC Relay (msg.value)
18 decimals
For compatibility with EVM tooling, msg.value in JSON-RPC Relay represents HBAR with 18 decimal places. Consequently, gasPrice also uses 18 decimal places in this context.
HTS Tokens
Configurable (up to 8 decimals)
HTS tokens allow token creators to define precision at token creation, offering flexibility for various use cases.
ERC Tokens
Default 18 decimals
ERC tokens on Hedera follow Ethereum token standards, with 18 decimals as the default unless specified otherwise.
**Key Impacts**:
* Account for scaling differences when converting HBAR between APIs, especially when using JSON-RPC.
* HBAR fees are always calculated in tinybars, regardless of the API or service used.
* JSON-RPC’s use of 18 decimals ensures smooth integration with EVM tools and libraries.
***
## Conversion Helpers
Utility functions are essential for managing discrepancies between HBAR (measured in tinybars, 8 decimals), HTS tokens (which can have configurable decimal places), and ERC tokens (measured in wei, 18 decimals). These conversions ensure consistency across your smart contracts, front-end applications, and APIs.
**Code Example: Decimal Conversion Helpers**
```solidity wrap theme={null}
// Convert from 18 decimals (weibar/wei) to 8 decimals (tinybar)
function convertToTinybar(uint256 weiAmount) public pure returns (uint256) {
// 1 tinybar = 10^10 weibar
return weiAmount / (10 ** 10);
}
// Convert from 8 decimals (tinybar) to 18 decimals (weibar/wei)
function convertToWei(uint256 tinybarAmount) public pure returns (uint256) {
return tinybarAmount * (10 ** 10);
}
```
Reference: [**Smart Contracts Gas and Fees**](/evm/development/gas-fees)
***
### **Additional Resources**
* [**ERC-20 Token Standard**](/evm/tokens/erc20)
* [**Hedera Token Service Documentation**](/learn/core-concepts/tokens)
* [**HBAR Decimal Places Documentation**](/native/fundamentals/hbars#hbar-decimal-places)
* [**Token Managed by Smart Contracts**](/evm/tokens)
# Understanding Hedera's EVM Differences and Compatibility
Source: https://docs.hedera.com/evm/differences/index
Hedera's EVM-compatible environment lets you deploy Solidity smart contracts using Hardhat, Foundry, or Remix and connect with standard Ethereum tooling. Hedera's architecture introduces differences in account models, key management, token handling, and JSON-RPC behavior that affect how you build and migrate from Ethereum.
This guide is for:
* **EVM developers migrating to Hedera:** Understand key differences in Hedera's architecture, tokenomics, and tooling, including ED25519 key management and native system contracts (introduced in [HIP-632](https://hips.hedera.com/hip/hip-632)).
* **Hedera-native developers adding smart contract functionality:** Learn how EVM contracts interact with Hedera's native services (HTS, HCS, HFS) and how to bridge both worlds.
## High-Level Differences: Hedera vs. Ethereum
| **Feature** | **Hedera** | **Ethereum** |
| ------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------- |
| Consensus Mechanism | Asynchronous Byzantine Fault Tolerance (aBFT), Proof of Stake (PoS) | Byzantine Fault Tolerance (BFT), Proof of Stake (PoS) |
| Transaction Fees | Low and predictable [fees](/networks/fees) | Variable gas fees; can spike during network congestion |
| Governance Model | Governed by the Hedera Governing Council, comprising leading global organizations | Decentralized; governed by the Ethereum community |
| Native Token | HBAR | ETH |
| Token Standard | ERC-20 and ERC-721 supported; Hedera Token Service (HTS) enables native token issuance and management without smart contracts | ERC-20 and ERC-721 |
| Network State | Virtual Merkle Tree | Merkle Patricia Trie |
| Historical Data | Off-chain mirror nodes provide access to historical data and state queries | On-chain `stateRoot` |
| Key Management | Supports [ED25519](/support/glossary#ed25519) (Hedera-native accounts), [ECDSA (secp256k1)](/support/glossary#ecdsa-secp256k1), and complex keys (keylist and threshold) | ECDSA (secp256k1) only |
| Network Upgrades | Proposed through HIPs; governed by the Hedera Governing Council; backward compatible, not forks | Proposed and implemented through EIPs |
***
## Jumbo Ethereum Transactions
Hedera supports jumbo Ethereum transactions (introduced in [HIP-1086](https://hips.hedera.com/hip/hip-1086)), allowing larger `callData` payloads to be included directly in the `ethereumData` field of `EthereumTransaction`. This aligns Hedera's EVM behavior more closely with Ethereum's, enabling seamless deployment of complex contracts.
*📣 To learn more, including size limits, gas calculation, and limitations, see the* [*Ethereum Transaction SDK documentation*](/native/smart-contracts/ethereum-transaction)*.*
***
## EVM Developers: What Changes on Hedera
The following topics cover the most common differences when coming from Ethereum:
| **Topic** | **Description** |
| ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| [Hedera Account Model & Aliases](/evm/differences/accounts-and-keys#understanding-account-models-and-aliases) | How Hedera's account structure differs from Ethereum's: ED25519 vs. ECDSA keys, dynamic key rotation, and aliases for EVM compatibility. |
| [Decimal Handling](/evm/differences/hbar-decimals) | How to handle the difference between EVM's 18-decimal standard and Hedera's 8 decimals for accurate token calculations and conversions. |
| [Key Rotation](/evm/differences/accounts-and-keys#key-rotation-adapting-to-hederas-dynamic-model) | Strategies for designing smart contracts that work with Hedera's dynamic key rotation model. |
| [HBAR Transfers](/evm/differences/native-token-transfers) | Explicit handling of HBAR in Solidity contracts for native token flows. |
| [JSON-RPC Relay](/evm/differences/json-rpc-differences) | How Hedera's JSON-RPC relay differs from standard EVM RPC APIs. |
## Hedera-Native Developers: Adding Smart Contracts
If you're already building with Hedera's native services and want to add EVM smart contract functionality, see [Hedera-Native Developers Adding Smart Contracts](/evm/differences/native-devs).
## Additional Resources
* [Getting Started for EVM Developers](/evm)
* [JSON-RPC Documentation](/evm/development/json-rpc)
* [Mirror Node API Documentation](/reference/rest-api)
* [Java SDK](https://github.com/hiero-ledger/hiero-sdk-java), [Go SDK](https://github.com/hiero-ledger/hiero-sdk-go), [JavaScript SDK](https://github.com/hiero-ledger/hiero-sdk-js)
# JSON-RPC Relay and EVM Tooling
Source: https://docs.hedera.com/evm/differences/json-rpc-differences
Learn how to use the JSON-RPC relay and familiar EVM tools to interact with the Hedera network.
## **Overview**
Hiero’s JSON-RPC relay provides a familiar interface for EVM developers by supporting standard Ethereum JSON RPC methods. This compatibility means you can use popular EVM development tools (like Hardhat, Truffle, or Foundry) and wallets (like Metamask) to interact with Hedera’s network. However, Hedera’s unique state management model affects how you retrieve historical data and verify states, requiring a shift in approach from the standard EVM workflow.
***
## **Key Relay Features**
The relay offers several advanced features that enhance dApp development on Hedera:
#### **Real-Time Data and Event Filtering**
The relay provides robust support for real-time data streaming and event filtering through its WebSocket server and Filter API methods. This allows applications to listen for on-chain events and receive updates as they happen.
* WebSocket Support (`eth_subscribe`): Developers can establish a WebSocket connection to the relay (default: `ws://localhost:8546`) to subscribe to logs and newHeads events. This is ideal for applications that need to react instantly to new blocks or specific contract events. This functionality is enabled by HIP-694.
* Filter API Methods: The relay supports the standard Ethereum Filter API, including `eth_newFilter`, `eth_getFilterChanges`, and `eth_getFilterLogs`. These methods allow you to create and query filters for historical logs and pending transactions, providing a powerful way to track contract activity.
#### **Paymaster Support for Gasless Transactions**
The JSON-RPC relay supports a paymaster feature, enabling gasless transactions for users. When this feature is enabled, the relay operator can sponsor transaction fees, allowing dApp users to interact with smart contracts without needing to hold HBAR for gas. This is ideal for improving user onboarding and creating seamless application experiences.
**Key features of paymaster support include:**
* **Gasless Transactions**: Users can send transactions with a gas price of 0.
* **Operator-Sponsored Fees**: The relay operator covers the HAPI and Ethereum fees.
* **Flexible Configuration**: Operators can enable paymaster support for all transactions (wildcard) or restrict it to a whitelist of specific smart contract addresses.
**💡***For more details on how to configure and use the paymaster feature, please refer to the configuration details in the* [***Hiero JSON RPC Relay repository***](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/configuration.md)**.**
#### **Testing with Network Forking**
Hedera now supports network forking, which allows you to test smart contracts against a live network's state without executing transactions on the actual network. This is a feature for development and debugging, as it lets you simulate transactions and contract interactions in a realistic environment. You can fork the Hedera network using both Hardhat and Foundry.
For detailed instructions and examples, please refer to our tutorials:
* [Forking the Hedera Network for Local Testing (Core Concepts)](/evm/development/forking)
* [How to Fork the Hedera Network with Hardhat (Basic ERC-20)](/evm/tools/hardhat/forking-basic)
* [How to Fork the Hedera Network with Hardhat (Advanced HTS)](/evm/tools/hardhat/forking-advanced)
* [How to Fork the Hedera Network with Foundry (Basic ERC-20)](/evm/tools/foundry/forking)
***
## **Ethereum RPC API Behavior via JSON-RPC Relay**
On Ethereum, methods like `eth_getBlockByNumber` return the true value of `stateRoot` that enables direct historical state verification. Hiero’s JSON-RPC relay, however, returns the root hash of an empty Merkle trie for the `stateRoot` value for compatibility. Instead of relying on it, you should query Hedera’s mirror nodes for historical states, event logs, and transaction details.
#### **Example JSON-RPC Query Request**
A request to `eth_getBlockByNumber` returns a `stateRoot`, but it’s not useful for historical verification on Hedera. Instead, use mirror node REST APIs to fetch the necessary historical information.
```shell theme={null}
curl -X POST \
-H "Content-Type: application/json" \
-d
"jsonrpc": "2.0",
"method": "eth_getBlockByNumber",
"params": [
"0x1",
false
],
"id": 1
}
https://testnet.hashio.io/api
```
This returns the root hash of an empty Merkle trie for compatibility and not the actual `stateRoot` value.
***
## **Endpoints**
The JSON RPC Relay methods implement a subset of the standard method:
#### **Gossip Methods**
These methods track the head of the chain. This is how transactions make their way around the network, find their way into blocks, and how clients find out about new blocks.
| Method | Static Response Value |
| --------------------------------------------------------------------------------------------------------- | --------------------- |
| [`eth_blockNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_blocknumber) | N/A |
| [`eth_sendRawTransaction`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_sendrawtransaction) | N/A |
#### **State Methods**
Methods that report the current state of all the data stored. The “state” is like one big shared piece of RAM, and includes account balances, contract data, and gas estimations.
| Method | Static Response Value |
| ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
| [eth\_getBalance](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getbalance) | n/a |
| [eth\_getStorageAt](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getstorageat) | n/a |
| [eth\_getTransactionCount](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactioncount) | n/a |
| [eth\_getCode](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getcode) | n/a |
| [eth\_call](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_call) | n/a |
| [eth\_estimateGas](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_estimategas) | generates and returns an estimate of the gas required for the transaction to complete |
#### **History Methods**
Fetches historical records of every block back to genesis. This is like one large append-only file, and includes all block headers, block bodies, uncle blocks, and transaction receipts.
| Method | Static Response Value |
| ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------- |
| [eth\_getBlockTransactionCountByHash](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblocktransactioncountbyhash) | n/a |
| [eth\_getBlockTransactionCountByNumber](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblocktransactioncountbynumber) | n/a |
| [eth\_getUncleCountByBlockHash](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getunclecountbyblockhash) | `null` |
| [eth\_getUncleCountByBlockNumber](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getunclecountbyblocknumber) | `0x0` |
| [eth\_getBlockByHash](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbyhash) | `stateRoot` is always zero |
| [eth\_getBlockByNumber](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getblockbynumber) | `stateRoot` is always zero |
| [eth\_getTransactionByHash](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyhash) | n/a |
| [eth\_getTransactionByBlockHashAndIndex](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyblockhashandindex) | n/a |
| [eth\_getTransactionByBlockNumberAndIndex](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionbyblocknumberandindex) | n/a |
| [eth\_getTransactionReceipt](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_gettransactionreceipt) | n/a |
| [eth\_getUncleByBlockHashAndIndex](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getunclebyblockhashandindex) | `null` |
| [eth\_getUncleByBlockNumberAndIndex](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_getunclebyblocknumberandindex) | `null` |
**💡*****See the full list of methods*** [***here***](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/rpc-api.md)***.***
## **Supported EVM Development Tools**
Feature
web3js
Truffle
ethers
Hardhat
Remix IDE
Foundry
Transfer HBARS
✅
✅
✅
✅
✅
✅
Contract Deployment
✅
✅
✅
✅
✅
✅
Can use the contract instance after deploy without re-initialization
✅
✅
✅
✅
✅
✅
Contract View Function Call
✅
✅
✅
✅
✅
✅
Contract Function Call
✅
✅
✅
✅
✅
✅
Debug Operations\*\*
✅
✅
✅
✅
✅
✅
\*\***Debug operations** are supported via the `debug_traceTransaction` and `debug_traceBlockByNumber` methods. To enable these methods, you must set `DEBUG_API_ENABLED=true` in your relay configuration. For more information, see the [debugging documentation](https://github.com/hiero-ledger/hiero-json-rpc-relay/blob/main/docs/debugging-transactions.md).
**Note**: Development tools usually make a lot of requests to certain endpoints, especially during contract deployment. Be aware of rate limiting when deploying multiple large contracts.
**Note**: Enable `development mode` to correctly assert revert messages of contract calls with `hardhat-chai-matchers`.
***
## Additional Resources
* [**Supported EVM Tooling**](https://github.com/hiero-ledger/hiero-json-rpc-relay/tree/main/tools)
* [**JSON-RPC Relay Docs**](/evm/development/json-rpc)
* [**Hiero JSON-RPC Relay Repo**](https://github.com/hiero-ledger/hiero-json-rpc-relay)
# Integrating ED25519 Accounts and Advanced Features Into Smart Contracts
Source: https://docs.hedera.com/evm/differences/native-devs/ed25519-integration
## Overview
Hedera-native developers can leverage Hedera’s advanced account and key management features, including ED25519
accounts, multi-sig configurations, and threshold keys. To integrate seamlessly with EVM-compatible chains and
applications, you’ll need to work with ECDSA key pairs.
Hedera’s [HIP-632](https://hips.hedera.com/hip/hip-632)
system contract functions—`isAuthorized`
and `isAuthorizedRaw`—bridge
this gap by enabling on-chain verification of both ED25519 and ECDSA signatures. This ensures you can extend
Hedera-native security features into EVM-compatible smart contracts without compromising trust boundaries or
functionality.
***
### **Bridging ED25519 Accounts with Solidity**
Hedera’s ED25519 accounts are incompatible with Solidity’s `ECRECOVER`
function, which supports ECDSA. To enable seamless integration, HIP-632 introduces two key system contract
functions:
* `isAuthorizedRaw`:
Validates a single raw ED25519 signature.
* `isAuthorized`:
Validates multiple signatures or threshold key configurations, supporting multi-sig and advanced key
schemes.
These functions allow you to enforce the same account security models on-chain within smart contracts.
**Basic Example: Validating a Single ED25519 Signature**
Here’s a Solidity example for validating ED25519 signatures using the isAuthorizedRaw function. The function calls
the system contract function (`isAuthorizedRaw`)
to verify a raw signature on-chain.
```solidity theme={null}
function verifyED25519Signature(
address accountAlias,
bytes32 messageHash,
bytes memory signatureBlob
) public returns (bool) {
(bool success, ) = address(0x167).call(
abi.encodeWithSignature(
"isAuthorizedRaw(address,bytes32,bytes)",
accountAlias,
messageHash,
signatureBlob
)
);
return success;
}
```
**Use Case**: Validate ED25519 signatures on-chain to ensure that only authorized
accounts execute sensitive operations.
***
### Integrating Multi-Sig and Threshold Keys On-Chain
Hedera’s account model supports multi-sig and threshold key configurations. You can replicate these models
on-chain with `isAuthorized`
for robust access control.
**Example: On-Chain Multi-Sig Verification**
This example demonstrates requiring multiple valid signatures for critical contract actions:
```solidity theme={null}
function validateMultiSig(address accountAlias, bytes memory proposalData, bytes memory signatureBlob) public returns (bool) {
(bool success, ) = address(0x167).call(
abi.encodeWithSignature("isAuthorized(address,bytes,bytes)", accountAlias, proposalData, signatureBlob)
);
return success;
}
```
**Use Case**: Ideal for governance scenarios like DAOs where multiple stakeholders
must approve actions.
**Advanced Example: Executing a DAO Proposal with Threshold Keys**
In more complex scenarios, like DAOs, you can combine multi-sig verification with actionable contract logic to
enforce threshold-based governance processes:
```solidity theme={null}
function executeProposal(address daoAccount, bytes memory proposalData, bytes memory signatures) public {
require(validateMultiSig(daoAccount, proposalData, signatures), "Invalid signatures");
// Execute the proposal logic here
}
```
This ensures that even on-chain actions that modify state or issue tokens adhere to your established
threshold-based governance processes.
***
### Supporting Dynamic Key Rotation
Hedera’s dynamic key rotation allows you to update an account’s keys without changing its alias. By integrating
`isAuthorized`
checks into your contracts, your on-chain logic automatically remains in sync with the current authorized keys.
Even as keys change over time to improve security or operational flexibility, your contracts don’t need to be
redeployed or modified—`isAuthorized`
will always reflect the latest configuration.
***
## **References**
* [**HIP-632 Documentation**](https://hips.hedera.com/HIP/hip-632)
* [**Hedera Account Service**](/evm/hedera-services/system-contracts/account-service)
# Extending Token Management with Smart Contracts
Source: https://docs.hedera.com/evm/differences/native-devs/extending-token-management
## **Overview**
As a Hedera developer, you’re familiar with managing token supply through the Hedera Token Service (HTS). By integrating smart contracts, you can add programmable logic to your tokens, enabling conditional minting, burning, or transferring based on on-chain criteria. This approach allows you to design advanced tokenomics mechanisms tailored to your application’s needs.
### **Key Considerations for Tokenomics on Hedera**
* Hedera does not support native HBAR burning; custom tokenomics strategies rely on HTS for minting and burning tokens.
* The supply key grants critical permissions for token management, and its secure handling is essential.
**Recommended Practices**
* **Combine HTS and Smart Contracts**:
* Use HTS system contract functions (`mintToken`, `burnToken`) to manage token supply programmatically within smart contracts.
* Securely assign a supply key to your HTS token.
* **Implement Access Control**:
* Use multi-sig accounts or role-based permissions to secure supply modifications.
* Validate input parameters in smart contract functions to prevent misuse.
***
### **Example: Minting and Burning HTS Tokens**
The following smart contract demonstrates how to mint and burn HTS tokens using system contracts:
```solidity wrap theme={null}
pragma solidity ^0.8.0;
interface HederaTokenService {
function mintToken(address token, int64 amount, bytes[] calldata metadata) external returns (int64 newTotalSupply);
function burnToken(address token, int64 amount, bytes[] calldata metadata) external returns (int64 newTotalSupply);
}
contract TokenManager {
HederaTokenService constant hts = HederaTokenService(0x167);
address public tokenAddress; // HTS token with a supply key
constructor(address _tokenAddress) {
tokenAddress = _tokenAddress;
}
function mintTokens(int64 amount) external {
hts.mintToken(tokenAddress, amount, new bytes[](0));
}
function burnTokens(int64 amount) external {
hts.burnToken(tokenAddress, amount, new bytes );
}
}
```
***
## Additional Resources
* [**HTS System Contract Functions**](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/token-service)
* [**Tokens Managed by Smart Contracts**](/evm/tokens)
* [**Accessing HTS Tokens Through the EVM**](/evm/hedera-services/hybrid)
# For Hedera-Native Developers Adding Smart Contract Functionality
Source: https://docs.hedera.com/evm/differences/native-devs/index
## **Introduction**
As a Hedera-native developer, you are already familiar with Hedera’s features, such as ED25519-based key management, the Hedera Token Service (HTS), the Hedera Consensus Service (HCS), and workflows enabled by Hedera's SDKs. Integrating smart contracts into your existing workflows by leveraging Hedera’s EVM implementation allows you to embed on-chain logic directly into Hedera-native applications. This guide outlines the key considerations for adding EVM-compatible smart contract functionality without losing the performance and security benefits of Hedera’s architecture.
***
### **What You'll Learn**
Topic
Description
Cross-Chain Compatibility
Manage ECDSA and ED25519 key types to enable interoperability with EVM-based ecosystems.
State Management
Adapt to Hedera’s off-chain state model, leveraging mirror nodes and event logs for querying and validation.
Token Management
Extend the Hedera Token Service with custom on-chain logic for minting, burning, and transferring tokens.
Signature Verification
Implement robust authorization mechanisms using isAuthorized and isAuthorizedRaw system contract functions.
### **Why Add Smart Contract Functionality?**
Adding Hedera's EVM-compatible smart contract functionality allows developers to:
* Build custom logic directly into your applications without relying solely on external SDKs.
* Connect and interact with other EVM-compatible chains and tools.
* Combine Solidity’s flexibility with Hedera’s predictable cost model, high throughput, and finality guarantees.
***
## Additional Resources
* [**JSON-RPC Relay Guide**](/evm/development/json-rpc)
* [**Mirror Node API Reference**](/reference/rest-api)
* [**Hedera Token Service Documentation**](/learn/core-concepts/tokens)
# JSON-RPC Relay and State Queries
Source: https://docs.hedera.com/evm/differences/native-devs/json-rpc-state-queries
## Overview
Hedera’s JSON-RPC relay provides compatibility with standard Ethereum JSON-RPC methods but is tailored to Hedera’s unique architecture and state management model. This page outlines key differences and practical guidance for developers adding smart contract functionality to Hedera-native applications. The content emphasizes adapting workflows for Hedera's consensus-driven model, understanding JSON-RPC’s behavior on Hedera, and leveraging tools like mirror nodes effectively.
***
## **Key Differences in JSON-RPC Behavior on Hedera**
Hedera’s JSON-RPC relay acts as a compatibility layer, enabling EVM-based tooling to interact with Hedera. While it mirrors the standard Ethereum JSON-RPC API structure, its behavior reflects Hedera’s unique architecture:
Feature
Hedera
Ethereum
State Management
No Merkle Patricia Trie. For RPC block data requests, it returns the root hash of an empty Merkle trie.
Uses a Merkle Patricia Trie for stateRoot, enabling direct historical state verification.
Historical Data
Use mirror nodes to retrieve historical events, balances, and transaction details.
Historical data can be queried directly using Ethereum RPC methods like eth\_getBlockByNumber.
Testing Features
Does not support contract snapshot features.
Supports snapshots for fast and modular testing.
***
## Contract Interactions
* Use methods like `eth_call` and `eth_sendTransaction` to interact with deployed contracts via the JSON-RPC relay.
* Fetch historical states or balances using mirror node REST APIs, as Hedera does not use a Merkle Patricia Trie.
### **Example**
#### `eth_call` Request
```bash theme={null}
curl -X POST \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": "0x1234567890abcdef1234567890abcdef12345678",
"data": "0x6d4ce63c"
},
"latest"
],
"id": 1
}' \
https://testnet.hashio.io/api
```
#### Querying historical balances
```bash wrap theme={null}
curl -X GET \
-H "Content-Type: application/json" \
"https://testnet.mirrornode.hedera.com/api/v1/accounts/0.0.123/balances?timestamp=1672549200"
```
***
## Additional Resources
* [**Mirror Node REST API Documentation**](/reference/rest-api)
* [**JSON-RPC Methods on Hedera**](https://github.com/hashgraph/hedera-json-rpc-relay/blob/main/docs/rpc-api.md)
# Handling HBAR Transfers in Contracts
Source: https://docs.hedera.com/evm/differences/native-token-transfers
## Overview
On Ethereum, sending ETH to a contract address automatically triggers the `receive()` or `fallback()` functions, allowing contracts to process incoming funds. On Hedera, these functions also exist but require HBAR to be explicitly sent via `contractCall` for them to execute. Direct HBAR transfers to a contract’s Hedera account won’t trigger any logic unless additional steps are taken.
Fortunately, the core Solidity patterns—like using `transfer()`, `send()`, or `call()`—work the same way on Hedera, making it easy for developers familiar with EVM. This guide highlights these mechanisms, details the key Hedera-specific considerations, and provides examples to help you handle HBAR transfers in your smart contracts
### **Sending to Contract**
In Solidity, there are three ways to transfer value to and from contracts:
* `transfer()`: Sends a fixed amount of gas and reverts on failure.
* `send()`: Sends a fixed amount of gas and returns `false` on failure instead of reverting.
* `call()`: A low-level function for sending value that allows specifying gas and includes additional data payloads.
These methods are supported on Hedera, ensuring compatibility with existing Solidity patterns. For more information on the supported functions, refer to the [Hiero Contracts Repo](https://github.com/hiero-ledger/hiero-contracts/tree/main).
### **Key Considerations**
* **Fallback and Receive Functions**: When sending HBAR to a contract address via `contractCall`, Hedera behaves like Ethereum. If `receive()` or `fallback()` functions are defined in the contract, they will be triggered upon receipt of HBAR.
* **Important Note**: Directly transferring HBAR to a contract’s Hedera account (not via `contractCall`) will not trigger these functions. To execute logic upon receipt, ensure transfers occur within the EVM environment.
***
## Example Contract Functions for HBAR Transfers
Below is an example of contract functions and how HBAR transfers are handled using Solidity. These patterns are identical to those used for ETH on the EVM:
```solidity theme={null}
// Handle incoming HBAR transfers
receive() external payable {
// Example logic for received HBAR
emit HbarReceived(msg.sender, msg.value);
}
// Transfer HBAR using different methods
function transferHbar(address payable _receiverAddress, uint _amount) public {
_receiverAddress.transfer(_amount);
}
function sendHbar(address payable _receiverAddress, uint _amount) public {
require(_receiverAddress.send(_amount), "Failed to send HBAR");
}
function callHbar(address payable _receiverAddress, uint _amount) public {
(bool sent, ) = _receiverAddress.call{value: _amount}("");
require(sent, "Failed to send HBAR");
}
// Event for logging received HBAR
event HbarReceived(address sender, uint256 amount);
```
**Suggested Tutorial**
For developers newer to Solidity, we recommend exploring [Solidity courses](https://solidity-by-example.org/) to gain a deeper understanding of handling value transfers. A detailed tutorial on sending and receiving HBAR using Solidity smart contracts on Hedera can be found [here](/evm/tutorials/intermediate/send-receive-hbar).
***
### Additional Resources
* [**Hiero Contracts**](https://github.com/hiero-ledger/hiero-contracts/tree/main)
* [**Solidity Documentation**](https://docs.soliditylang.org/)
* [**Solidity by Example**](https://solidity-by-example.org/)
# Token Management with Hedera Token Service
Source: https://docs.hedera.com/evm/differences/tooling-compatibility
## Overview
Ethereum supports native ETH burning via mechanisms like [EIP-1559](https://ethereum.github.io/abm1559/notebooks/eip1559.html), but Hedera takes a different approach. The native HBAR token cannot be burned. Instead, developers can use the Hedera Token Service (HTS) to create and manage custom tokens with built-in minting and burning capabilities.
### Key Features for EVM Developers
* **Supply Key**
* Controls minting and burning of tokens.
* Must be securely managed to prevent unauthorized actions.
* **HTS System Contract**
* System contract functions accessible via reserved address `0x167`.
* Enable token creation, minting, and burning directly from Solidity contracts.
* **Access Control**
* Only addresses authorized by the supply key can mint or burn tokens.
* Multi-signature (multi-sig) or threshold key configurations can enhance security.
***
### Minting and Burning Tokens with HTS
* **Minting Tokens**: Introduce new tokens for incentives, rewards, or liquidity.
* **Burning Tokens**: Remove tokens to increase scarcity or meet regulatory requirements.
#### Code Example: HTS Mint/Burn
```solidity wrap theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.0;
interface HederaTokenService {
function createFungibleToken(
address treasury,
uint64 initialSupply,
string memory tokenName,
string memory tokenSymbol,
uint32 decimals
) external returns (address tokenAddress);
function mintToken(address token, int64 amount, bytes[] calldata metadata) external returns (int64 newTotalSupply);
function burnToken(address token, int64 amount, bytes[] calldata metadata) external returns (int64 newTotalSupply);
}
contract TokenManager {
HederaTokenService constant hts = HederaTokenService(0x167);
address public token;
constructor(address treasury) {
// Create a fungible token with an initial supply of 1,000 units
// Token parameters: name = "MyHederaToken", symbol = "MHT", decimals = 8
token = hts.createFungibleToken(treasury, 1000, "MyHederaToken", "MHT", 8);
}
// Mint additional tokens. Ensure that msg.sender holds the supply key or is authorized.
function mintMoreTokens(int64 amount) external {
// Metadata array left empty, but can be used for NFT-like functionality or extra data
hts.mintToken(token, amount, new bytes[](0));
}
// Burn existing tokens. Ensure the caller is authorized via supply key management.
function burnSomeTokens(int64 amount) external {
hts.burnToken(token, amount, new bytes[](0));
}
}
```
**Important Notes**
* Ensure the caller holds the supply key.
* Associate the treasury account with the token for successful operations.
* Minting and burning fail without proper key authorization.
***
## Additional Resources
* [**Access HTS Tokens Through the EVM**](/evm/hedera-services/hybrid/erc-compatibility)
* [**HTS System Contract Functions**](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/token-service)
# Create Tokens
Source: https://docs.hedera.com/evm/hedera-services/hts-solidity/create-tokens
Create fungible and non-fungible HTS tokens directly from Solidity via the 0x167 system contract.
The HTS system contract at `0x167` lets a Solidity contract create native HTS tokens. The resulting token is a real HTS token: same association rules, same mirror node REST responses, same HashScan view as an SDK-created one. You can operate on it through the HTS interface or through ERC-20 / ERC-721 redirects.
## At a glance
| Field | Value |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------- |
| Contract address | `0x167` |
| Reference HIPs | [HIP-358 (token creation)](https://hips.hedera.com/hip/hip-358), [HIP-206 (HTS precompile)](https://hips.hedera.com/hip/hip-206) |
| Solidity source | [hiero-contracts/contracts/token-service](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/token-service) |
| Key functions | `createFungibleToken`, `createNonFungibleToken`, `createFungibleTokenWithCustomFees`, `createNonFungibleTokenWithCustomFees` |
## Why use this instead of a plain ERC-20?
Two practical reasons.
The first is compliance features. HTS tokens get native support for KYC keys, freeze, pause, wipe, supply caps, and royalty fees, and the network enforces them. If you build the same controls into an ERC-20 contract, you write them yourself and pay gas every time they fire.
The second is pricing. HTS operations are priced in USD and paid in HBAR by the network. ERC-20 operations are priced in EVM gas, which scales with whatever you put in your contract.
If you don't need any of that, a plain ERC-20 is simpler and works fine. The choice is per-token; nothing stops you from using both in one app.
## Required imports
The helper library is `HederaTokenService.sol`, in the [hiero-contracts repository](https://github.com/hiero-ledger/hiero-contracts). Copy these files into your `contracts/` directory:
```text theme={null}
contracts/
├── HederaTokenService.sol # Wrapper that handles call/response codes
├── IHederaTokenService.sol # Raw interface (function signatures, structs)
├── HederaResponseCodes.sol # Numeric response codes (SUCCESS, INVALID_TOKEN_ID, ...) — from contracts/common/
├── ExpiryHelper.sol # Builds the Expiry struct
├── FeeHelper.sol # Builds FixedFee, FractionalFee, RoyaltyFee structs
└── KeyHelper.sol # Builds HederaToken.TokenKey entries
```
## Example: fungible token
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;
import "./HederaTokenService.sol";
import "./IHederaTokenService.sol";
import "./HederaResponseCodes.sol";
import "./KeyHelper.sol";
import "./ExpiryHelper.sol";
contract TokenFactory is HederaTokenService, KeyHelper, ExpiryHelper {
event TokenCreated(address tokenAddress);
function createFungible(
string memory name,
string memory symbol,
int64 initialSupply,
int32 decimals
) external payable returns (address tokenAddress) {
// Build the supply key. This contract will be authorized to mint more.
IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](1);
keys[0] = getSingleKey(
KeyType.SUPPLY,
KeyValueType.CONTRACT_ID,
address(this)
);
IHederaTokenService.HederaToken memory token;
token.name = name;
token.symbol = symbol;
token.treasury = address(this);
token.memo = "created via system contract";
token.tokenSupplyType = false; // false = infinite supply
token.maxSupply = 0;
token.freezeDefault = false;
token.tokenKeys = keys;
token.expiry = createAutoRenewExpiry(address(this), defaultAutoRenewPeriod);
// Token creation costs HBAR (rent); forward msg.value to the precompile.
(int responseCode, address created) = HederaTokenService.createFungibleToken(
token, initialSupply, decimals
);
require(responseCode == HederaResponseCodes.SUCCESS, "HTS token create failed");
emit TokenCreated(created);
return created;
}
}
```
## Example: non-fungible token
```solidity theme={null}
function createNft(
string memory name,
string memory symbol
) external payable returns (address tokenAddress) {
IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](1);
keys[0] = getSingleKey(KeyType.SUPPLY, KeyValueType.CONTRACT_ID, address(this));
IHederaTokenService.HederaToken memory token;
token.name = name;
token.symbol = symbol;
token.treasury = address(this);
token.tokenSupplyType = true; // true = finite supply
token.maxSupply = 10_000;
token.tokenKeys = keys;
token.expiry = createAutoRenewExpiry(address(this), defaultAutoRenewPeriod);
(int responseCode, address created) = HederaTokenService.createNonFungibleToken(token);
require(responseCode == HederaResponseCodes.SUCCESS, "HTS NFT create failed");
return created;
}
```
## Paying for it
Token creation costs HBAR. The caller has to forward enough `msg.value` to cover the `TokenCreate` transaction fee, which buys the token its initial auto-renew period (\~92 days). The current base fee is around \$1 USD worth of HBAR for both fungible and non-fungible tokens.
Two important things to know about how Hedera handles the money:
* **Gas is charged on consumption.** Per HIP-1249, Hedera refunds 100% of unused gas, and the per-transaction limit is 15M (HIP-185). Set a generous gas limit; you only pay for what you actually use.
* **Excess `msg.value` is not refunded.** Anything you forward beyond what the precompile consumes for the `TokenCreate` fee stays in the calling contract's balance. There is no automatic refund to the EOA.
The practical pattern: compute the exact tinybars via the [Exchange Rate system contract](/evm/hedera-services/system-contracts/exchange-rate) just before the call, or build a refund step into your contract that returns leftover HBAR to `msg.sender` after the precompile call.
```solidity theme={null}
// Compute the exact value just before the call, then forward only that amount.
// 1.5 USD in tinycents = 1.5 * 10^8 tinycents. Gives a small safety margin
// over the ~$1 base fee.
uint256 tinybars = IExchangeRate(0x168).tinycentsToTinybars(15 * 10**7);
uint256 wei_ = tinybars * 10**10; // tinybars (8 decimals) -> wei (18 decimals)
TokenFactory(factory).createFungible{value: wei_}("MyToken", "MTK", 1_000_000, 2);
```
Token creation fails with `INSUFFICIENT_PAYER_BALANCE` if `msg.value` doesn't cover the fee. If you can't compute the exact amount, pass extra and refund the leftover from inside your contract. Don't expect Hedera to do it for you.
## Token keys
The `HederaToken.tokenKeys` array decides who can do what to the token. Each entry is a `(keyType, keyValue)` pair:
| Key | Authorizes |
| -------------- | ------------------------------------------------- |
| `ADMIN` | Updating token properties and rotating other keys |
| `SUPPLY` | Minting and burning |
| `FREEZE` | Freezing and unfreezing accounts |
| `WIPE` | Wiping tokens from accounts |
| `KYC` | Granting and revoking KYC status |
| `PAUSE` | Pausing all token operations |
| `FEE_SCHEDULE` | Updating the custom-fee schedule |
| `METADATA` | Updating NFT serial-number metadata (HIP-657) |
Build each entry with `KeyHelper.getSingleKey(KeyType.X, KeyValueType.Y, address)`. Common `KeyValueType` values: `CONTRACT_ID` (the contract signs implicitly), `INHERIT_ACCOUNT_KEY` (use the caller's key), `ED25519` / `ECDSA` (provide a raw public key).
## See also
Full function reference for the HTS precompile, including transfer, mint, burn, freeze, pause, and KYC operations.
# ERC/EVM-Compatible Tokenization
Source: https://docs.hedera.com/evm/hedera-services/hybrid/erc-compatibility
Hedera provides full compatibility with ERC token standards through its EVM smart contract support, allowing developers to deploy and interact with ERC-20, ERC-721, and other EVM-based tokens. By integrating ERC standards with Hedera’s scalability, security, and low fees, developers can use familiar EVM tooling while benefiting from Hedera’s performance optimizations.
***
## **Why Choose ERC/EVM Tokenization on Hedera?**
* Deploy ERC-20, ERC-721, and other EVM-based contracts directly on Hedera's EVM implementation.
* Use EVM-native tools like Hardhat, Web3.js, ethers.js, and Remix to interact with smart contracts.
* Interact with smart contracts via JSON-RPC relay, maintaining a familiar EVM development workflow.
* Achieve greater scalability and efficiency, with predictable low-cost transactions and higher throughput than Ethereum.
This makes Hedera an ideal platform for EVM developers looking for high-performance alternatives without modifying their existing smart contracts.
***
## **ERC Token Standards on Hedera**
Hedera supports multiple ERC token standards, allowing developers to deploy smart contracts that interact seamlessly with EVM dApps and wallets.
Payable tokens supporting direct contract payments
Subscription models, in-app purchases
These standards enable the deployment of any smart contract, including DeFi applications and tokenization contracts like ERC-20 and ERC-721 tokens. This compatibility alows EVM developers to leverage familiar workflows, tools and, frameworks on Hedera.
***
## Deploying and Interacting with ERC Tokens on Hedera
### **Using JSON-RPC for EVM Tooling**
Hedera provides a JSON-RPC relay, making it easy for developers to interact with smart contracts using EVM-native tools. These tools provide standard EVM developer workflows on Hedera's EVM environment. Developers can use the same JSON-RPC methods as Ethereum, ensuring compatibility with dApps, wallets, and DeFi protocols.
### **HTS Tokens as ERC-20/ERC-721 via Facade Contracts**
Hedera provides facade contracts (per HIP-218 and HIP-376) that allow HTS-native tokens to function as ERC-20 or ERC-721 tokens. With these contracts, developers can leverage Hedera’s efficiency while maintaining EVM compatibility.
A facade contract on Hedera acts as a built-in adapter, allowing Hedera Token Service (HTS) tokens to function seamlessly as standard ERC-20 or ERC-721 tokens within EVM-compatible (EVM) environments. This integration enables developers to interact with HTS tokens using familiar Ethereum interfaces, such as `transfer()`, `approve()`, and `transferFrom()`, without requiring modifications to existing Ethereum wallets or decentralized applications (dApps).
Under the hood, when an EVM-compatible tool interacts with an HTS token's facade contract, the call is delegated to Hedera's native token service. This design ensures that HTS tokens can be managed and transacted using standard Ethereum tooling, providing a seamless developer experience.
In summary, facade contracts provide a bridge between Hedera's native token services and the Ethereum ecosystem, enabling developers to leverage Hedera's performance benefits while maintaining compatibility with established Ethereum standards and tools.
### Token Associations
When transferring HTS tokens on Hedera, recipients must associate the token with their account before receiving it. [Learn more about token auto associations and fees](/learn/core-concepts/tokens/airdrops#auto-associations-and-fees).
### **Synthetic Events for Tokens Managed by Smart Contracts**
Smart contract tokens like ERC-20 and ERC-721 emit events, creating contract logs that developers can query or subscribe to. Hedera Token Service (HTS) tokens do not natively generate such event logs. As a solution to this limitation, Hedera Mirror Nodes generate synthetic event logs, enabling event-driven workflows to mimic the behavior of smart contract tokens for HTS transactions. Synthetic events are generated for transactions such as:
* `CryptoTransfer`
* `CryptoApproveAllowance`
* `CryptoDeleteAllowance`
* `TokenMint`
* `TokenWipe`
* `TokenBurn`
This feature enables developers to effectively monitor HTS token activities as if they were smart contract tokens. An example code implementation demonstrating using ethers.js to listen to synthetic events can be found [here](https://github.com/ed-marquez/hedera-example-hts-synthetic-events-sdk-ethers).
## Video Resource
Oracles for EVM accessible data for prices of tokens, etc
# Hybrid (HTS + EVM ) Tokenization
Source: https://docs.hedera.com/evm/hedera-services/hybrid/index
## **Hybrid Tokenization: Combining HTS and Smart Contracts**
Hedera's system contracts allow EVM-based smart contracts to interact directly with HTS tokens. This integration enables smart contracts to manage HTS tokens as if they were standard ERC tokens, facilitating complex interactions and programmability. For example, the Hedera Account Service (HAS) system contract introduces an account proxy to interact with other contracts, enabling functionalities such as HBAR allowances and authorization checks directly within smart contracts.
By combining these features, Hedera provides a robust platform for developers to leverage both native token services and EVM-based smart contracts, ensuring scalability, security, and interoperability within the blockchain ecosystem.
### Smart Contract-Based Token Management
Smart contracts provide programmable, self-executing contracts to create, manage, and enforce conditions for tokens. Tokenized assets managed by smart contracts could represent various types of assets, such as cryptocurrencies, non-fungible tokens, and real-world assets (RWAs). Secure transfer and complex interactions across decentralized apps (dApps) are facilitated by tokens, beyond mere transactions.
Ethereum’s ERC-20 (fungible tokens) and ERC-721 (non-fungible tokens) standards offer universal interfaces, ensuring compatibility across exchanges, wallets, and dApps. Developers find it convenient to implement by adhering to these standards, while predictable platform behavior is guaranteed.
Hedera extends this compatibility further by allowing HTS-native tokens to act as ERC-20 or ERC-721. This makes it possible to make minimal, if any, adjustments while deploying EVM smart contracts on Hedera while still tapping HTS's native efficiencies, such as low-cost transactions and compliant-by-default.
***
## How HTS and the EVM Work Together
### **Token Creation & Management with HTS**
Hedera offers the Hedera API (HAPI), granting comprehensive access to services like account management, token transactions, and consensus. Developers can utilize Hedera SDKs to perform actions such as token transfers, contract calls, and consensus messaging. HTS is used for native token issuance, transfers, and compliance controls, ensuring fast and efficient transactions.
* Mint, burn, and transfer tokens with low fees.
* Use built-in compliance tools (KYC, Freeze, Pause, Wipe).
* Enable atomic swaps between HTS tokens and HBAR.
### **Advanced Logic & Automation with Smart Contracts**
Solidity smart contracts add programmability to token operations, allowing developers to:
* Define automated rules for token transfers, staking, or rewards.
* Integrate with DeFi applications using lending, swaps, and pooling logic.
* Enforce complex business logic for RWAs, gaming economies, and NFT royalties.
HTS provides efficiency, while smart contracts enable custom behavior.
***
## **System Contracts for Direct HTS Token Interactions**
Hedera's system contracts allow EVM-based smart contracts to interact directly with HTS tokens. This integration enables smart contracts to manage HTS tokens as if they were standard ERC tokens, facilitating complex interactions and programmability. For example, the Hedera Account Service (HAS) system contract introduces an account proxy to interact with other contracts, enabling functionalities such as HBAR allowances and authorization checks directly within smart contracts. By leveraging system contracts, developers can:
* Hold and manage HTS tokens within smart contracts, just like ERC-20 and ERC-721 tokens.
* Transfer HTS tokens using EVM-based logic, enabling seamless token operations.
* Access Hedera accounts within smart contracts, unlocking new dApp functionalities.
This native integration eliminates the need for custom bridges or complex workarounds, making HTS token management within smart contracts more efficient and developer-friendly.
***
## **HTS vs. Smart Contract Performance Features**
By combining these features, Hedera provides a robust platform for developers to leverage both native token services and EVM-based smart contracts, ensuring scalability, security, and interoperability within the blockchain ecosystem.
Feature
HTS-Native Tokens
Smart Contract Tokens
Hybrid Approach
Transaction Speed
10,000+ TPS
\~350 TPS (gas-limited)
HTS speed with smart contract flexibility
Cost Efficiency
Fixed, low-cost fees
Higher gas costs
HTS transactions remain low-cost
Custom Logic
❌ Limited to built-in controls
✅ Fully programmable
✅ Smart contracts enhance HTS functionality
Compliance Features
✅ KYC, Freeze, Pause, Wipe
❌ Must be custom-coded
✅ Hybrid approach supports compliance via smart contracts
EVM Compatibility
✅ Via Facade Contracts
✅ Standard ERC-20/ERC-721
✅ HTS tokens accessible via smart contracts
For high-frequency transactions, HTS-native tokens provide superior performance and lower costs. For custom business logic, smart contract tokens offer greater flexibility. Hybrid tokenization lets developers leverage both.
# Hedera Account Service
Source: https://docs.hedera.com/evm/hedera-services/system-contracts/account-service
Use the HIP-632 Hedera Account Service system contract, including isAuthorized and isAuthorizedRaw, to verify ECDSA and ED25519 signatures from Solidity.
## 📣 ECRECOVER Support for ECDSA Hedera Accounts
EVM developers should note that `ECRECOVER` natively supports ECDSA accounts on Hedera. Aliases for these accounts, derived using Keccak-256(publicKey), are fully compatible with Ethereum's `ECRECOVER` logic. This enables seamless interaction with ECDSA accounts on Hedera using `ECRECOVER`, just like standard Ethereum accounts.
To verify an ECDSA signature, developers can call `ECRECOVER(messageHash, r, s, v)` in Ethereum smart contracts. If the recovered address matches the alias of a Hedera account, the signer is confirmed to control the account. No special integration is needed—`ECRECOVER` functions as a standard EVM precompiled contract on Hedera
**Note**: This functionality is specific to ECDSA accounts with Keccak-256(publicKey) aliases. For ED25519 accounts, Hedera offers alternative authorization methods, such as `isAuthorized()` or `isAuthorizedRaw()`, to verify control of an account. For details, refer to the [Hedera Account Service System Contract](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/account-service). See below for details on these two precompile functions.
***
## Signature Validation Precompiles (HIP-632)
To extend Hedera’s compatibility with Ethereum’s EVM, [HIP-632](https://hips.hedera.com/hip/hip-632) introduced two new precompile functions for signature validation: `isAuthorizedRaw` and `isAuthorized`. These precompiles enable smart contracts on Hedera to validate both ED25519 and ECDSA (secp256k1) Hedera account signatures, making it easier for developers to build applications that operate across Hedera-native and EVM-based ecosystems.
Additionally, these functions allow developers to verify account authorization directly within smart contracts. This extends the capabilities of Ethereum’s `ECRECOVER` by supporting:
* Simple raw signature verification for both ED25519 and ECDSA keys, similar to how Ethereum's `ECRECOVER` checks the signature against the provided message hash.
* Account authorization verification for ECDSA accounts, confirming that the account associated with the signature is authorized to execute transactions without being restricted to `ECRECOVER`.
* Comprehensive account authorization using protobuf signature maps, which allows for a more complex and flexible account validation process, ensuring compatibility with Hedera's advanced account model. May include multi-sig or threshold key structure.
By addressing these use cases, HIP-632 provides a seamless way for developers to handle authorization and signature validation across both Hedera and EVM-compatible environments.
#### **isAuthorizedRaw(address, messageHash, signatureBlob)**
* Validates whether a given ED25519 or ECDSA signature is valid for a message against the public key associated with the specified account alias.
* Operates similarly to Ethereum's `ECRECOVER` for single key structure but supports both secp256k1 (Ethereum) and ED25519 (Hedera) cryptography.
* Returns `true` if the signature is valid and linked to the account, otherwise `false`.
#### **isAuthorized(address, message, signatureBlob)**
* Extends **isAuthorizedRaw** by validating that the specific Hedera account (referenced by either its EVM Address from Account ID or EVM Address from Public Key) is authorized to execute the transaction.
* Supports Hedera’s complex account key structures, including multi-sig and threshold key requirements.
* Provides a way to confirm authorization based on Hedera’s advanced account-based key management.
* Returns `true` if the signature is valid and linked to the account, otherwise `false`.
Parameter
Description
address
A 20-byte identifier used to represent an account on the Hedera network or an EVM-compatible account.
message
The original plaintext data or payload that the signature is derived from. This is the information that was signed to produce the signature.
messaHash
A cryptographic hash of the message, calculated using an algorithm like SHA-256 or Keccak-256. This is typically what is signed instead of the raw message.
signatureBlob
A concatenation of the digital signature components, typically including r, s, and v values for ECDSA, or the equivalent data for ED25519 signatures.
#### Behavior and Cost
* Both functions return a boolean indicating whether the signature is valid and is authorized for the account.
* These methods incur gas costs proportional to the computational resources required for signature validation, including cryptographic hashing of the message and verifying the signature against the public key.
* The additional gas charge varies depending on the type of cryptographic operation (ED25519 vs. ECDSA) and the size of the associated key structures.
Gas fee schedule and calculation
**Reference**: [HIP-632](https://hips.hedera.com/hip/hip-632)
# Exchange Rate System Contract
Source: https://docs.hedera.com/evm/hedera-services/system-contracts/exchange-rate
Query HBAR/USD exchange rates directly from your smart contracts via the 0x168 system contract.
The exchange rate system contract exposes the network's active HBAR/USD rate to your EVM contracts. If you want to price something in USD but settle in HBAR, this is what you call.
## At a glance
| Field | Value |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| Contract address | `0x168` |
| Reference HIP | [HIP-475](https://hips.hedera.com/hip/hip-475) |
| Solidity source | [hiero-contracts/contracts/exchange-rate](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/exchange-rate) |
| Functions | `tinycentsToTinybars(uint256)`, `tinybarsToTinycents(uint256)` |
The contract is implemented by consensus nodes, so calls are deterministic and don't depend on an off-chain feed. The rate comes from the network's exchange rate file (`0.0.112`), which is the same source the network uses internally to charge transaction fees.
## Why it exists
Hedera fees are quoted in USD but paid in HBAR at the current network rate. The contract gives Solidity code access to the same conversion. If you want to charge a flat 10 cents, you compute the HBAR amount at call time. No hard-coded prices, no oracle fee.
Units to keep straight:
* 1 tinycent = 10⁻⁸ US cents = 10⁻¹⁰ USD
* 1 tinybar = 10⁻⁸ HBAR
## Solidity interface
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.4.9 <0.9.0;
interface IExchangeRate {
// Given a value in tinycents (10^-8 US cents), returns the equivalent
// value in tinybars at the current network exchange rate.
function tinycentsToTinybars(uint256 tinycents) external returns (uint256);
// Given a value in tinybars, returns the equivalent value in tinycents
// at the current network exchange rate.
function tinybarsToTinycents(uint256 tinybars) external returns (uint256);
}
```
## Example: charge a flat USD fee
This contract charges \$0.10 worth of HBAR per call. The HBAR amount is computed against the live rate, so it doesn't matter if HBAR moves.
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;
interface IExchangeRate {
function tinycentsToTinybars(uint256 tinycents) external returns (uint256);
}
contract UsdPricedStore {
// The Exchange Rate system contract address.
address constant EXCHANGE_RATE = address(0x168);
// 10 cents = 10 * 10^8 tinycents = 1_000_000_000.
uint256 constant TEN_CENTS_IN_TINYCENTS = 10 * 10**8;
event Purchased(address indexed buyer, uint256 paidTinybars);
function purchase() external payable {
// Ask the network how many tinybars are equivalent to $0.10 right now.
uint256 requiredTinybars =
IExchangeRate(EXCHANGE_RATE).tinycentsToTinybars(TEN_CENTS_IN_TINYCENTS);
// msg.value is in 18-decimal wei. Convert tinybars (8 decimals) to wei
// by multiplying by 10^10 so the units line up.
uint256 requiredWei = requiredTinybars * 10**10;
require(msg.value >= requiredWei, "insufficient HBAR for $0.10");
emit Purchased(msg.sender, requiredTinybars);
}
}
```
`tinycentsToTinybars` returns tinybars (8 decimals). `msg.value` is in wei (18 decimals). You have to multiply by `10**10` to compare them. This is the most common bug when wiring up the precompile.
## When to reach for it
Useful when you need stable USD pricing on-chain: subscription fees, paywalled functions, auction reserve prices, or a guard that rejects transactions above a USD-equivalent cap. Also useful for any contract that wants to mirror Hedera's own fee model (quote in USD, settle in HBAR) instead of building a Chainlink integration.
## Things to know
The interface marks both functions as non-view, so calls cost gas even though they read state. Budget for that when batching conversions.
The rate updates roughly once an hour from the network exchange rate file. It is not a live market feed; for anything that needs second-by-second tracking, layer an oracle on top.
The rate is what the network uses for fee calculation. It tends to track exchange spot prices closely but isn't guaranteed to match.
## See also
The token-service precompile at `0x167` for creating, minting, and transferring HTS tokens from Solidity.
# Hedera Token Service System Contract
Source: https://docs.hedera.com/evm/hedera-services/system-contracts/hts
Hedera enables the native creation of fungible and non-fungible tokens through its SDKs, eliminating the need for smart contracts. This approach leverages Hedera's core features like high TPS, security, and low latency for an optimized user experience. Additionally, the Hedera Token Service provides a cost-effective method for tokenization. Smart contracts on Hedera can also interact with this service via the Hedera Token Service System contract, offering functionalities like token creation, burning, and minting through the EVM.
Some of the key functions defined in the Hedera Token Service System Contract include:
transferFrom(address from, address to, uint256 tokenId)
#### Example
#### Additional References
# System Smart Contracts
Source: https://docs.hedera.com/evm/hedera-services/system-contracts/index
System smart contracts are Hedera API functionality logic presented at reserved address locations on the EVM network. These addresses contain reserved function selectors. When a deployed contract calls these selectors, they execute as though a corresponding system contract exists on the network. Both system and user-deployed contracts live at the same address. If a contract is redeployed, it gets a new address while the original address retains the old bytecode.
**System Smart Contract Interfaces**
Solidity interfaces provide and define a set of functions that other smart contracts can call. The interfaces for all Hedera systems contracts written in Solidity are maintained in the [`hiero-contracts`](https://github.com/hiero-ledger/hiero-contracts) repository.
**Note:** The following Solidity examples are *not* production-ready and are intended solely for instructional purposes to guide developers.
The following is a list of available system contracts on Hedera:
### ➡ **Exchange Rate**
The exchange rate contract allows you to convert from tinycents to tinybars and from tinybars to tinycents.
| Contract Address | Source |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0x168` | [https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/exchange-rate](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/exchange-rate) |
**Example ⬇**
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.4.9 <0.9.0;
interface IExchangeRate {
// Given a value in tinycents (1e-8 US cents or 1e-10 USD), returns the
// equivalent value in tinybars (1e-8 HBAR) at the current exchange rate
// stored in system file 0.0.112.
//
// This rate is a weighted median of the the recent" HBAR-USD exchange
// rate on major exchanges, but should _not_ be treated as a live price
// oracle! It is important primarily because the network will use it to
// compute the tinybar fees for the active transaction.
//
// So a "self-funding" contract can use this rate to compute how much
// tinybar its users must send to cover the Hedera fees for the transaction.
function tinycentsToTinybars(uint256 tinycents) external returns (uint256);
// Given a value in tinybars (1e-8 HBAR), returns the equivalent value in
// tinycents (1e-8 US cents or 1e-10 USD) at the current exchange rate
// stored in system file 0.0.112.
//
// This rate tracks the the HBAR-USD rate on public exchanges, but
// should _not_ be treated as a live price oracle! This conversion is
// less likely to be needed than the above conversion from tinycent to
// tinybars, but we include it for completeness.
function tinybarsToTinycents(uint256 tinybars) external returns (uint256);
}
```
Reference: [HIP-475](https://hips.hedera.com/hip/hip-475).
### ➡ **Hedera Token Service**
The Hedera Token Service smart contract precompile provides functions to use the native Hedera Token Service in smart contracts. Tokens created using this method can also be managed using the native Hedera Token Service APIs.
| Contract Address | Source |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0x167` | [https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/token-service](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/token-service) |
**Example ⬇**
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.4.9 <0.9.0;
pragma experimental ABIEncoderV2;
interface IHederaTokenService {
/// Transfers cryptocurrency among two or more accounts by making the desired adjustments to their
/// balances. Each transfer list can specify up to 10 adjustments. Each negative amount is withdrawn
/// from the corresponding account (a sender), and each positive one is added to the corresponding
/// account (a receiver). The amounts list must sum to zero. Each amount is a number of tinybars
/// (there are 100,000,000 tinybars in one hbar). If any sender account fails to have sufficient
/// hbars, then the entire transaction fails, and none of those transfers occur, though the
/// transaction fee is still charged. This transaction must be signed by the keys for all the sending
/// accounts, and for any receiving accounts that have receiverSigRequired == true. The signatures
/// are in the same order as the accounts, skipping those accounts that don't need a signature.
/// @custom:version 0.3.0 previous version did not include isApproval
struct AccountAmount {
// The Account ID, as a solidity address, that sends/receives cryptocurrency or tokens
address accountID;
// The amount of the lowest denomination of the given token that
// the account sends(negative) or receives(positive)
int64 amount;
// If true then the transfer is expected to be an approved allowance and the
// accountID is expected to be the owner. The default is false (omitted).
bool isApproval;
}
/// A sender account, a receiver account, and the serial number of an NFT of a Token with
/// NON_FUNGIBLE_UNIQUE type. When minting NFTs the sender will be the default AccountID instance
/// (0.0.0 aka 0x0) and when burning NFTs, the receiver will be the default AccountID instance.
/// @custom:version 0.3.0 previous version did not include isApproval
struct NftTransfer {
// The solidity address of the sender
address senderAccountID;
// The solidity address of the receiver
address receiverAccountID;
// The serial number of the NFT
int64 serialNumber;
// If true then the transfer is expected to be an approved allowance and the
// accountID is expected to be the owner. The default is false (omitted).
bool isApproval;
}
struct TokenTransferList {
// The ID of the token as a solidity address
address token;
// Applicable to tokens of type FUNGIBLE_COMMON. Multiple list of AccountAmounts, each of which
// has an account and amount.
AccountAmount[] transfers;
// Applicable to tokens of type NON_FUNGIBLE_UNIQUE. Multiple list of NftTransfers, each of
// which has a sender and receiver account, including the serial number of the NFT
NftTransfer[] nftTransfers;
}
struct TransferList {
// Multiple list of AccountAmounts, each of which has an account and amount.
// Used to transfer hbars between the accounts in the list.
AccountAmount[] transfers;
}
/// Expiry properties of a Hedera token - second, autoRenewAccount, autoRenewPeriod
struct Expiry {
// The epoch second at which the token should expire; if an auto-renew account and period are
// specified, this is coerced to the current epoch second plus the autoRenewPeriod
int64 second;
// ID of an account which will be automatically charged to renew the token's expiration, at
// autoRenewPeriod interval, expressed as a solidity address
address autoRenewAccount;
// The interval at which the auto-renew account will be charged to extend the token's expiry
int64 autoRenewPeriod;
}
/// A Key can be a public key from either the Ed25519 or ECDSA(secp256k1) signature schemes, where
/// in the ECDSA(secp256k1) case we require the 33-byte compressed form of the public key. We call
/// these public keys primitive keys.
/// A Key can also be the ID of a smart contract instance, which is then authorized to perform any
/// precompiled contract action that requires this key to sign.
/// Note that when a Key is a smart contract ID, it doesn't mean the contract with that ID
/// will actually create a cryptographic signature. It only means that when the contract calls a
/// precompiled contract, the resulting "child transaction" will be authorized to perform any action
/// controlled by the Key.
/// Exactly one of the possible values should be populated in order for the Key to be valid.
struct KeyValue {
// if set to true, the key of the calling Hedera account will be inherited as the token key
bool inheritAccountKey;
// smart contract instance that is authorized as if it had signed with a key
address contractId;
// Ed25519 public key bytes
bytes ed25519;
// Compressed ECDSA(secp256k1) public key bytes
bytes ECDSA_secp256k1;
// A smart contract that, if the recipient of the active message frame, should be treated
// as having signed. (Note this does not mean the code being executed in the frame
// will belong to the given contract, since it could be running another contract's code via
// delegatecall. So setting this key is a more permissive version of setting the
// contractID key, which also requires the code in the active message frame belong to the
// the contract with the given id.)
address delegatableContractId;
}
/// A list of token key types the key should be applied to and the value of the key
struct TokenKey {
// bit field representing the key type. Keys of all types that have corresponding bits set to 1
// will be created for the token.
// 0th bit: adminKey
// 1st bit: kycKey
// 2nd bit: freezeKey
// 3rd bit: wipeKey
// 4th bit: supplyKey
// 5th bit: feeScheduleKey
// 6th bit: pauseKey
// 7th bit: ignored
uint keyType;
// the value that will be set to the key type
KeyValue key;
}
/// Basic properties of a Hedera Token - name, symbol, memo, tokenSupplyType, maxSupply,
/// treasury, freezeDefault. These properties are related both to Fungible and NFT token types.
struct HederaToken {
// The publicly visible name of the token. The token name is specified as a Unicode string.
// Its UTF-8 encoding cannot exceed 100 bytes, and cannot contain the 0 byte (NUL).
string name;
// The publicly visible token symbol. The token symbol is specified as a Unicode string.
// Its UTF-8 encoding cannot exceed 100 bytes, and cannot contain the 0 byte (NUL).
string symbol;
// The ID of the account which will act as a treasury for the token as a solidity address.
// This account will receive the specified initial supply or the newly minted NFTs in
// the case for NON_FUNGIBLE_UNIQUE Type
address treasury;
// The memo associated with the token (UTF-8 encoding max 100 bytes)
string memo;
// IWA compatibility. Specified the token supply type. Defaults to INFINITE
bool tokenSupplyType;
// IWA Compatibility. Depends on TokenSupplyType. For tokens of type FUNGIBLE_COMMON - the
// maximum number of tokens that can be in circulation. For tokens of type NON_FUNGIBLE_UNIQUE -
// the maximum number of NFTs (serial numbers) that can be minted. This field can never be changed!
int64 maxSupply;
// The default Freeze status (frozen or unfrozen) of Hedera accounts relative to this token. If
// true, an account must be unfrozen before it can receive the token
bool freezeDefault;
// list of keys to set to the token
TokenKey[] tokenKeys;
// expiry properties of a Hedera token - second, autoRenewAccount, autoRenewPeriod
Expiry expiry;
}
/// Additional post creation fungible and non fungible properties of a Hedera Token.
struct TokenInfo {
/// Basic properties of a Hedera Token
HederaToken token;
/// The number of tokens (fungible) or serials (non-fungible) of the token
int64 totalSupply;
/// Specifies whether the token is deleted or not
bool deleted;
/// Specifies whether the token kyc was defaulted with KycNotApplicable (true) or Revoked (false)
bool defaultKycStatus;
/// Specifies whether the token is currently paused or not
bool pauseStatus;
/// The fixed fees collected when transferring the token
FixedFee[] fixedFees;
/// The fractional fees collected when transferring the token
FractionalFee[] fractionalFees;
/// The royalty fees collected when transferring the token
RoyaltyFee[] royaltyFees;
/// The ID of the network ledger
string ledgerId;
}
/// Additional fungible properties of a Hedera Token.
struct FungibleTokenInfo {
/// The shared hedera token info
TokenInfo tokenInfo;
/// The number of decimal places a token is divisible by
int32 decimals;
}
/// Additional non fungible properties of a Hedera Token.
struct NonFungibleTokenInfo {
/// The shared hedera token info
TokenInfo tokenInfo;
/// The serial number of the nft
int64 serialNumber;
/// The account id specifying the owner of the non fungible token
address ownerId;
/// The epoch second at which the token was created.
int64 creationTime;
/// The unique metadata of the NFT
bytes metadata;
/// The account id specifying an account that has been granted spending permissions on this nft
address spenderId;
}
/// A fixed number of units (hbar or token) to assess as a fee during a transfer of
/// units of the token to which this fixed fee is attached. The denomination of
/// the fee depends on the values of tokenId, useHbarsForPayment and
/// useCurrentTokenForPayment. Exactly one of the values should be set.
struct FixedFee {
int64 amount;
// Specifies ID of token that should be used for fixed fee denomination
address tokenId;
// Specifies this fixed fee should be denominated in Hbar
bool useHbarsForPayment;
// Specifies this fixed fee should be denominated in the Token currently being created
bool useCurrentTokenForPayment;
// The ID of the account to receive the custom fee, expressed as a solidity address
address feeCollector;
}
/// A fraction of the transferred units of a token to assess as a fee. The amount assessed will never
/// be less than the given minimumAmount, and never greater than the given maximumAmount. The
/// denomination is always units of the token to which this fractional fee is attached.
struct FractionalFee {
// A rational number's numerator, used to set the amount of a value transfer to collect as a custom fee
int64 numerator;
// A rational number's denominator, used to set the amount of a value transfer to collect as a custom fee
int64 denominator;
// The minimum amount to assess
int64 minimumAmount;
// The maximum amount to assess (zero implies no maximum)
int64 maximumAmount;
bool netOfTransfers;
// The ID of the account to receive the custom fee, expressed as a solidity address
address feeCollector;
}
/// A fee to assess during a transfer that changes ownership of an NFT. Defines the fraction of
/// the fungible value exchanged for an NFT that the ledger should collect as a royalty. ("Fungible
/// value" includes both â and units of fungible HTS tokens.) When the NFT sender does not receive
/// any fungible value, the ledger will assess the fallback fee, if present, to the new NFT owner.
/// Royalty fees can only be added to tokens of type type NON_FUNGIBLE_UNIQUE.
struct RoyaltyFee {
// A fraction's numerator of fungible value exchanged for an NFT to collect as royalty
int64 numerator;
// A fraction's denominator of fungible value exchanged for an NFT to collect as royalty
int64 denominator;
// If present, the fee to assess to the NFT receiver when no fungible value
// is exchanged with the sender. Consists of:
// amount: the amount to charge for the fee
// tokenId: Specifies ID of token that should be used for fixed fee denomination
// useHbarsForPayment: Specifies this fee should be denominated in Hbar
int64 amount;
address tokenId;
bool useHbarsForPayment;
// The ID of the account to receive the custom fee, expressed as a solidity address
address feeCollector;
}
/**********************
* Direct HTS Calls *
**********************/
/// Performs transfers among combinations of tokens and hbars
/// @param transferList the list of hbar transfers to do
/// @param tokenTransfers the list of token transfers to do
/// @custom:version 0.3.0 the signature of the previous version was cryptoTransfer(TokenTransferList[] memory tokenTransfers)
function cryptoTransfer(TransferList memory transferList, TokenTransferList[] memory tokenTransfers)
external
returns (int64 responseCode);
/// Mints an amount of the token to the defined treasury account
/// @param token The token for which to mint tokens. If token does not exist, transaction results in
/// INVALID_TOKEN_ID
/// @param amount Applicable to tokens of type FUNGIBLE_COMMON. The amount to mint to the Treasury Account.
/// Amount must be a positive non-zero number represented in the lowest denomination of the
/// token. The new supply must be lower than 2^63.
/// @param metadata Applicable to tokens of type NON_FUNGIBLE_UNIQUE. A list of metadata that are being created.
/// Maximum allowed size of each metadata is 100 bytes
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return newTotalSupply The new supply of tokens. For NFTs it is the total count of NFTs
/// @return serialNumbers If the token is an NFT the newly generate serial numbers, othersise empty.
function mintToken(
address token,
int64 amount,
bytes[] memory metadata
)
external
returns (
int64 responseCode,
int64 newTotalSupply,
int64[] memory serialNumbers
);
/// Burns an amount of the token from the defined treasury account
/// @param token The token for which to burn tokens. If token does not exist, transaction results in
/// INVALID_TOKEN_ID
/// @param amount Applicable to tokens of type FUNGIBLE_COMMON. The amount to burn from the Treasury Account.
/// Amount must be a positive non-zero number, not bigger than the token balance of the treasury
/// account (0; balance], represented in the lowest denomination.
/// @param serialNumbers Applicable to tokens of type NON_FUNGIBLE_UNIQUE. The list of serial numbers to be burned.
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return newTotalSupply The new supply of tokens. For NFTs it is the total count of NFTs
function burnToken(
address token,
int64 amount,
int64[] memory serialNumbers
) external returns (int64 responseCode, int64 newTotalSupply);
/// Associates the provided account with the provided tokens. Must be signed by the provided
/// Account's key or called from the accounts contract key
/// If the provided account is not found, the transaction will resolve to INVALID_ACCOUNT_ID.
/// If the provided account has been deleted, the transaction will resolve to ACCOUNT_DELETED.
/// If any of the provided tokens is not found, the transaction will resolve to INVALID_TOKEN_REF.
/// If any of the provided tokens has been deleted, the transaction will resolve to TOKEN_WAS_DELETED.
/// If an association between the provided account and any of the tokens already exists, the
/// transaction will resolve to TOKEN_ALREADY_ASSOCIATED_TO_ACCOUNT.
/// If the provided account's associations count exceed the constraint of maximum token associations
/// per account, the transaction will resolve to TOKENS_PER_ACCOUNT_LIMIT_EXCEEDED.
/// On success, associations between the provided account and tokens are made and the account is
/// ready to interact with the tokens.
/// @param account The account to be associated with the provided tokens
/// @param tokens The tokens to be associated with the provided account. In the case of NON_FUNGIBLE_UNIQUE
/// Type, once an account is associated, it can hold any number of NFTs (serial numbers) of that
/// token type
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function associateTokens(address account, address[] memory tokens)
external
returns (int64 responseCode);
/// Single-token variant of associateTokens. Will be mapped to a single entry array call of associateTokens
/// @param account The account to be associated with the provided token
/// @param token The token to be associated with the provided account
function associateToken(address account, address token)
external
returns (int64 responseCode);
/// Dissociates the provided account with the provided tokens. Must be signed by the provided
/// Account's key.
/// If the provided account is not found, the transaction will resolve to INVALID_ACCOUNT_ID.
/// If the provided account has been deleted, the transaction will resolve to ACCOUNT_DELETED.
/// If any of the provided tokens is not found, the transaction will resolve to INVALID_TOKEN_REF.
/// If any of the provided tokens has been deleted, the transaction will resolve to TOKEN_WAS_DELETED.
/// If an association between the provided account and any of the tokens does not exist, the
/// transaction will resolve to TOKEN_NOT_ASSOCIATED_TO_ACCOUNT.
/// If a token has not been deleted and has not expired, and the user has a nonzero balance, the
/// transaction will resolve to TRANSACTION_REQUIRES_ZERO_TOKEN_BALANCES.
/// If a fungible token has expired, the user can disassociate even if their token balance is
/// not zero.
/// If a non fungible token has expired, the user can not disassociate if their token
/// balance is not zero. The transaction will resolve to TRANSACTION_REQUIRED_ZERO_TOKEN_BALANCES.
/// On success, associations between the provided account and tokens are removed.
/// @param account The account to be dissociated from the provided tokens
/// @param tokens The tokens to be dissociated from the provided account.
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function dissociateTokens(address account, address[] memory tokens)
external
returns (int64 responseCode);
/// Single-token variant of dissociateTokens. Will be mapped to a single entry array call of dissociateTokens
/// @param account The account to be associated with the provided token
/// @param token The token to be associated with the provided account
function dissociateToken(address account, address token)
external
returns (int64 responseCode);
/// Creates a Fungible Token with the specified properties
/// @param token the basic properties of the token being created
/// @param initialTotalSupply Specifies the initial supply of tokens to be put in circulation. The
/// initial supply is sent to the Treasury Account. The supply is in the lowest denomination possible.
/// @param decimals the number of decimal places a token is divisible by
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenAddress the created token's address
function createFungibleToken(
HederaToken memory token,
int64 initialTotalSupply,
int32 decimals
) external payable returns (int64 responseCode, address tokenAddress);
/// Creates a Fungible Token with the specified properties
/// @param token the basic properties of the token being created
/// @param initialTotalSupply Specifies the initial supply of tokens to be put in circulation. The
/// initial supply is sent to the Treasury Account. The supply is in the lowest denomination possible.
/// @param decimals the number of decimal places a token is divisible by.
/// @param fixedFees list of fixed fees to apply to the token
/// @param fractionalFees list of fractional fees to apply to the token
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenAddress the created token's address
function createFungibleTokenWithCustomFees(
HederaToken memory token,
int64 initialTotalSupply,
int32 decimals,
FixedFee[] memory fixedFees,
FractionalFee[] memory fractionalFees
) external payable returns (int64 responseCode, address tokenAddress);
/// Creates an Non Fungible Unique Token with the specified properties
/// @param token the basic properties of the token being created
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenAddress the created token's address
function createNonFungibleToken(HederaToken memory token)
external
payable
returns (int64 responseCode, address tokenAddress);
/// Creates an Non Fungible Unique Token with the specified properties
/// @param token the basic properties of the token being created
/// @param fixedFees list of fixed fees to apply to the token
/// @param royaltyFees list of royalty fees to apply to the token
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenAddress the created token's address
function createNonFungibleTokenWithCustomFees(
HederaToken memory token,
FixedFee[] memory fixedFees,
RoyaltyFee[] memory royaltyFees
) external payable returns (int64 responseCode, address tokenAddress);
/**********************
* ABIV1 calls *
**********************/
/// Initiates a Fungible Token Transfer
/// @param token The ID of the token as a solidity address
/// @param accountId account to do a transfer to/from
/// @param amount The amount from the accountId at the same index
function transferTokens(
address token,
address[] memory accountId,
int64[] memory amount
) external returns (int64 responseCode);
/// Initiates a Non-Fungable Token Transfer
/// @param token The ID of the token as a solidity address
/// @param sender the sender of an nft
/// @param receiver the receiver of the nft sent by the same index at sender
/// @param serialNumber the serial number of the nft sent by the same index at sender
function transferNFTs(
address token,
address[] memory sender,
address[] memory receiver,
int64[] memory serialNumber
) external returns (int64 responseCode);
/// Transfers tokens where the calling account/contract is implicitly the first entry in the token transfer list,
/// where the amount is the value needed to zero balance the transfers. Regular signing rules apply for sending
/// (positive amount) or receiving (negative amount)
/// @param token The token to transfer to/from
/// @param sender The sender for the transaction
/// @param recipient The receiver of the transaction
/// @param amount Non-negative value to send. a negative value will result in a failure.
function transferToken(
address token,
address sender,
address recipient,
int64 amount
) external returns (int64 responseCode);
/// Transfers tokens where the calling account/contract is implicitly the first entry in the token transfer list,
/// where the amount is the value needed to zero balance the transfers. Regular signing rules apply for sending
/// (positive amount) or receiving (negative amount)
/// @param token The token to transfer to/from
/// @param sender The sender for the transaction
/// @param recipient The receiver of the transaction
/// @param serialNumber The serial number of the NFT to transfer.
function transferNFT(
address token,
address sender,
address recipient,
int64 serialNumber
) external returns (int64 responseCode);
/// Allows spender to withdraw from your account multiple times, up to the value amount. If this function is called
/// again it overwrites the current allowance with value.
/// Only Applicable to Fungible Tokens
/// @param token The hedera token address to approve
/// @param spender the account address authorized to spend
/// @param amount the amount of tokens authorized to spend.
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function approve(
address token,
address spender,
uint256 amount
) external returns (int64 responseCode);
/// Transfers `amount` tokens from `from` to `to` using the
// allowance mechanism. `amount` is then deducted from the caller's allowance.
/// Only applicable to fungible tokens
/// @param token The address of the fungible Hedera token to transfer
/// @param from The account address of the owner of the token, on the behalf of which to transfer `amount` tokens
/// @param to The account address of the receiver of the `amount` tokens
/// @param amount The amount of tokens to transfer from `from` to `to`
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function transferFrom(address token, address from, address to, uint256 amount) external returns (int64 responseCode);
/// Returns the amount which spender is still allowed to withdraw from owner.
/// Only Applicable to Fungible Tokens
/// @param token The Hedera token address to check the allowance of
/// @param owner the owner of the tokens to be spent
/// @param spender the spender of the tokens
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return allowance The amount which spender is still allowed to withdraw from owner.
function allowance(
address token,
address owner,
address spender
) external returns (int64 responseCode, uint256 allowance);
/// Allow or reaffirm the approved address to transfer an NFT the approved address does not own.
/// Only Applicable to NFT Tokens
/// @param token The Hedera NFT token address to approve
/// @param approved The new approved NFT controller. To revoke approvals pass in the zero address.
/// @param serialNumber The NFT serial number to approve
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function approveNFT(
address token,
address approved,
uint256 serialNumber
) external returns (int64 responseCode);
/// Transfers `serialNumber` of `token` from `from` to `to` using the allowance mechanism.
/// Only applicable to NFT tokens
/// @param token The address of the non-fungible Hedera token to transfer
/// @param from The account address of the owner of `serialNumber` of `token`
/// @param to The account address of the receiver of `serialNumber`
/// @param serialNumber The NFT serial number to transfer
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function transferFromNFT(address token, address from, address to, uint256 serialNumber) external returns (int64 responseCode);
/// Get the approved address for a single NFT
/// Only Applicable to NFT Tokens
/// @param token The Hedera NFT token address to check approval
/// @param serialNumber The NFT to find the approved address for
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return approved The approved address for this NFT, or the zero address if there is none
function getApproved(address token, uint256 serialNumber)
external
returns (int64 responseCode, address approved);
/// Enable or disable approval for a third party ("operator") to manage
/// all of `msg.sender`'s assets
/// @param token The Hedera NFT token address to approve
/// @param operator Address to add to the set of authorized operators
/// @param approved True if the operator is approved, false to revoke approval
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function setApprovalForAll(
address token,
address operator,
bool approved
) external returns (int64 responseCode);
/// Query if an address is an authorized operator for another address
/// Only Applicable to NFT Tokens
/// @param token The Hedera NFT token address to approve
/// @param owner The address that owns the NFTs
/// @param operator The address that acts on behalf of the owner
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return approved True if `operator` is an approved operator for `owner`, false otherwise
function isApprovedForAll(
address token,
address owner,
address operator
) external returns (int64 responseCode, bool approved);
/// Query if token account is frozen
/// @param token The token address to check
/// @param account The account address associated with the token
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return frozen True if `account` is frozen for `token`
function isFrozen(address token, address account)
external
returns (int64 responseCode, bool frozen);
/// Query if token account has kyc granted
/// @param token The token address to check
/// @param account The account address associated with the token
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return kycGranted True if `account` has kyc granted for `token`
function isKyc(address token, address account)
external
returns (int64 responseCode, bool kycGranted);
/// Operation to delete token
/// @param token The token address to be deleted
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function deleteToken(address token) external returns (int64 responseCode);
/// Query token custom fees
/// @param token The token address to check
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return fixedFees Set of fixed fees for `token`
/// @return fractionalFees Set of fractional fees for `token`
/// @return royaltyFees Set of royalty fees for `token`
function getTokenCustomFees(address token)
external
returns (int64 responseCode, FixedFee[] memory fixedFees, FractionalFee[] memory fractionalFees, RoyaltyFee[] memory royaltyFees);
/// Query token default freeze status
/// @param token The token address to check
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return defaultFreezeStatus True if `token` default freeze status is frozen.
function getTokenDefaultFreezeStatus(address token)
external
returns (int64 responseCode, bool defaultFreezeStatus);
/// Query token default kyc status
/// @param token The token address to check
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return defaultKycStatus True if `token` default kyc status is KycNotApplicable and false if Revoked.
function getTokenDefaultKycStatus(address token)
external
returns (int64 responseCode, bool defaultKycStatus);
/// Query token expiry info
/// @param token The token address to check
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return expiry Expiry info for `token`
function getTokenExpiryInfo(address token)
external
returns (int64 responseCode, Expiry memory expiry);
/// Query fungible token info
/// @param token The token address to check
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return fungibleTokenInfo FungibleTokenInfo info for `token`
function getFungibleTokenInfo(address token)
external
returns (int64 responseCode, FungibleTokenInfo memory fungibleTokenInfo);
/// Query token info
/// @param token The token address to check
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenInfo TokenInfo info for `token`
function getTokenInfo(address token)
external
returns (int64 responseCode, TokenInfo memory tokenInfo);
/// Query token KeyValue
/// @param token The token address to check
/// @param keyType The keyType of the desired KeyValue
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return key KeyValue info for key of type `keyType`
function getTokenKey(address token, uint keyType)
external
returns (int64 responseCode, KeyValue memory key);
/// Query non fungible token info
/// @param token The token address to check
/// @param serialNumber The NFT serialNumber to check
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return nonFungibleTokenInfo NonFungibleTokenInfo info for `token` `serialNumber`
function getNonFungibleTokenInfo(address token, int64 serialNumber)
external
returns (int64 responseCode, NonFungibleTokenInfo memory nonFungibleTokenInfo);
/// Operation to freeze token account
/// @param token The token address
/// @param account The account address to be frozen
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function freezeToken(address token, address account)
external
returns (int64 responseCode);
/// Operation to unfreeze token account
/// @param token The token address
/// @param account The account address to be unfrozen
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function unfreezeToken(address token, address account)
external
returns (int64 responseCode);
/// Operation to grant kyc to token account
/// @param token The token address
/// @param account The account address to grant kyc
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function grantTokenKyc(address token, address account)
external
returns (int64 responseCode);
/// Operation to revoke kyc to token account
/// @param token The token address
/// @param account The account address to revoke kyc
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function revokeTokenKyc(address token, address account)
external
returns (int64 responseCode);
/// Operation to pause token
/// @param token The token address to be paused
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function pauseToken(address token) external returns (int64 responseCode);
/// Operation to unpause token
/// @param token The token address to be unpaused
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function unpauseToken(address token) external returns (int64 responseCode);
/// Operation to wipe fungible tokens from account
/// @param token The token address
/// @param account The account address to revoke kyc
/// @param amount The number of tokens to wipe
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function wipeTokenAccount(
address token,
address account,
int64 amount
) external returns (int64 responseCode);
/// Operation to wipe non fungible tokens from account
/// @param token The token address
/// @param account The account address to revoke kyc
/// @param serialNumbers The serial numbers of token to wipe
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function wipeTokenAccountNFT(
address token,
address account,
int64[] memory serialNumbers
) external returns (int64 responseCode);
/// Operation to update token info
/// @param token The token address
/// @param tokenInfo The hedera token info to update token with
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function updateTokenInfo(address token, HederaToken memory tokenInfo)
external
returns (int64 responseCode);
/// Operation to update token expiry info
/// @param token The token address
/// @param expiryInfo The hedera token expiry info
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function updateTokenExpiryInfo(address token, Expiry memory expiryInfo)
external
returns (int64 responseCode);
/// Operation to update token expiry info
/// @param token The token address
/// @param keys The token keys
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function updateTokenKeys(address token, TokenKey[] memory keys)
external
returns (int64 responseCode);
/// Query if valid token found for the given address
/// @param token The token address
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return isToken True if valid token found for the given address
function isToken(address token)
external returns
(int64 responseCode, bool isToken);
/// Query to return the token type for a given address
/// @param token The token address
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return tokenType the token type. 0 is FUNGIBLE_COMMON, 1 is NON_FUNGIBLE_UNIQUE, -1 is UNRECOGNIZED
function getTokenType(address token)
external returns
(int64 responseCode, int32 tokenType);
/// Initiates a Redirect For Token
/// @param token The token address
/// @param encodedFunctionSelector The function selector from the ERC20 interface + the bytes input for the function called
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return response The result of the call that had been encoded and sent for execution.
function redirectForToken(address token, bytes memory encodedFunctionSelector) external returns (int64 responseCode, bytes memory response);
}
```
Reference: [HIP-358](https://hips.hedera.com/hip/hip-358), [HIP-206](https://hips.hedera.com/hip/hip-206), [HIP-376](https://hips.hedera.com/hip/hip-376), [HIP-514](https://hips.hedera.com/hip/hip-514), [HIP-719](https://hips.hedera.com/hip/hip-719).
### ➡ [**Hedera Account Service**](/evm/hedera-services/system-contracts/account-service)
The Hedera Account Service contract provides functions to interact with the Hedera network to manage HBAR allowances.
| Contract Address | Source |
| ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0x16a` | [https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/account-service](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/account-service) |
#### Example ⬇
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.4.9 <0.9.0;
pragma experimental ABIEncoderV2;
interface IHederaAccountService {
/// Returns the amount of hbars that the spender has been authorized to spend on behalf of the owner.
/// @param owner The account that has authorized the spender
/// @param spender The account that has been authorized by the owner
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return amount The amount of hbar that the spender has been authorized to spend on behalf of the owner.
function hbarAllowance(address owner, address spender)
external
returns (int64 responseCode, int256 amount);
/// Allows spender to withdraw hbars from the owner account multiple times, up to the value amount. If this function is called
/// again it overwrites the current allowance with the new amount.
/// @param owner The owner of the hbars
/// @param spender the account address authorized to spend
/// @param amount the amount of hbars authorized to spend.
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function hbarApprove(
address owner,
address spender,
int256 amount
) external returns (int64 responseCode);
}
```
Reference: [HIP-906](https://hips.hedera.com/hip/hip-906), [HIP-632](https://hips.hedera.com/hip/hip-632).
### **➡** [**Hedera Schedule Service**](/evm/hedera-services/system-contracts/schedule-service)
The Hedera Schedule Service (HSS) system contract exposes functions that enable smart contracts to interact with Hedera's native schedule service. It allows for scheduling native transactions (like token creation via [HIP-756](https://hips.hedera.com/hip/hip-756)) and, with the introduction of [**HIP-1215**](https://hips.hedera.com/hip/hip-1215), supports generalized scheduled contract calls, allowing smart contracts to schedule arbitrary calls to other contracts (or themselves) directly from within the EVM.
| Contract Address | Source |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0x16b` | [https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/schedule-service](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/schedule-service) |
#### Example ⬇
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.4.9 <0.9.0;
pragma experimental ABIEncoderV2;
import "../hedera-token-service/IHederaTokenService.sol";
interface IHederaScheduleService {
/// Authorizes the calling contract as a signer to the schedule transaction.
/// @param schedule the address of the schedule transaction.
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function authorizeSchedule(address schedule) external returns (int64 responseCode);
/// Allows for the signing of a schedule transaction given a protobuf encoded signature map
/// The message signed by the keys is defined to be the concatenation of the shard, realm, and schedule transaction ID.
/// @param schedule the address of the schedule transaction.
/// @param signatureMap the protobuf encoded signature map
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
function signSchedule(address schedule, bytes memory signatureMap) external returns (int64 responseCode);
/// Allows for the creation of a schedule transaction for a given system contract address, abi encoded call data and payer address
/// Currently supports the Hedera Token Service System Contract (0x167) with encoded call data for
/// createFungibleToken, createNonFungibleToken, createFungibleTokenWithCustomFees, createNonFungibleTokenWithCustomFees
/// and updateToken functions
/// @param systemContractAddress the address of the system contract from which to create the schedule transaction
/// @param callData the abi encoded call data for the system contract function
/// @param payer the address of the account that will pay for the schedule transaction
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return scheduleAddress The address of the newly created schedule transaction.
function scheduleNative(address systemContractAddress, bytes memory callData, address payer) external returns (int64 responseCode, address scheduleAddress);
/// Returns the token information for a scheduled fungible token create transaction
/// @param scheduleAddress the address of the schedule transaction
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return fungibleTokenInfo The token information for the scheduled fungible token create transaction
function getScheduledCreateFungibleTokenInfo(address scheduleAddress) external returns (int64 responseCode, IHederaTokenService.FungibleTokenInfo memory fungibleTokenInfo);
/// Returns the token information for a scheduled non fungible token create transaction
/// @param scheduleAddress the address of the schedule transaction
/// @return responseCode The response code for the status of the request. SUCCESS is 22.
/// @return nonFungibleTokenInfo The token information for the scheduled non fungible token create transaction
function getScheduledCreateNonFungibleTokenInfo(address scheduleAddress) external returns (int64 responseCode, IHederaTokenService.NonFungibleTokenInfo memory nonFungibleTokenInfo);
}
```
Reference: [HIP-1215](https://hips.hedera.com/hip/hip-1215), [HIP-756](https://hips.hedera.com/hip/hip-756), [HIP-755](https://hips.hedera.com/hip/hip-755).
### ➡ **Pseudo Random Number Generator (PRNG)**
The `PRNG` system contract allows you to generate a pseudo-random number that can be used in smart contracts.
| Contract Address | Source |
| ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0x169` | [https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/prng](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/prng) |
**Example ⬇**
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.4.9 <0.9.0;
interface IPrngSystemContract {
// Generates a 256-bit pseudorandom seed using the first 256-bits of running hash of n-3 transaction record.
// Users can generate a pseudorandom number in a specified range using the seed by (integer value of seed % range)
function getPseudorandomSeed() external returns (bytes32);
}
```
Reference: [HIP-351](https://hips.hedera.com/hip/hip-351).
# Hedera Schedule Service
Source: https://docs.hedera.com/evm/hedera-services/system-contracts/schedule-service
## Overview
The **Hedera Schedule Service (HSS) system contract** exposes functions that enable smart contracts to interact with Hedera's native schedule service. The schedule service allows transactions to be scheduled for future execution, simplifies multi-sig coordination, and automates execution once all required signatures are collected. This eliminates the need for off-ledger signature coordination and reduces the complexity of multi-sig workflows in decentralized applications (dApps).
Additionally, the HSS includes expiration handling, where scheduled transactions that fail to collect and verify all required signatures within the specified expiration window are automatically removed from the network.
The `IHederaScheduleService` interface, introduced in [HIP-755](https://hips.hedera.com/hip/hip-755), allows accounts to interact with the schedule transaction service via smart contracts. With the introduction of [**HIP-1215**](https://hips.hedera.com/hip/hip-1215), the HSS now supports generalized scheduled contract calls, allowing smart contracts to schedule arbitrary calls to other contracts (or themselves) directly from within the EVM. This powerful feature enables a wide range of decentralized automation use cases, such as:
* **DeFi Automation**: Automatically rebalancing a portfolio or harvesting yield farming rewards.
* **Vesting Schedules**: Creating token vesting contracts that automatically release tokens at specified intervals.
* **DAO Operations**: Scheduling recurring governance tasks, such as distributing rewards or executing proposals.
Following system contract conventions, HSS is callable at the reserved `0x16b` address and exposes the following functions callable within smart contracts.
#### **authorizeSchedule(address)**
* Signs the schedule transaction identified by the pass-in parameter (`address`) with a `ContractKey` using the format `0.0.`.
* Allows contracts to sign schedule transactions using their own contract key.
* Returns a `responseCode` indicating the success or failure of the authorization attempt.
#### **signSchedule(address schedule, bytes memory signatureMap)**
* Allows for the signing of a schedule transaction given a protobuf encoded signature map.
* The message signed by the keys is defined to be the concatenation of the shard, realm, and schedule transaction address.
* Returns a `responseCode` indicating the success or failure of the signature addition attempt.
#### **scheduleNative(address systemContractAddress, bytes memory callData, address payer)**
* Allows for the creation of a schedule transaction for a given system contract address, ABI-encoded call data, and payer address.
* Currently supports the Hedera Token Service System Contract (`0x167`) for token-related functions.
* Returns the `scheduleAddress` of the newly created scheduled transaction and a `responseCode`.
#### **getScheduledCreateFungibleTokenInfo(address scheduleAddress)**
* Returns the token information for a scheduled fungible token create transaction.
* `scheduleAddress`: The address of the scheduled fungible token create transaction.
* Returns a `responseCode` and an `IHederaTokenService.FungibleTokenInfo` struct.
#### **getScheduledCreateNonFungibleTokenInfo(address scheduleAddress)**
* Returns the token information for a scheduled non-fungible token create transaction.
* `scheduleAddress`: The address of the scheduled non-fungible token create transaction.
* Returns a `responseCode` and an `IHederaTokenService.NonFungibleTokenInfo` struct.
#### **scheduleCall(address to, uint256 expirySecond, uint256 gasLimit, uint64 value, bytes memory callData)**
* Schedules a contract call with the calling contract acting as the payer.
* `to`: The address of the contract to call.
* `expirySecond`: The epoch second at which the transaction should expire.
* `gasLimit`: The maximum amount of gas to use for the call.
* `value`: The amount of HBAR (in tinybars) to send with the call.
* `callData`: The ABI-encoded data for the function call.
* Returns a `responseCode` indicating success or failure (`22` for SUCCESS) and the `scheduleAddress` of the newly created scheduled transaction.
#### **scheduleCallWithPayer(address to, address payer, uint256 expirySecond, uint256 gasLimit, uint64 value, bytes memory callData)**
* Schedules a contract call with a specified payer address. This method collects the required signatures but **only executes the transaction at the `expirySecond` timestamp**, even if all signatures are gathered earlier. The payer must provide signatures before the transaction can execute.
* `to`: The address of the contract to call.
* `payer`: The address of the account that will pay for the transaction.
* `expirySecond`: The epoch second at which the transaction should expire.
* `gasLimit`: The maximum amount of gas to use for the call.
* `value`: The amount of HBAR (in tinybars) to send with the call.
* `callData`: The ABI-encoded data for the function call.
* Returns a `responseCode` indicating success or failure (`22` for SUCCESS) and the `scheduleAddress` of the newly created scheduled transaction.
#### **executeCallOnPayerSignature(address to, address payer, uint256 expirySecond, uint256 gasLimit, uint64 value, bytes memory callData)**
* Schedules and executes a contract call immediately upon receiving the payer's signature. This method also collects signatures, but **executes the transaction immediately once all required signatures are present**, without waiting for the `expirySecond` timestamp, unless the consensus time has already passed the `expirySecond`.
* `to`: The address of the contract to call.
* `payer`: The address of the account that will pay for the transaction.
* `expirySecond`: The epoch second at which the transaction should expire.
* `gasLimit`: The maximum amount of gas to use for the call.
* `value`: The amount of HBAR (in tinybars) to send with the call.
* `callData`: The ABI-encoded data for the function call.
* Returns a `responseCode` indicating success or failure (`22` for SUCCESS) and the `scheduleAddress` of the newly created scheduled transaction.
#### **deleteSchedule(address scheduleAddress)**
* Deletes a previously scheduled transaction.
* `scheduleAddress`: The address of the scheduled transaction to delete.
* Returns a `responseCode` indicating success or failure (`22` for SUCCESS).
#### **deleteSchedule()**
* Deletes a scheduled transaction by calling this parameter-less redirect function directly on the schedule's address.
* This provides a convenient alternative for contracts or Externally Owned Accounts (EOAs) to the main `deleteSchedule(address)` function.
* Returns a `responseCode` indicating success or failure (`22` for SUCCESS).
#### **hasScheduleCapacity(uint256 expirySecond, uint256 gasLimit)**
* A view function that checks if there is enough capacity on the network to schedule a contract call at a given time with a specified gas limit.
* `expirySecond`: The epoch second to check for capacity.
* `gasLimit`: The gas limit of the call to check for capacity.
* Returns `true` if there is capacity, `false` otherwise.
* For a reliable retry pattern, consider implementing a method similar to `findAvailableSecond()` as described in HIP-1215.
## Behavior and Costs
#### Behavior
* Scheduled transactions must collect all required signatures before they can be executed. These signatures can be added asynchronously using the `signSchedule` function.
* If all required signatures are not received within the specified expiration window, the transaction expires and is removed from the network.
* The execution of a schedule transaction occurs automatically when the final required signature is submitted.
* **Throttling and Capacity:** The main design concern for scheduled contract calls is managing network capacity. The `hasScheduleCapacity()` view function allows contracts to check if a given second has capacity to schedule a contract call with a specified gas limit. This enables contracts to find an acceptable second for execution with an affordable gas budget.
* **Return Values:** The new `scheduleCall` functions do not revert. On success, they return the address of the newly created scheduled transaction and a `SUCCESS` (22) response code. On failure, they return a zero address and a failure response code from the `ResponseCodeEnum`.
#### Costs
* Schedule transaction fees are the same as a HAPI sign schedule transaction, with a 20% markup for using system contracts.
* Fees include gas costs for EVM execution, storage costs, and network fees for consensus.
* Expired transactions cost no additional fees beyond the initial scheduling and signature costs.
Gas fee schedule and calculation
### **Reference**
* [HIP-1215: Generalized Scheduled Contract Calls](https://hips.hedera.com/hip/hip-1215)
* [HIP-756: Contract Scheduled Token Create](https://hips.hedera.com/hip/hip-756)
* [HIP-755: Schedule Service System Contract](https://hips.hedera.com/hip/hip-755)
# EVM Developers
Source: https://docs.hedera.com/evm/index
Deploy Solidity smart contracts on Hedera using MetaMask, Hardhat, Foundry, and the JSON-RPC relay, your existing EVM workflow.
# Chainlink CCIP
Source: https://docs.hedera.com/evm/integrations/cross-chain/chainlink-ccip
Learn how Chainlink’s Cross-Chain Interoperability Protocol (CCIP) integrates with Hedera to enable secure cross-chain messaging and token transfers.
## Chainlink CCIP on Hedera
The **Chainlink Cross-Chain Interoperability Protocol (CCIP)** is a standard for blockchain interoperability that enables developers to build **secure cross-chain applications** capable of transferring tokens, sending messages, and executing actions across multiple blockchains.
Through the **Cross-Chain Token (CCT)** standard, CCIP allows token developers to integrate new or existing tokens in a **self-serve** way—without vendor lock-in or restrictive dependencies.
**CCTs support:**
* Self-serve deployments with full control and ownership
* Zero-slippage transfers across supported chains
* Enhanced programmability via configurable rate limits
* Smart Execution for reliable cross-chain delivery
CCIP is powered by **Chainlink Decentralized Oracle Networks (DONs)**—infrastructure that has secured **tens of billions of dollars** and enabled **over \$21 trillion** in on-chain transaction value.
***
## Overview of CCIP and HTS Compatibility
On Hedera, CCIP extends interoperability between **Hedera’s Hashgraph network** and **EVM-compatible blockchains** such as Ethereum.\
It integrates seamlessly with both the **Hedera Token Service (HTS)** and standard **EVM tokens (ERC-20 and ERC-721)**, enabling developers to bridge tokens, data, and logic across ecosystems.
With CCIP, developers can:
* Send and receive arbitrary messages between chains
* Execute programmable cross-chain logic via Chainlink’s oracle network
* Use **LINK**, **Wrapped HBAR (WHBAR)**, or **ETH** as fee tokens
* Prepare for **Cross-Chain Token (CCT)** transfers once fully enabled on Hedera
**Note:** Cross-Chain Token (CCT) transfers on Hedera are currently in
progress on testnet. Current demos focus on message passing between **Hedera
Testnet** and **Ethereum Sepolia**.
***
## Getting Started with CCIP on Hedera
To explore CCIP functionality, start with the [**Hedera CCIP Demo Repository**](https://github.com/mgarbs/hedera-ccip-demos), which showcases **bi-directional cross-chain messaging** between the **Hedera Testnet** and **Ethereum Sepolia** using the Chainlink CCIP JavaScript SDK.
Explore Chainlink CCIP cross-chain message passing between Hedera and Ethereum Sepolia.
Learn how to integrate CCIP in your JavaScript dApps.
Deploy and manage Cross-Chain Tokens (CCTs) with a no-code interface.
***
### Setup
1. **Install dependencies**
```bash theme={null}
pnpm install
```
2. **Configure your environment**
```bash theme={null}
cp .env.example .env
```
Add your Hedera Testnet private key:
```
PRIVATE_KEY=0x...
```
3. **Run a demo**
```bash theme={null}
pnpm run demo:hedera-sepolia-link
```
***
## Available Demos
| Direction | Payment | Command | Description |
| ---------------- | ------- | ------------------------------------ | ------------------------------------------------------ |
| Read-only | — | `pnpm run demo:readonly` | Query CCIP configuration without sending transactions. |
| Hedera → Sepolia | LINK | `pnpm run demo:hedera-sepolia-link` | Send a message using LINK for fees. |
| Hedera → Sepolia | WHBAR | `pnpm run demo:hedera-sepolia-whbar` | Send a message using Wrapped HBAR for fees. |
| Sepolia → Hedera | ETH | `pnpm run demo:sepolia-hedera` | Send a message using ETH for fees. |
💡 **Tip:** Wrap HBAR before running WHBAR examples: `bash pnpm run
wrap-hbar ` All demos operate on **testnet** and are for educational
purposes only. They are **not audited** and should not be used in production.
***
## Network Configuration
### Hedera Testnet
| Parameter | Value |
| ------------------ | -------------------------------------------- |
| **Network Name** | Hedera Testnet |
| **RPC Endpoint** | `https://testnet.hashio.io/api` |
| **Chain ID** | `296` |
| **CCIP Router** | `0x802C5F84eAD128Ff36fD6a3f8a418e339f467Ce4` |
| **Chain Selector** | `222782988166878823` |
[**Add Hedera Testnet**](https://chainlist.org/chain/296)
***
### Ethereum Sepolia
| Parameter | Value |
| ------------------ | -------------------------------------------- |
| **Network Name** | Ethereum Sepolia |
| **Chain ID** | `11155111` |
| **CCIP Router** | `0x0BF3dE8c5D3e8A2B34D2BEeB17ABfCeBaf363A59` |
| **Chain Selector** | `16015286601757825753` |
[**Add Ethereum Sepolia**](https://chainlist.org/chain/11155111)
***
## Token Addresses
| Network | Token | Address |
| -------------------- | ----- | -------------------------------------------- |
| **Hedera Testnet** | LINK | `0x90a386d59b9A6a4795a011e8f032Fc21ED6FEFb6` |
| **Hedera Testnet** | WHBAR | `0xb1F616b8134F602c3Bb465fB5b5e6565cCAd37Ed` |
| **Ethereum Sepolia** | LINK | `0x779877A7B0D9E8603169DdbD7836e478b4624789` |
⚠️ **Note:** WHBAR uses **8 decimals**, while most EVM tokens use **18
decimals**.
***
## Developer Considerations
### Decimal Precision
HBAR’s smallest unit is the **tinybar (8 decimals)**.
Since CCIP on EVM networks operates with **18-decimal precision**, conversions between tinybars and wei-based values may be required.
See [Understanding Hedera’s EVM Differences and Compatibility](/evm/differences).
### Cross-Chain Fees
Ensure your wallet holds sufficient **LINK**, **WHBAR**, or **ETH** to cover CCIP transaction fees.
### Message Timing
Cross-chain message delivery on testnet may take several minutes while the Chainlink oracle network finalizes execution.
***
## Test Tokens and Faucets
| Network | Token | Faucet |
| ------- | ----- | -------------------------------------------------------------------- |
| Hedera | HBAR | [Hedera Faucet](https://portal.hedera.com/faucet) |
| Hedera | LINK | [Chainlink Hedera Faucet](https://faucets.chain.link/hedera-testnet) |
| Sepolia | ETH | [Chainlink Sepolia Faucet](https://faucets.chain.link/sepolia) |
***
## Additional Resources
* [**CCIP Documentation**](https://docs.chain.link/ccip)
* [**CCIP Token Manager**](https://tokenmanager.chain.link/)
* [**CCIP SDK (JavaScript)**](https://docs.chain.link/ccip/ccip-javascript-sdk)
* [**Hedera CCIP Demo Repository**](https://github.com/mgarbs/hedera-ccip-demos)
* [**HashScan Explorer**](https://hashscan.io/testnet)
* [**Chainlink Faucets**](https://faucets.chain.link/)
***
## Summary
**Chainlink CCIP** establishes a universal standard for **secure, decentralized cross-chain interoperability**.
It connects Hedera to a broader multi-chain ecosystem—enabling assets, logic, and state to move across blockchains through a unified protocol.
On Hedera, CCIP provides a **modular, future-ready foundation** for interoperability, supporting **HTS and EVM token compatibility** under the **CCT standard**.
This complements other interoperability solutions such as [**LayerZero**](/evm/integrations/cross-chain/layerzero) and **Hashport**, positioning Hedera as a key network in the interoperable web3 ecosystem.
# Interoperability and Bridging
Source: https://docs.hedera.com/evm/integrations/cross-chain/index
Explore Hedera's interoperability and bridging options: cross-chain messaging protocols like Chainlink CCIP and LayerZero, and ready-to-use bridged assets like USDT0.
Interoperability and bridging let Hedera applications communicate and move assets across other networks so a dApp is not confined to a single chain. These integrations fall into two broad groups:
* **Cross-chain messaging protocols** that carry arbitrary data and token transfers between chains, such as [Chainlink CCIP](/evm/integrations/cross-chain/chainlink-ccip) and [LayerZero](/evm/integrations/cross-chain/layerzero).
* **Ready-to-use bridged assets** that already span multiple chains, such as [USDT0](/evm/integrations/cross-chain/usdt0), the omnichain deployment of Tether's USDT.
Programmable cross-chain messaging and token transfers with built-in risk controls.
Omnichain messaging and token bridging via the OApp and OFT standards.
Omnichain USDT on Hedera, built on LayerZero's OFT standard.
## Choosing an integration
Match the integration to what you are trying to do, not the other way around.
| Integration | Best for | You deploy a contract? | Under the hood |
| -------------------------------------------------------------- | ------------------------------------------------------------------------- | ----------------------------------------------------------- | --------------------------------------------- |
| [Chainlink CCIP](/evm/integrations/cross-chain/chainlink-ccip) | Programmable cross-chain messaging and token transfers with risk controls | Yes, integrate the CCIP router and receiver | CCIP messaging plus supported token transfers |
| [LayerZero](/evm/integrations/cross-chain/layerzero) | Sending arbitrary messages or bridging your own token across chains | Yes, an OApp/OFT contract (or HTS Connector for HTS tokens) | OFT, ONFT, OFT Adapter, HTS Connector |
| [USDT0](/evm/integrations/cross-chain/usdt0) | Using a dollar stablecoin that already exists on Hedera and other chains | No, the token is already deployed | HTS token; LayerZero OFT |
# LayerZero
Source: https://docs.hedera.com/evm/integrations/cross-chain/layerzero
Learn how LayerZero's omnichain messaging and Omnichain Fungible Token (OFT) standard integrate with Hedera to bridge HTS and ERC tokens across chains.
## What is LayerZero?
LayerZero is an omnichannel interoperability protocol designed to facilitate cross-chain communication. By enabling secure and efficient message passing across chains, LayerZero allows developers to build [decentralized applications (dApps)](/support/glossary#decentralized-application-dapp) that operate cohesively over multiple blockchains. This capability enhances the functionality and user experience of dApps by leveraging the unique features of various ecosystems.
***
## Overview of LayerZero and HTS Compatibility
LayerZero integrates seamlessly with the Hedera Token Service (HTS) and EVM-compatible tokens (ERC-20 and ERC-721). This integration enables efficient cross-chain communication for various token types, making it easier to bridge assets such as $USDC or $Sauce between Hedera and other networks.
***
## Getting Started with LayerZero on Hedera
To get started quickly, you can begin with the Gitpod demo, which requires no environment setup. Alternatively, you can go directly into the [LayerZero Quickstart series](https://docs.layerzero.network/v2/developers/evm/getting-started). These guides provide an overview of deploying an Omnichain Application (OApp) on Hedera and other EVM-compatible networks, covering essentials like setting up your LayerZero environment and deploying contracts for cross-chain messaging.
A no-setup-required demo to experience LayerZero
A step-by-step guide for deploying an omnichain application.
Explore examples for bridging tokens with LayerZero.
***
## LayerZero Examples and Key Components
All examples in the demo repo are exploratory code and have NOT been audited. Please use it at your own risk!
### Omnichain Fungible Token (OFT) / Omnichain Non-Fungible Token (ONFT)
The OFT and ONFT token standards allow the transfer of fungible (ERC-20s) and non-fungible tokens (ERC-721s) across chains using LayerZero's messaging infrastructure. If you're bridging an existing fungible ERC token on Hedera to another EVM chain, you can deploy the [**OFT**](https://github.com/hashgraph/hedera-smart-contracts/tree/main/lib/layer-zero#oft) standard contract and the [**ONFT**](https://github.com/hashgraph/hedera-smart-contracts/tree/main/lib/layer-zero#onft) standard contract for NFTs.
#### **Use Case**
Allows fungible and non-fungible tokens to be transferred across chains.
**Example Contracts**
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {OFT} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFT.sol";
contract ExampleOFT is OFT {
uint8 decimalsArg = 8;
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _delegate,
uint256 _initialMint,
uint8 _decimals
) OFT(_name, _symbol, _lzEndpoint, _delegate) Ownable(_delegate) {
_mint(msg.sender, _initialMint);
decimalsArg = _decimals;
}
function decimals() public view override returns (uint8) {
return decimalsArg;
}
}
```
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ONFT721} from "@layerzerolabs/onft-evm/contracts/onft721/ONFT721.sol";
contract ExampleONFT is ONFT721 {
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _delegate,
uint256 tokenId
) ONFT721(_name, _symbol, _lzEndpoint, _delegate) {
_mint(msg.sender, tokenId);
}
}
```
**Considerations**
* HTS tokens can function as ERC-compatible assets through an OFT adapter, requiring no modifications.
* OFT standard token contracts must be deployed on the destination chain.
### OFT Adapter / ONFT Adapter
The OFT Adapter acts as an intermediary contract that handles sending and receiving existing fungible tokens across chains. If your token already exists on the chain you want to connect to, you can deploy the [**OFT Adapter**](https://github.com/hashgraph/hedera-smart-contracts/tree/main/lib/layer-zero#oft-adapter) contract to act as an intermediary lockbox for the token. Similarly, the ONFT Adapter handles sending and receiving existing fungible tokens across chains. If your NFT already exists on the chain you want to connect to, you can deploy the [**ONFT Adapter**](https://github.com/hashgraph/hedera-smart-contracts/tree/main/lib/layer-zero#onft-adapter) contract to act as an intermediary lockbox for the NFT.
#### **Use Case**
Intermediary contract that handles sending and receiving existing fungible tokens across chains. If your token already exists on the chain you want to connect to, you can deploy the OFT Adapter contract to act as an intermediary lockbox for the token.
**Example Contracts**
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;
import {OFTAdapter} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFTAdapter.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract ExampleOFTAdapter is OFTAdapter {
constructor(
address _token,
address _lzEndpoint,
address _owner
) OFTAdapter(_token, _lzEndpoint, _owner) Ownable(_owner) {}
}
```
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.22;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ONFT721Adapter} from "@layerzerolabs/onft-evm/contracts/onft721/ONFT721Adapter.sol";
contract ExampleONFTAdapter is ONFT721Adapter {
constructor(
address _token,
address _lzEndpoint,
address _owner
) ONFT721Adapter(_token, _lzEndpoint, _owner) { }
}
```
**Considerations**
* HTS tokens can function as ERC-compatible assets through an OFT adapter, requiring no modifications.
### HTS Connector
The [**HTS Connector Contract**](https://github.com/hashgraph/hedera-smart-contracts/tree/main/lib/layer-zero#hts-connector) ([independent audit report](https://hedera.com/audits-and-standards)) extends LayerZero’s functionality to accommodate HTS tokens, addressing the differences between HTS and ERC token standards. It facilitates the integration of existing ERC tokens with Hedera by bridging them as native HTS tokens. This is a variant of OFT when bringing tokens to Hedera as HTS. If you bring a token to Hedera as an ERC token, you can use [**OFT**](https://github.com/hashgraph/hedera-smart-contracts/tree/main/lib/layer-zero#oft) or [**ONFT**](https://github.com/hashgraph/hedera-smart-contracts/tree/main/lib/layer-zero#onft).
#### **Use Case**
Bridging a token from other networks to Hedera as an HTS token.
**Considerations:**
* When deploying HTS connector:
* The contract should be associated to the token or be deployed with `maxAutomaticAssociation`
* The "supply key" of the token must contain the address of the HTS Connector contract.
```solidity theme={null}
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;
import {OFTCore} from "@layerzerolabs/lz-evm-oapp-v2/contracts/oft/OFTCore.sol";
import "./hts/HederaTokenService.sol";
import "./hts/IHederaTokenService.sol";
import "./hts/KeyHelper.sol";
/**
* @title HTS Connector
* @dev HTS Connector is a HTS token that extends the functionality of the OFTCore contract.
*/
abstract contract HTSConnector is OFTCore, KeyHelper, HederaTokenService {
address public htsTokenAddress;
bool public finiteTotalSupplyType = true;
event TokenCreated(address tokenAddress);
/**
* @dev Constructor for the HTS Connector contract.
* @param _name The name of HTS token
* @param _symbol The symbol of HTS token
* @param _lzEndpoint The LayerZero endpoint address.
* @param _delegate The delegate capable of making OApp configurations inside of the endpoint.
*/
constructor(
string memory _name,
string memory _symbol,
address _lzEndpoint,
address _delegate
) payable OFTCore(8, _lzEndpoint, _delegate) {
IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](1);
keys[0] = getSingleKey(
KeyType.SUPPLY,
KeyValueType.INHERIT_ACCOUNT_KEY,
bytes("")
);
IHederaTokenService.Expiry memory expiry = IHederaTokenService.Expiry(0, address(this), 8000000);
IHederaTokenService.HederaToken memory token = IHederaTokenService.HederaToken(
_name, _symbol, address(this), "memo", finiteTotalSupplyType, 5000, false, keys, expiry
);
(int responseCode, address tokenAddress) = HederaTokenService.createFungibleToken(
token, 1000, int32(int256(uint256(8)))
);
require(responseCode == HederaTokenService.SUCCESS_CODE, "Failed to create HTS token");
int256 transferResponse = HederaTokenService.transferToken(tokenAddress, address(this), msg.sender, 1000);
require(transferResponse == HederaTokenService.SUCCESS_CODE, "HTS: Transfer failed");
htsTokenAddress = tokenAddress;
emit TokenCreated(tokenAddress);
}
/**
* @dev Retrieves the address of the underlying HTS implementation.
* @return The address of the HTS token.
*/
function token() public view returns (address) {
return htsTokenAddress;
}
/**
* @notice Indicates whether the HTS Connector contract requires approval of the 'token()' to send.
* @return requiresApproval Needs approval of the underlying token implementation.
*/
function approvalRequired() external pure virtual returns (bool) {
return false;
}
/**
* @dev Burns tokens from the sender's specified balance.
* @param _from The address to debit the tokens from.
* @param _amountLD The amount of tokens to send in local decimals.
* @param _minAmountLD The minimum amount to send in local decimals.
* @param _dstEid The destination chain ID.
* @return amountSentLD The amount sent in local decimals.
* @return amountReceivedLD The amount received in local decimals on the remote.
*/
function _debit(
address _from,
uint256 _amountLD,
uint256 _minAmountLD,
uint32 _dstEid
) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) {
(amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid);
int256 transferResponse = HederaTokenService.transferToken(htsTokenAddress, _from, address(this), int64(uint64(_amountLD)));
require(transferResponse == HederaTokenService.SUCCESS_CODE, "HTS: Transfer failed");
(int256 response,) = HederaTokenService.burnToken(htsTokenAddress, int64(uint64(amountSentLD)), new int64[](0));
require(response == HederaTokenService.SUCCESS_CODE, "HTS: Burn failed");
}
/**
* @dev Credits tokens to the specified address.
* @param _to The address to credit the tokens to.
* @param _amountLD The amount of tokens to credit in local decimals.
* @dev _srcEid The source chain ID.
* @return amountReceivedLD The amount of tokens ACTUALLY received in local decimals.
*/
function _credit(
address _to,
uint256 _amountLD,
uint32 /*_srcEid*/
) internal virtual override returns (uint256) {
(int256 response, ,) = HederaTokenService.mintToken(htsTokenAddress, int64(uint64(_amountLD)), new bytes[](0));
require(response == HederaTokenService.SUCCESS_CODE, "HTS: Mint failed");
int256 transferResponse = HederaTokenService.transferToken(htsTokenAddress, address(this), _to, int64(uint64(_amountLD)));
require(transferResponse == HederaTokenService.SUCCESS_CODE, "HTS: Transfer failed");
return _amountLD;
}
}
```
***
## Developer Considerations to Note EVM Differences
Please note the smallest unit of ***HBAR is the tinybar (8 decimal places)***, while the ***JSON-RPC relay operates 18 decimal places*** for compatibility with EVM tools. This means when dealing with `msg.value`, conversions between tinybars and weibars are necessary. Please take these differences into account, especially when calling `quote`. Reference the [Understanding Hedera's EVM Differences and Compatibility](/evm/differences) section for a more comprehensive list of differences.
***
## Additional Resources
* [**LZ Examples Repo**](https://github.com/hashgraph/hedera-smart-contracts/tree/main/lib/layer-zero#layer-zero-examples)
* [**Independent Audit Report**](https://hedera.com/audits-and-standards)
* [**Demo Code Repo**](https://github.com/hedera-dev/hedera-example-layer-zero-bridging-oapp)
* [**LayerZero Scan**](https://layerzeroscan.com/)
* [**Hedera Fee Estimator**](https://hedera.com/fees)
* [**LayerZero Developer Resources**](https://layerzero.network/developers)
* [**Hedera Testnet LayerZero Endpoint**](https://docs.layerzero.network/v2/developers/evm/technical-reference/deployed-contracts#hedera-testnet)
# USDT0 Omnichain Stablecoin on Hedera
Source: https://docs.hedera.com/evm/integrations/cross-chain/usdt0
Integrate USDT0, the omnichain deployment of Tether's USDT, on Hedera. Move native USDT liquidity across 20+ chains with LayerZero's OFT standard, no wrapped tokens or third-party bridges.
[USDT0](https://usdt0.to/) is the omnichain deployment of Tether's USDT, the largest and most widely used dollar stablecoin. It is now live on Hedera, giving applications access to deep, cross-chain dollar liquidity that moves between networks as a single unified token, without wrapped assets, synthetic representations, or third-party bridges.
USDT0 is built on LayerZero's Omnichain Fungible Token (OFT) standard, so the same USDT0 balance can move between Hedera and other supported chains with 1:1 backing and no liquidity fragmentation. It is a production deployment of the same standard covered generically in [LayerZero on Hedera](/evm/integrations/cross-chain/layerzero). See that page for the OFT, OFT Adapter, and HTS Connector building blocks. This page covers the USDT0 deployment specifically and how to use it.
For the background and Hedera's perspective on why this matters, read the announcement: [**Hedera integrates USDT0 for crosschain stablecoin liquidity**](https://hedera.com/blog/hedera-integrates-usdt0-for-crosschain-stablecoin-liquidity/).
## What USDT0 is (and what it is not)
* **Native USDT liquidity** on Hedera, backed 1:1 by Tether's USDT
* A **single omnichain token**, the same asset across every supported chain
* Built on **LayerZero's OFT standard** for secure cross-chain messaging
* On Hedera, a standard **HTS fungible token** with an EVM-compatible interface
* **Not a wrapped or synthetic token** with its own separate liquidity pool
* **Not a third-party bridge** that issues a chain-specific IOU
* **Not a new stablecoin** with a separate peg or reserve from USDT
* **Not custodied by Hedera** or the LayerZero messaging layer
## How it works
USDT0 uses a lock-and-mint model anchored on Ethereum and an omnichain messaging layer to keep one unified supply across chains.
The canonical USDT supply is held by an OFT Adapter contract on Ethereum mainnet. Locking USDT there authorizes an equivalent mint elsewhere.
An equivalent amount of USDT0 mints on the destination chain (such as Hedera) with strict 1:1 backing.
A cross-chain transfer burns USDT0 on the source chain and mints it on the destination chain. LayerZero's decentralized verifier network carries and validates the message, so no separate bridge liquidity pool is involved.
Burning USDT0 and routing back to Ethereum unlocks the original USDT from the adapter, keeping total supply constant.
## USDT0 on Hedera
On Hedera, USDT0 is a native **Hedera Token Service (HTS)** fungible token that is also reachable through its EVM address, so you can work with it from both native SDKs and EVM tooling such as ethers.js, Hardhat, or Foundry.
| Detail | Value |
| --------------------------------------- | -------------------------------------------- |
| Token name | USDT0 |
| Decimals | 6 |
| Hedera token ID | `0.0.10282787` |
| Token EVM address | `0x00000000000000000000000000000000009Ce723` |
| OFT contract (cross-chain send/receive) | `0xe3119e23fC2371d1E6b01775ba312035425A53d6` |
| LayerZero endpoint ID (EID) for Hedera | `30316` |
| Hedera EVM chain ID | `295` (mainnet) |
Contract addresses and endpoint IDs are reproduced here for convenience and were accurate at the time of writing. Always confirm against the canonical [USDT0 deployments page](https://docs.usdt0.to/technical-documentation/deployments) before sending value, and treat the official USDT0 documentation as the source of truth.
Because USDT0 is an HTS token, a receiving Hedera account must be **associated** with the token before it can hold a balance. Accounts with automatic association slots available, or that already hold the token, do not need an explicit association. See [Token Association](/native/tokens/associate) for details.
The underlying token is an **HTS token reached through Hedera's [EVM facade](/support/glossary#facade-contract)**, not a plain ERC-20. The OFT contract reports `approvalRequired() == true`, so you must `approve` the OFT to spend USDT0 before calling `send`; that approval routes through the HTS allowance facade. The OFT is deployed behind an **upgradeable proxy**, so confirm current behavior against the [USDT0 Developer Guide](https://docs.usdt0.to/technical-documentation/developer/) before integrating.
## Sending USDT0 across chains
Cross-chain transfers go through the OFT contract, not a plain ERC-20 `transfer`. The flow is the same as any LayerZero OFT: quote the messaging fee, approve the amount if needed, then call `send`. You pay the LayerZero messaging fee in the source chain's native gas token (HBAR when sending from Hedera).
The code below **illustrates the standard LayerZero OFT pattern** so you can see the shape of the flow. It is not a Hedera-tested recipe. Treat the [**USDT0 Developer Guide**](https://docs.usdt0.to/technical-documentation/developer/) as the source of truth for working integration code — it ships maintained TypeScript/ethers.js examples — and verify the Hedera-specific behavior noted below before moving real value.
In particular, mind the **tinybar/weibar unit difference**: `quoteSend` returns the fee in tinybars (8 decimals), but the JSON-RPC relay expects `msg.value` in weibars (18 decimals), so the fee must be converted before it is attached to `send`. See [Developer Considerations on the LayerZero page](/evm/integrations/cross-chain/layerzero#developer-considerations-to-note-evm-differences) for more on this.
The `send` function takes a `SendParam` struct and a `MessagingFee`:
```solidity SendParam theme={null}
struct SendParam {
uint32 dstEid; // destination chain LayerZero endpoint ID
bytes32 to; // recipient address, left-padded to bytes32
uint256 amountLD; // amount to send, in local decimals (6 for USDT0)
uint256 minAmountLD; // minimum received after fees/slippage
bytes extraOptions; // execution options (e.g. gas for the destination)
bytes composeMsg; // optional composed message payload
bytes oftCmd; // OFT command bytes (empty for a standard send)
}
struct MessagingFee {
uint256 nativeFee; // fee paid in native gas (HBAR on Hedera)
uint256 lzTokenFee; // optional fee paid in the LayerZero token
}
```
### Example: send USDT0 from Hedera with ethers.js
```javascript send-usdt0.js theme={null}
import { ethers } from "ethers";
// Minimal ABI for the OFT send flow.
const OFT_ABI = [
"function quoteSend((uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd) sendParam, bool payInLzToken) view returns ((uint256 nativeFee, uint256 lzTokenFee) fee)",
"function send((uint32 dstEid, bytes32 to, uint256 amountLD, uint256 minAmountLD, bytes extraOptions, bytes composeMsg, bytes oftCmd) sendParam, (uint256 nativeFee, uint256 lzTokenFee) fee, address refundAddress) payable returns ((bytes32 guid, uint64 nonce, (uint256 nativeFee, uint256 lzTokenFee) fee) msgReceipt, (uint256 amountSentLD, uint256 amountReceivedLD) oftReceipt)",
"function token() view returns (address)",
];
const ERC20_ABI = [
"function approve(address spender, uint256 amount) returns (bool)",
"function allowance(address owner, address spender) view returns (uint256)",
];
// USDT0 OFT contract on Hedera mainnet.
const HEDERA_USDT0_OFT = "0xe3119e23fC2371d1E6b01775ba312035425A53d6";
// Left-pad a 20-byte EVM address to a 32-byte value.
function addressToBytes32(address) {
return ethers.zeroPadValue(address, 32);
}
async function sendUsdt0({ provider, signer, dstEid, recipient, amount }) {
const oft = new ethers.Contract(HEDERA_USDT0_OFT, OFT_ABI, signer);
const sendParam = {
dstEid, // e.g. 30110 for Arbitrum One
to: addressToBytes32(recipient), // recipient on the destination chain
amountLD: amount, // 6-decimal amount, e.g. 100 USDT0 = 100_000000n
minAmountLD: amount, // adjust to allow for slippage if needed
extraOptions: "0x", // default execution options
composeMsg: "0x",
oftCmd: "0x",
};
// 1. Quote the LayerZero messaging fee (paid in HBAR when sending from Hedera).
const fee = await oft.quoteSend(sendParam, false);
// 2. Approve the OFT to pull the underlying token, if it is not already approved.
const tokenAddress = await oft.token();
const token = new ethers.Contract(tokenAddress, ERC20_ABI, signer);
const owner = await signer.getAddress();
const allowance = await token.allowance(owner, HEDERA_USDT0_OFT);
if (allowance < amount) {
await (await token.approve(HEDERA_USDT0_OFT, amount)).wait();
}
// 3. Send, attaching the native fee as msg.value.
// Hedera gotcha: quoteSend returns nativeFee in tinybars (8 decimals), but the
// JSON-RPC relay expects msg.value in weibars (18 decimals). Convert before sending.
const nativeFeeWeibars = fee.nativeFee * 10n ** 10n;
const tx = await oft.send(sendParam, fee, owner, { value: nativeFeeWeibars });
return tx.wait();
}
```
`amountLD` and `minAmountLD` are expressed in USDT0's 6 decimals, so 100 USDT0 is `100_000000`. USDT0's OFT uses 6 shared decimals, matching its 6 local decimals, so there is no dust removal or rounding on transfer; the full amount you send is delivered. The LayerZero messaging fee is paid separately in HBAR (the `nativeFee`), not deducted from the transferred USDT0.
### Common destination endpoint IDs
Use the destination chain's LayerZero endpoint ID (EID) as `dstEid`. A few common values:
| Destination chain | Chain ID | LayerZero EID |
| ----------------- | -------- | ------------- |
| Ethereum | 1 | 30101 |
| Arbitrum One | 42161 | 30110 |
| Optimism | 10 | 30111 |
| Polygon PoS | 137 | 30109 |
| Hedera | 295 | 30316 |
The full, authoritative list lives on the [USDT0 deployments page](https://docs.usdt0.to/technical-documentation/deployments) and the [LayerZero deployments reference](https://docs.layerzero.network/v2/deployments/deployed-contracts).
## Using USDT0 as a regular token on Hedera
Once USDT0 is on a Hedera account, it behaves like any other HTS fungible token for in-network use. You can transfer it between Hedera accounts, hold it in smart contracts, and use it in DeFi protocols using either the native SDKs or the EVM interface:
* **Native SDKs:** use [`TransferTransaction`](/native/accounts/transfer) with the token ID `0.0.10282787`, and [associate the token](/native/tokens/associate) first if needed.
* **EVM tooling:** treat the token EVM address `0x00000000000000000000000000000000009Ce723` as a standard 6-decimal ERC-20.
Reserve the OFT `send` flow for moving USDT0 **across chains**; a same-chain Hedera transfer is just a normal HTS or ERC-20 transfer.
## Requirements and limitations
USDT0 is a third-party omnichain token. Hedera does not custody it or operate the cross-chain messaging layer. Review the official USDT0 and LayerZero documentation before moving real value.
* **The USDT0 token is mainnet-only:** USDT0 is deployed on Hedera **mainnet**; there is no Hedera testnet USDT0 token to test transfers against. LayerZero's messaging layer itself does have a [Hedera testnet endpoint](https://docs.layerzero.network/v2/deployments/chains/hedera-testnet) (EID 40285), so you can exercise OFT wiring on testnet with your own token. Validate the USDT0 path with small amounts on mainnet.
* **HBAR for fees:** Sending USDT0 from Hedera requires HBAR to cover both the Hedera transaction fee and the LayerZero messaging fee (the `nativeFee` you attach as `msg.value`).
* **Token association:** A receiving Hedera account must be associated with USDT0 (or have an open auto-association slot) before it can hold a balance.
* **6 decimals:** USDT0 uses 6 decimals on every chain, and its OFT uses 6 shared decimals, so cross-chain transfers move the exact amount with no dust rounding.
* **Destination gas:** Cross-chain delivery may require execution options (`extraOptions`) that allocate enough gas on the destination chain. Use `quoteSend` to price the transfer with the options you intend to use.
* **Confirm addresses:** Always verify contract addresses and endpoint IDs against the canonical USDT0 deployments page before integrating.
## Resources
Official USDT0 technical documentation, architecture, and developer guide.
Maintained TypeScript/ethers.js integration examples — the source of truth for working code.
Canonical contract addresses and endpoint IDs for every supported chain.
Hedera's announcement covering the what and why.
The OFT, OFT Adapter, and HTS Connector building blocks USDT0 is built on.
How the Omnichain Fungible Token standard works under the hood.
# Chainlink Oracles
Source: https://docs.hedera.com/evm/integrations/oracles/chainlink
## What is Chainlink?
Chainlink Oracles are decentralized services that securely connect smart contracts to real-world data, events, and computations. By bridging the gap between blockchain applications and off-chain environments, Chainlink Oracles empowers developers to build advanced, feature-rich decentralized applications (dApps) across various industries.
***
## **Getting Started with Chainlink Oracles on Hedera**
To integrate Chainlink Oracles into your Hedera-based dApp, you can start with a ready-to-use Gitpod demonstrating how to get Chainlink price feeds on Hedera using the Chainlink Price Feeds Adapter.
### Try It in Gitpod
[](https://gitpod.io/?autostart=true#https://github.com/ed-marquez/hedera-example-chainlink-price-feeds)
1. Enter your Hedera testnet credentials in the `.env` file
2. Run the test to get the latest prices for all the price feeds:
```bash theme={null}
npx hardhat test
```
[](https://github.com/hedera-dev/hedera-example-chainlink-price-feeds/blob/main/assets/console-output.png)
***
## References
* [**Chainlink Price Feeds**](https://docs.chain.link/data-feeds/price-feeds/addresses/?network=hedera\&%3Bpage=1\&page=1)
* [**Testnet LINK Token Contract**](https://docs.chain.link/resources/link-token-contracts#hedera)
* #### [**Using Data Feeds on EVM Chains**](https://docs.chain.link/data-feeds/using-data-feeds#overview)
**Have questions?** Join the [Hedera Discord](https://hedera.com/discord) and post them in the [`developer-general`](https://discord.com/channels/373889138199494658/373889138199494660) channel.
# Oracle Networks
Source: https://docs.hedera.com/evm/integrations/oracles/index
Oracle networks integrated with the Hedera network.
Oracle networks integrated with Hedera provide secure, reliable, and decentralized off-chain data feeds for applications and smart contracts. They bridge the gap between blockchain-based systems and external data, enabling smart contracts to access real-world information like market prices, weather forecasts, and more to execute automated actions, such as payments or trading decisions.
Hedera supports integration with multiple oracle networks, each offering unique features and specialized data feeds tailored to various use cases. Explore the oracle networks below to find the one that best suits your needs:
# Pyth Oracles
Source: https://docs.hedera.com/evm/integrations/oracles/pyth
## What is Pyth?
The Pyth Network is a first-party financial oracle network designed to provide low-latency real-world data to multiple blockchains securely and transparently. Pyth currently supports 400+ real-time price feeds across crypto, equities, ETFs, FX pairs, and commodities and has facilitated more than \$100B in total trading volume across over 50 blockchain ecosystems.
## How to integrate with the Pyth Network on Hedera?
The Pyth Price Feeds uses a "pull" price update model, where users are responsible for posting price updates on-chain when needed. In the pull model, developers should integrate Pyth into both their on-chain and off-chain code:
1. On-chain programs should read prices from the Pyth program deployed on the same chain
2. Off-chain frontends and jobs should include Pyth price updates alongside (or within) their application-specific transactions.
Please note that it is possible to replicate the legacy Oracle design with the Pyth scheduler (previously known as "price pusher"). It is an off-chain application that regularly pulls price updates on to a blockchain you can [find here](https://docs.pyth.network/price-feeds/schedule-price-updates/using-scheduler).
## Demonstration: How to integrate with the Pyth Network on Hedera?
This demo is a simple example of using Pyth prices in Hedera and is based on the [tutorial here](https://docs.pyth.network/price-feeds/create-your-first-pyth-app/evm/part-1). Follow the instructions in the tutorial to build, deploy, and use this demo.
This demo is similar to the contract written in the tutorial. The only difference is that it uses a different math to calculate the price of 1\$ in HBAR because, In the Hedera EVM layer, the native token has only 8 decimal places, while Ethereum has 18 decimal places. This means that the smallest unit of HBAR (1 wei in Hedera EVM) is 0.00000001 HBAR, while the smallest unit of ETH is 0.000000000000000001 ETH. You can see the change in the code in the [MyFirstPythContract.sol](https://github.com/hedera-dev/tutorial-js-pyth-oracle-contract-pull/blob/main/contracts/src/MyFirstPythContract.sol).
For more details, please visit this [GitHub repository](https://github.com/hedera-dev/tutorial-js-pyth-oracle-contract-pull/).
If you have any questions, please refer to the Pyth Network [documentation](https://docs.pyth.network/home) or join [Discord](https://discord.gg/invite/PythNetwork).
***
**Contributors:** [**@KemarTiti**](https://github.com/KemarTiti)
# Supra Oracles
Source: https://docs.hedera.com/evm/integrations/oracles/supra
## Overview
[Supra](https://supra.com/) is a novel, high-throughput Oracle & IntraLayer offering a vertically integrated toolkit of cross-chain solutions. These solutions include data oracles, asset bridges, and automation networks that aim to interlink all public and private blockchains. Supra provides decentralized Oracle price feeds to deliver real-world data to web3 ecosystems through various on-chain and off-chain use cases. Oracles ensure that the data from the real world is accurate, which is crucial for decentralized applications (dApps) that rely on real-world, real-time data. This is important for dApps that need real-time information, such as the prices of cryptocurrencies and various other assets. The Supra x Hedera integration aims to bring speed, security, and accuracy to real-time data feeds, enhancing the functionality and reliability of dApps on the Hedera network.
## **Developer Considerations**
Contract calls, such as `eth_call` and `eth_estmateGas`, go to the mirror node, which limits those to a small data payload size. Our engineering team has successfully upgraded the API call data payload capacity to 24 KB. This enhancement is designed to fetch a single price pair from the data feed efficiently, ensuring a more streamlined retrieval process.
**Please Note:** While this update offers improved performance to pull single price pairs, attempting to pull more than one price pair at a time may surpass the new 24 KB data payload limit. Should this limit be exceeded, the API will return an error message. We recommend structuring your API calls accordingly to avoid any potential disruptions.
## Supra Demo on Hedera
### Pull Model
This example shows how to use Supra Oracles real-world data feeds (Pull model). It fetches and verifies price data from Supra's gRPC server and use it within a smart contract on the Hedera network. These are key files:
[**`main.js`**](https://github.com/hedera-dev/hedera-example-supra-oracle-contract-pull/blob/main/client/main.js)
This `main` script interfaces with the Supra Oracle to request price data proofs for a specified pair. It demonstrates the initialization of a PullServiceClient, the request for data proofs, and the interaction with a smart contract deployed on Hedera to deliver the obtained price data.
The script enables switching between the Hedera mainnet and testnet environments. It uses Web3.js and includes functions to sign and send transactions to Hedera, estimate gas, and extract price data.
[**`MockOracleClient.sol`**](https://github.com/hedera-dev/hedera-example-supra-oracle-contract-pull/blob/main/smartcontract/MockOracleClient.sol)
This Solidity smart contract acts as a mock client for consuming Oracle pull data. It defines a structure for the price data and a function to receive and process the verified Oracle proof bytes.
### Try this Example on Your Browser with GitPod
1. Go to [this link](https://gitpod.io/#https://github.com/hedera-dev/hedera-example-supra-oracle-contract)
2. Run the following commands on the terminal:
`cd client` `npm init --y` `npm install`
3. Rename the file `.env.SAMPLE` to `.env` and enter you Hedera network credentials (testnet/mainnet):
`cp .env.SAMPLE .env`
4. Run the `main.js` script:
`node main.js`
You should see a console output similar to:
[](https://github.com/hedera-dev/hedera-example-supra-oracle-contract-pull/blob/main/images/console_output.png)
### Push Model
This example shows how to use Supra Oracles real-world data feeds (Push model).
[**`main.js`**](https://github.com/hedera-dev/hedera-example-supra-oracle-contract-push/blob/master/main.js)
With this script, you start by deploying the [`ConsumerContract.sol`](https://github.com/hedera-dev/hedera-example-supra-oracle-contract-push/blob/master/contracts/ConsumerContract.sol) and passing to its constructor the Supra storage contract address (`storageContractAddress`). Get the right storage address from: [https://supra.com/docs/data-feeds/decentralized/networks/](https://supra.com/docs/data-feeds/decentralized/networks/)
Then call the `getPrice` and/or `getPriceForMultiplePair` functions using the desired price pair indices. Get the right price pair indices from: [https://supra.com/docs/data-feeds/data-feeds-index/](https://supra.com/docs/data-feeds/data-feeds-index/)
### Try this Example on Your Browser with GitPod
1. Go to [this link](https://gitpod.io/#https://github.com/hedera-dev/hedera-example-supra-oracle-contract-push)
2. Run the following commands on the terminal:
`npm install`
3. Rename the file `.env.SAMPLE` to `.env` and enter you Hedera network credentials (testnet/mainnet):
`cp .env.SAMPLE .env`
4. Run the `main.js` script:
`node main.js`
[](https://github.com/hedera-dev/hedera-example-supra-oracle-contract-push/blob/master/images/console_output.png)
# Hedera Wallet Snap By MetaMask
Source: https://docs.hedera.com/evm/integrations/wallets/metamask-snap
## Overview
MetaMask is a popular Ethereum wallet and browser extension that developers can integrate into a variety of third-party applications. MetaMask Snaps is an open-source solution to enhance MetaMask's functionalities beyond its native capabilities. The [Hedera Wallet Snap](https://snaps.metamask.io/snap/npm/hashgraph/hedera-wallet-snap/), developed by [Tuum Tech](https://www.tuum.tech/) and managed by [Hashgraph](https://www.hashgraph.com/), enables users to interact directly with the Hedera network without relying on [Hedera JSON-RPC Relay](https://github.com/hashgraph/hedera-json-rpc-relay), offering Hedera native functionalities like sending HBAR to different accounts and retrieving account information.
## What is a Snap?
MetaMask Snaps is an open-source framework allowing secure extensions to MetaMask, thus enhancing web3 user experiences. It empowers the addition of new API methods, supports various blockchain protocols, and tweaks existing functionalities via the Snaps JSON-RPC API
Snaps enable users to interact with new blockchains, protocols, and decentralized applications `(dApps)` beyond what is natively supported by MetaMask. The goal of the MetaMask Snaps system is to create a more open, customizable, and extensible wallet experience for users while fostering innovation and collaboration within the blockchain and decentralized application ecosystem.
## FAQs
The Hedera JSON RPC Relay supports only the methods defined at [Hedera JSON RPC Relay Methods](https://playground.open-rpc.org/?schemaUrl=https%3A%2F%2Fraw.githubusercontent.com%2Fhashgraph%2Fhedera-json-rpc-relay%2Fmain%2Fdocs%2Fopenrpc.json), which are limited to Hedera Smart Contract Services. In contrast, the Hedera Wallet Snap uses the Hedera SDK to interact natively with the ledger, allowing the future support of a wider range of Consensus Node services like Hedera Token Service, Hedera Consensus Service, and Hedera File Service, beyond just smart contracts.
To deploy a smart contract on Hedera using MetaMask, you will need to use the [Hedera JSON RPC relay](/evm/development/json-rpc). You can deploy using tools compatible with EVM-based chains. For detailed steps, refer to Deploying Smart Contracts on Hedera.
No, you cannot use a signer created via ED25519 for Ethereum-based transactions due to the difference in cryptographic algorithms and key formats. EVM uses ECDSA with the secp256k1 curve, which is different from ED25519. For interacting directly with smart contracts on Hedera, only ECDSA-based accounts can be used.
Currently, there is no direct way to delegate the signing process to MetaMask or WalletConnect for transactions composed by the Hedera SDK, as they do not provide private keys of users.
The Hedera JSON RPC relay exposes specific methods, as detailed in [Hedera JSON RPC Relay Methods](https://playground.open-rpc.org/?schemaUrl=https%3A%2F%2Fraw.githubusercontent.com%2Fhashgraph%2Fhedera-json-rpc-relay%2Fmain%2Fdocs%2Fopenrpc.json). You can use these methods for transactions with Hedera’s smart contracts. The Hedera Wallet Snap, using the Hedera SDK, can perform all Hedera transactions and will eventually support interactions with smart contracts as well.
While Hashio RPC and other RPCs are limited to methods exposed by the Hedera JSON RPC relay, the Hedera Wallet Snap, using the Hedera SDK natively, offers access to all Hedera native features, including Hedera Token Service, Hedera File Service, and Hedera Consensus Service, enabling a broader range of interactions beyond smart contracts.
Yes, that’s correct. The Hedera Wallet Snap is ideal for custom Hedera functionalities. It uses the Hedera SDK for all operations, allowing for native interactions with the full spectrum of Hedera’s offerings.
# Tutorial: MetaMask Snaps – What Are They and How to Use Them
Source: https://docs.hedera.com/evm/integrations/wallets/metamask-snap-tutorial
Step-by-step tutorial on installing the Hedera Wallet Snap, connecting MetaMask to a Hedera network, and sending HBAR transactions from your dApp.
## Introduction
MetaMask is a widely used EVM wallet and browser extension – MetaMask Snaps is an open-source solution designed to enhance the capabilities of this wallet. Snaps are created by developers using JavaScript and enable users to interact with various blockchains, protocols, and [decentralized applications (dApps)](/support/glossary#decentralized-application-dapp) that MetaMask does not natively support. To learn more about Snaps, visit the [MetaMask Snap Guide](https://docs.metamask.io/snaps/).
The [Hedera Wallet Snap](https://snaps.metamask.io/snap/npm/hashgraph/hedera-wallet-snap/), developed by [Tuum Tech](https://www.tuum.tech/) and managed by [Hashgraph](https://www.hashgraph.com/), enables users to interact directly with the Hedera network. It offers functionalities like sending HBAR to different accounts and retrieving account information.
**What You Will Learn**
This tutorial will demonstrate how dApp builders and developers can seamlessly integrate and utilize the Hedera Wallet Snap in their applications. You will learn how to:
* Pair dApp with MetaMask
* Install the Hedera Wallet Snap
* Get the Snap Ethereum Virtual Machine (EVM) address
* Create the Snap account and check its balance
* Call other methods in the Hedera Wallet Snap, like: `transferCrypto`
**Tools You Will Use**
* React JS ([Documentation](https://react.dev/))
* MetaMask ([Documentation](https://docs.metamask.io/wallet/))
* Hedera Wallet Snap for MetaMask ([Documentation](https://docs.tuum.tech/hedera-wallet-snap/basics/introduction))
* Hedera JSON-RPC Relay ([Hashio](https://www.hashgraph.com/hashio/))
* Ethers JS ([Documentation](https://docs.ethers.org/v6/))
* Mirror Node REST API ([Learn More](https://hedera.com/blog/how-to-look-up-transaction-history-on-hedera-using-mirror-nodes-back-to-the-basics))
* Mirror Node Explorer ([HashScan](https://hashscan.io/))
***
## Prerequisites
* NodeJS >= 18.13 ([Download](https://nodejs.org/en))
* TypeScript >= 4.7 ([Download](https://www.npmjs.com/package/typescript))
* Git Command Line ([Download](https://git-scm.com/downloads))
* Hedera Testnet Account ([Create](https://portal.hedera.com/))
* MetaMask Wallet Extension ([Download](https://metamask.io/download/))
***
## **Get Familiar with the dApp Structure and UI**
Explore the project files and functions and get a feel for how the sample dApp looks and functions. This will make it easier to follow along as you dive into the technical aspects of the dApp.
For convenience, two options are provided to run the code used in this example:
This option does not require installing anything on your machine. All you need is a compatible web browser. [Click here to set up the GitPod environment](https://gitpod.io/#https://github.com/ed-marquez/hedera-example-metamask-snap). You will see the following:
If you prefer to have a copy of the code files on your machine and run the application locally, follow these steps.
#### Clone the Repo
To clone the repository, open your terminal and navigate to the directory where you want to place the project. Then, run the following command:
```bash theme={null}
git clone https://github.com/hedera-dev/hedera-example-metamask-snap.git
```
#### Navigate to Directory
Once the cloning process is complete, navigate to the project folder using:
```bash theme={null}
cd hedera-example-metamask-snaps
```
**Install Project Dependencies and Start the Application**
After cloning the repo and navigating to the right folder, install all project dependencies. Dependencies are listed in the [`package.json`](https://github.com/ed-marquez/hedera-example-metamask-snap/blob/main/package.json) file, so you can just run the following command:
```bash theme={null}
npm install
```
To start the application, run:
```bash theme={null}
npm start
```
**Project Structure**
The project folder structure should look something like the following.
### Overall dApp Structure
The example application has four buttons, which complete different tasks when pressed.
* The first button connects the application to MetaMask.
* The second button installs the Hedera Wallet Snap.
* The third button obtains information about the Snap account.
* The fourth button (and respective input boxes) uses the Hedera Wallet Snap to transfer HBAR.
Now let’s look at the **App.jsx** file (inside the src folder) behind this UI. You can think of the code as three main sections (in addition to the imports):
1. The state management is the part that uses the [**useState()** React Hook](https://react.dev/reference/react/useState).
2. The functions that are executed with each button press; we’ll look at these in the next few sections.
3. The **return** statement groups the elements we see on the page.
The **useState()** hook helps store information about the state of the application. In this case, we store information like the [snapId](https://docs.tuum.tech/hedera-wallet-snap/getting-started/hello-world#javascript-function-to-interact-with-hedera-wallet-snap-api), wallet data, the account that is connected, the network, and the receiver address and HBAR amount, along with text and links presented in the UI. Remember that the first output of **useState()** is the variable of interest (e.g., **walletData**), and the second output is a function to set a new value for that variable (e.g., **setWalletData**).
#### Understanding the React Components
The buttons, text, and input boxes in the UI are a combination of React components in the **return** statement of **App.jsx**. Working with components, we take advantage of React's composability for better organization and readability. The properties for each component instance – like the function that each button executes, the label of the button, the text above the button, and the link for that text – are customized using [React props](https://react.dev/learn/passing-props-to-a-component).
```jsx theme={null}
import React from "react";
import MyButton from "./MyButton.jsx";
import MyText from "./MyText.jsx";
function MyGroup(props) {
return (
);
}
export default MyGroup;
```
**MyGroup** is a functional component that combines a text element with a button and receives props as an argument. These properties include:
* **`text`**: The text to be displayed by the **MyText** component.
* **`link`**: An optional link to be associated with the **MyText** component. If provided, the text will become clickable and redirect to the specified link.
* **`fcn`**: A function to be executed when the button within the **MyButton** component is clicked.
* **`buttonLabel`**: The label for the button in the **MyButton** component.
Inside the component, we return a **div** element that contains both the **MyText** and **MyButton** components. We pass the corresponding props down to these child components, allowing them to render the text, link, and button label and assign the click event handler. Finally, by exporting **MyGroup**, we make it available for use in other parts of the application, enabling us to quickly create reusable groups of text and button elements throughout the dApp. You can find the JSX files for the functional components mentioned under the folder **src/components**.
```jsx theme={null}
import React from "react";
function MyButton(props) {
return (
);
}
export default MyButton;
```
**MyButton** accepts the following props:
* **`fcn`**: A function to be executed when the button is clicked.
* **`buttonLabel`**: The label for the button, which will be displayed as the button's text.
Inside the component, we return a div element that wraps a button element. The button element is assigned the **onClick** event handler with the function **props.fcn**. This allows us to execute a specified function when the button is clicked. The **className** attribute is set to "**cta-button**," which is used for styling the button with CSS. Finally, we display the **props.buttonLabel** as the button's text.
By exporting **MyButton**, we make it available for use in other groups or parts of the application, allowing us to easily create consistent and reusable buttons throughout the dApp with customized functionality and labels.
```jsx theme={null}
import React from "react";
function MyText(props) {
if (props.link !== "") {
return (
```typescript theme={null}
import React from "react";
import MyInputBox from "./MyInputBox";
import MyButton from "./MyButton";
import MyText from "./MyText";
function MySendGroup(props) {
return (
);
}
export default MySendGroup;
```
**MySendGroup** uses a text display, two input boxes, and a button to facilitate data entry and user interaction. The component takes props as an argument, including:
* **`fcnI1`** and **`fcnI2`**: Functions to store the text provided via input fields in the application state.
* **`placeholderTxt1_app`** and **`placeholderTxt2_app`**: Placeholder texts for the input fields.
* The same arguments are mentioned for **MyText** and **MyButton**.
Inside the component, we return a div element that contains the **MyText**, **MyInputBox**, and **MyButton** components. We pass the corresponding props down to these child components. Finally, exporting **MySendGroup** makes it available for use in other parts of the application.
```jsx theme={null}
import React from "react";
function MyInputBox(props) {
return (
);
}
export default MyInputBox;
```
**MyInputBox** accepts the following props:
* **`fcn`**: A function to store the text provided via input fields in the application state.
* **`placeholderTxt`**: Placeholder texts for the input fields.
Inside the component, we return a **div** that wraps an input element. The input element is assigned the **onChange** event handler with the function **props.fcn**. This triggers the function that stores the inputs (receiver address and HBAR amount) to the state of the application. We display the **props.placeholderTxt** as the box text. The **className** attribute is set to **"text-input,"** which is used for styling the boxes with CSS. Finally, by exporting **MyInputBox**, we make it available for use in other parts of the application.
***
## Step 1: Pair MetaMask Wallet (select network and account)
In **App.jsx**, we use the **connectWallet()** function (code tab 1), which in turn calls the **walletConnectFcn()** function (code tab 2) that is imported from the file **src/components/hedera/walletConnect.js**.
```jsx connectWallet() theme={null}
async function connectWallet() {
if (account !== undefined) {
setConnectText(`🔌 Account ${account} already connected ⚡ ✅`);
} else {
const wData = await walletConnectFcn(network);
let newAccount = wData[0];
if (newAccount !== undefined) {
setConnectText(`🔌 Account ${newAccount} connected ⚡ ✅`);
setConnectLink(`https://hashscan.io/${network}/account/${newAccount}`);
setWalletData(wData);
setAccount(newAccount);
setSnapInstallText();
setSnapInfoText();
setSnapTransferText();
}
}
}
```
```jsx walletConnectFcn() theme={null}
import { ethers } from "ethers";
async function walletConnectFcn(network) {
console.log(`\n=======================================`);
// ETHERS PROVIDER
const provider = new ethers.providers.Web3Provider(window.ethereum, "any");
// SWITCH TO HEDERA TEST NETWORK
console.log(`- Switching network to the Hedera ${network}...🟠`);
let chainId;
if (network === "testnet") {
chainId = "0x128";
} else if (network === "previewnet") {
chainId = "0x129";
} else {
chainId = "0x127";
}
await window.ethereum.request({
method: "wallet_addEthereumChain",
params: [
{
chainName: `Hedera ${network}`,
chainId: chainId,
nativeCurrency: { name: "HBAR", symbol: "HBAR", decimals: 18 },
rpcUrls: [`https://${network}.hashio.io/api`],
blockExplorerUrls: [`https://hashscan.io/${network}/`],
},
],
});
console.log("- Switched ✅");
// // CONNECT TO ACCOUNT
console.log("- Connecting wallet...🟠");
let selectedAccount;
await provider
.send("eth_requestAccounts", [])
.then((accounts) => {
selectedAccount = accounts[0];
console.log(`- Selected account: ${selectedAccount} ✅`);
})
.catch((connectError) => {
console.log(`- ${connectError.message.toString()}`);
return;
});
return [selectedAccount, provider];
}
export default walletConnectFcn;
```
When the **Connect Wallet** button is pressed in the dApp, the **connectWallet()** function is executed. This function checks if an account is already connected. If it is, a message displaying the connected account is shown. If no account is connected, the **walletConnectFcn()** is called to establish a connection.
The **walletConnectFcn()** function performs the following steps:
1. **Creates an ethers provider**: It initializes an ethers provider using the **Web3Provider** from the [**ethers**](https://docs.ethers.org/v6/) library, which connects to MetaMask. An Ethers provider serves as a bridge between your application and the Hedera network. It allows you to perform actions like sending transactions and querying data.
2. **Switches to Hedera Testnet**: It determines the **chainId** based on the chosen Hedera network (testnet, previewnet, or mainnet) and sends a **wallet\_addEthereumChain** request to MetaMask to add the corresponding Hedera network. A chain ID is a unique identifier that represents a blockchain network. This is an important step that includes setting the native currency (HBAR) and providing the JSON-RPC and network explorer URLs. For the JSON-RPC provider, this example uses [Hashio](https://www.hashgraph.com/hashio/), a community-hosted [JSON-RPC relay](https://github.com/hashgraph/hedera-json-rpc-relay) provided by [Hashgraph](https://www.hashgraph.com/) (note that anyone can host their own relay and/or use other commercial providers, like [Arkhia](https://www.arkhia.io/features/#api-services)). For network explorer, [HashScan](https://hashscan.io/) is used. (Keep in mind that HashScan supports [EIP-3091](https://eips.ethereum.org/EIPS/eip-3091), which makes it easy to explore historical data like blocks, transactions, accounts, contracts, and tokens from wallets like MetaMask).
3. **Connects and Pairs Account**: The function sends an **eth\_requestAccounts** request to MetaMask to access the user's Hedera account. Upon successful connection, the selected account is returned.
Finally, the **connectWallet()** function in **App.jsx** updates the React state with the connected testnet account and provider information, allowing the dApp to display the connected testnet account information.
This is what you see when clicking the **Connect Wallet** button for the first time.
**Note**: The Hedera account selected in MetaMask for this example has ECDSA
keys and is created using the [Hedera Portal](https://portal.hedera.com/).
Once the network switches and the account is paired, you should see something like the following in the dApp UI and in HashScan (if you click on the hyperlinked text showing the account address).
***
## Step 2: Install the Hedera Wallet Snap
The **snapInstall()** function (code tab 1) in **App.jsx** calls the **snapInstallFcn()** function (code tab 2). The latter is imported from the file **src/components/hedera/snapInstall.js**.
```jsx snapInstall() theme={null}
async function snapInstall() {
if (account === undefined) {
setSnapInstallText("🛑Connect a wallet first!🛑");
} else {
const newSnapInstallText = await snapInstallFcn(snapId);
setSnapInstallText(newSnapInstallText);
setSnapInfoText();
```
```jsx snapInstallFcn() theme={null}
async function snapInstallFcn(snapId) {
console.log(`\n=======================================`);
console.log(`- Installing Hedera Wallet Snap...🟠`);
console.log(`SnapId: ${snapId}`);
let outText;
let snaps = await window.ethereum.request({
method: "wallet_getSnaps",
});
console.log("Installed snaps...", snaps);
try {
if (!(snapId in snaps)) {
console.log("Hedera Wallet Snap is not yet installed. Installing now...");
const result = await window.ethereum.request({
method: "wallet_requestSnaps",
params: {
[snapId]: {},
},
});
console.log("result: ", result);
snaps = await window.ethereum.request({
method: "wallet_getSnaps",
});
}
} catch (e) {
console.log(
`Failed to obtain installed snap: ${JSON.stringify(e, null, 4)}`
);
alert(`Failed to obtain installed snap: ${JSON.stringify(e, null, 4)}`);
}
if (snapId in snaps) {
outText = "Snap installed ✅";
console.log(`- Snap installed successfully ✅`);
alert("Snap installed successfully!");
} else {
console.log("Could not connect successfully. Please try again!");
alert("Could not connect successfully. Please try again!");
}
return outText;
}
export default snapInstallFcn;
```
The **snapInstallFcn()** function performs the following steps:
1. **Gets Installed Snaps**: The function requests a list of installed Snaps in the user's MetaMask wallet using the **wallet\_getSnaps** method. It logs the currently installed Snaps.
2. **Installs Snaps**: The function checks if the Hedera Wallet Snap (identified by **snapId**) is already installed. If it's not installed, the function attempts to install it by calling **wallet\_requestSnaps** and passing the **snapId**. It then logs the result of this installation attempt. After attempting installation, it checks again for installed Snaps.
3. **Handles Installation Outcome**: If the Snap is successfully installed (i.e., **snapId** is found in the list of installed snaps), it logs and alerts the user that the Snap installation was successful. If the installation fails (i.e., **snapId** is not found in the list), it logs and alerts the user that the connection could not be established successfully.
4. **Returns Result**: Lastly, the function returns a text message indicating whether the Snap was installed successfully.
The text output from **snapInstallFcn()** is used by **snapInstall()** in the front end of the application to update the message seen by the user.
This is what you see when clicking the **Install Snap** button for the first time.
***
## Step 3: Get the Snap EVM Address
Before we can start using the functionality of the Hedera Wallet Snap, this step obtains the Snap EVM address. In the next step, we send HBAR to that Snap EVM address to create the corresponding Snap account.
These steps are necessary because Snaps provide a way to create a secure and unique wallet associated with a user's MetaMask account without directly accessing the private key of the account. This separate Snap account is unique to the user's MetaMask account and remains accessible across different browsers or devices as long as the secret recovery phrase remains the same. For more information, read [this section of the Hedera Wallet Snap documentation](https://docs.tuum.tech/hedera-wallet-snap/hedera-wallet-snap/snap-account).
The **snapGetAccountInfo()** function (code tab 1) in **App.jsx** calls the **snapGetAccountInfoFcn()** function (code tab 2). The latter is imported from the file **src/components/hedera/ snapGetAccountInfo.js**.
```jsx snapGetAccountInfo() theme={null}
async function snapGetAccountInfo() {
if (account === undefined || snapInstallText === undefined) {
setSnapInfoText("🛑Connect a wallet and install the snap first!🛑");
} else {
const [snapAccountAddress, infoText] = await snapGetAccountInfoFcn(
network,
walletData,
snapId
);
setSnapInfoText(infoText);
setInfoLink(`https://hashscan.io/${network}/address/${snapAccountAddress}`);
setSnapTransferText();
}
}
```
```jsx snapGetAccountInfoFcn() theme={null}
async function snapGetAccountInfoFcn(network, walletData, snapId) {
console.log(`\n=======================================`);
console.log(`- Invoking GetAccountInfo...🟠`);
let outText;
let snapAccountEvmAddress;
let snapAccountBalance;
try {
const response = await window.ethereum.request({
method: "wallet_invokeSnap",
params: {
snapId,
request: {
method: "getAccountInfo",
params: {
network: network,
mirrorNodeUrl: `https://${network}.mirrornode.hedera.com`,
},
},
},
});
snapAccountEvmAddress = response.accountInfo.evmAddress;
snapAccountBalance = response.accountInfo.balance.hbars;
outText = `Snap Account ${snapAccountEvmAddress} has ${snapAccountBalance} ℏ ✅`;
} catch (e) {
snapAccountEvmAddress = e.message.match(/0x[a-fA-F0-9]{40}/)[0];
outText = `Go to MetaMask and transfer HBAR to the snap address to activate it: ${snapAccountEvmAddress} 📤`;
}
console.log(`- ${outText}}`);
console.log(`- Got account info ✅`);
return [snapAccountEvmAddress, outText];
}
export default snapGetAccountInfoFcn;
```
The **snapGetAccountInfoFcn()** function performs the following steps:
1. **Requests Account Information**: The function makes a request to MetaMask using the **wallet\_invokeSnap** method. It sends the **snapId** and a request for the **getAccountInfo** method, along with the network details and the URL of the Hedera mirror node. For more details on this method, read [this section of the Hedera Wallet Snap documentation](https://docs.tuum.tech/hedera-wallet-snap/hedera-wallet-snap/snap-rpc-apis/account-apis/getaccountinfo).
2. **Processes the Response**: If the request is successful, the function extracts the EVM address of the Snap account and its balance in HBAR from the response. It then constructs a message stating the account's EVM address and balance.
3. **Handles Errors**: If there's an error (like the account not being created yet), the function extracts the EVM address from the error message. It then creates a message instructing the user to transfer HBAR to this address to create the Snap account.
4. **Returns Results**: The function logs the outcome message (either the account details or the activation instruction). It then returns the EVM address of the Snap account and the outcome message.
The outputs from **snapGetAccountInfoFcn()** are used by **snapGetAccountInfo()** in the front end of the application to update the message and link seen by the user.
This is what you see when clicking the **Get Snap Account Info** button for the first time.
**Note**: When invoking a Snap method for the first time, MetaMask checks for
confirmation before connecting the Hedera Wallet Snap to the account.
Once the Snap is connected to the MetaMask account, you should see something like the following in the dApp UI and in HashScan (if you click on the hyperlinked text showing the Snap account address).
***
## Step 4: Create Snap Account and Check Its Balance
We now know the EVM address for the Snap account. However, this Snap account has not yet been created on the Hedera network (testnet in this case). It’s time to send HBAR to that address to create the actual Hedera account for the snap.
Perform a transfer of ***10 HBAR*** to the Snap address. In this case, the Snap address is: [`0xa7deaf8acd6be555740f9672dff34f510480f0c9`](https://hashscan.io/testnet/account/0.0.7660668)
**Note**: The Hedera account selected in MetaMask for this example has ECDSA
keys and is created using the [Hedera Developer
Portal](https://portal.hedera.com/).
Once the transfer is complete, check the balance of the Snap account by clicking on the **Get Snap Account Info** button again. You should see something like the following in the dApp UI and in HashScan (if you click on the hyperlinked text showing the snap account address and its balance).
***
## Step 5: Call Other Methods in the Hedera Wallet Snap
The last step in this exercise is to try other methods available in the Hedera Wallet Snap. As of version `0.1.3` of the Snap, the **transferCrypto** method enables transferring HBAR to other Hedera accounts from MetaMask. In this case, we send ***0.2 HBAR*** from the Snap account to the MetaMask account. Let’s see how this is done in the code.
The **snapTransferHbar()** function (code tab 1) in **App.jsx** calls the **snapTransferHbarFcn()** function (code tab 2). The latter is imported from the file **src/components/hedera/snapTransferHbar.js**.
```jsx snapTransferHbar() theme={null}
async function snapTransferHbar() {
if (account === undefined || snapInstallText === undefined || snapInfoText === undefined) {
setSnapTransferText("🛑Complete all the steps above first!🛑");
} else {
setSnapTransferText(`Transferring...`);
const transferText = await snapTransferHbarFcn(network, walletData, snapId, [receiverAddress, hbarAmount]);
setSnapTransferText(`${transferText}`);
}
}
```
```jsx snapTransferHbarFcn() theme={null}
async function snapTransferHbarFcn(network, walletData, snapId, args) {
console.log(`\n=======================================`);
console.log(`- Invoking transferCrypto...🟠`);
let outText;
const receiverAddress = args[0];
const hbarAmount = parseFloat(args[1]);
const maxFee = 0.05;
const transfers = [
{
asset: "HBAR",
to: receiverAddress,
amount: hbarAmount, // in Hbar
},
];
// If you're sending to an exchange account,
// you will likely need to fill this out
const memo = "";
try {
await window.ethereum.request({
method: "wallet_invokeSnap",
params: {
snapId,
request: {
method: "transferCrypto",
params: {
network: network,
transfers,
memo,
maxFee: maxFee,
},
},
},
});
outText = `Transfer successful ✅ | Get the snap account info again to see the updated balance!`;
console.log(`- ${outText}`);
} catch (e) {
outText = `Transaction failed. Try again 🛑`;
console.log(`- Transfer failed 🛑: ${JSON.stringify(e, null, 4)}`);
}
return outText;
}
export default snapTransferHbarFcn;
```
The **snapTransferHbarFcn()** function performs the following steps:
* **Sets Transfer Details**: It extracts the recipient's address and the amount of HBAR to be transferred from the arguments (*args*) provided. Note that these inputs are specified in the dApp UI – remember that the receiver address is that of the MetaMask account and the transfer amount is ***0.2 HBAR***. The function sets a maximum fee for the transaction, which is predefined as ***0.05 HBAR*** in the code. It also prepares the transfer details, including the asset type (*HBAR*), the receiver's address, and the amount.
* **Executes Transfer Request**: The function sends a **wallet\_invokeSnap** request. This request includes the **snapId**, the **transferCrypto** method, and parameters such as the network, the transfer details, a memo (may be needed for transferring to exchange accounts), and the maximum fee.
* **Handles Transfer Outcome**: If the transfer is successful, the function sets a message indicating success and suggests re-checking the Snap account info for an updated balance. In case of an error or failure, it sets a different message indicating that the transaction failed and prompts trying again.
* **Returns Output**: Finally, the function returns the outcome message, informing whether the transfer was successful or not.
The text output from **snapTransferHbarFcn()** is used by **snapTransferHbar()** in the front end of the application to update the message seen by the user.
This is what you see when clicking the **Transfer HBAR w/ Snap** button after entering a valid receiver address and HBAR amount in the corresponding input fields.
Once the transfer of HBAR from the Snap account to the MetaMask account is complete, you can check the Snap account balance by clicking on the **Get Snap Account Info** button again. You will see something like this:
***
## Summary
This tutorial provides a comprehensive guide on how to use MetaMask Snaps, focusing on the Hedera Wallet Snap.
* It starts by covering the dApp's structure and user interface, highlighting the functions of various buttons like connecting to MetaMask, installing the *Hedera Wallet Snap*, obtaining Snap account information, and transferring HBAR using the Snap.
* The tutorial also dives into the technical aspects, detailing the *App.jsx* file, which includes state management, functions for button actions, and the layout of the UI components. It explains the use of React components like *MyGroup*, *MyButton*, and *MyInputBox* to create an interactive and user-friendly interface.
* It then elaborates on MetaMask Snaps, its purpose, and how to use them, specifically focusing on the Hedera Wallet Snap for tasks like pairing the dApp with MetaMask, installing the Snap, managing the Snap account, and executing crypto transfers.
**🎉 Congratulations! You have learned about the new Hedera Wallet Snap by MetaMask and how to integrate it into a dApp. Feel free to reach out in** [**Discord**](https://hedera.com/discord) **if you have any questions!**
***
## Additional Resources
**➡** [**Project Repository**](https://github.com/hedera-dev/hedera-example-metamask-snap)
**➡** [**Hedera Wallet Snap**](https://snaps.metamask.io/snap/npm/hashgraph/hedera-wallet-snap/)
**➡** [**MetaMask Snap Guide**](https://docs.metamask.io/snaps/)
**➡** [**Hedera JSON-RPC Relay Repository**](https://github.com/hashgraph/hedera-json-rpc-relay)
[GitHub](https://github.com/ed-marquez) |
[LinkedIn](https://www.linkedin.com/in/ed-marquez/)
[GitHub](https://github.com/ed-marquez) |
[LinkedIn](https://www.linkedin.com/in/ed-marquez/)
[GitHub](https://github.com/theekrystallee) |
[Hashnode](https://hashnode.com/@theekrystallee)
[GitHub](https://github.com/theekrystallee) |
[Hashnode](https://hashnode.com/@theekrystallee)
# Hedera WalletConnect
Source: https://docs.hedera.com/evm/integrations/wallets/walletconnect
# Deploy your First Contract with Hedera Contract Builder
Source: https://docs.hedera.com/evm/quickstart/deploy-with-contract-builder
## Hedera Contract Builder Quickstart
The Hedera Contract Builder allows you to deploy smart contracts on the Hedera testnet quickly. The tool is provided through the Hedera Developer Portal.
**Developing or running CI?** [Solo](https://solo.hiero.org/docs/) runs a full Hedera network locally, no testnet rate limits, faucet, or resets, and works with the same EVM tooling. See the [Solo quickstart](https://solo.hiero.org/docs/simple-solo-setup/quickstart/) and [Using Solo with EVM tools](https://solo.hiero.org/docs/using-solo/using-solo-with-evm-tools/).
Head over to the [Hedera Smart Contract Builder](https://portal.hedera.com/contract-builder), a powerful web-based IDE for compiling and deploying contracts.
Select the ERC20 option at the top and modify its settings. Let's create a token with the "Mintable" feature and set access control to "Ownable." Once you've done that, click the "Compile" button at the bottom left of the page.
#### **Please note**
These smart contracts are not audited and are intended for learning purposes only. By accepting this disclaimer, you acknowledge that you understand the risks involved in deploying unaudited contracts. For more information, please see our terms of service: [https://hedera.com/terms](https://hedera.com/terms)
Finally, you can deploy your contract. First, copy your EVM account address at the top right of your profile information.
Next, let's paste the address in the constructor argument `initialOwner` for our ERC-20 contract. This address will be assigned the role of minting new tokens.
Click the "Deploy" button to deploy the contract. Once your contract is deployed, you get an interface where you can interact with your smart contract.
Let's use the interface to mint a new token. Expand the mint function, and let's mint a token for ourselves. Paste your EVM address in the `to` field and set the amount equal to 1.
**Expanding the output area allows you to check the status of your function call. If you see a status message "SUCCESS," you've successfully minted an ERC-20 token on Hedera.**
# Deploy and Verify a Smart Contract with Foundry
Source: https://docs.hedera.com/evm/quickstart/deploy-with-foundry
## Deploying a Contract Using Foundry
This tutorial will walk you through writing and compiling an ERC-20 Solidity smart contract. You'll then deploy and interact with it on the Hedera network using the [Hedera Smart Contract Service (HSCS)](/support/glossary#hedera-smart-contract-service-hscs) and [Foundry](https://getfoundry.sh/), connecting via the [JSON-RPC relay](/evm/development/json-rpc).
**Developing or running CI?** [Solo](https://solo.hiero.org/docs/) runs a full Hedera network locally, no testnet rate limits, faucet, or resets, and works with the same EVM tooling. See the [Solo quickstart](https://solo.hiero.org/docs/simple-solo-setup/quickstart/) and [Using Solo with EVM tools](https://solo.hiero.org/docs/using-solo/using-solo-with-evm-tools/).
#### What you will accomplish
By the end of this tutorial, you will be able to:
* Compile and deploy a smart contract using Foundry
* Interact with a smart contract using Foundry's `cast` command
* Verify your smart contract programmatically with `forge verify-contract`
***
## Prerequisites
Before you begin, you should have **completed** the following tutorial:
* [x] [Create and Fund a Testnet Account via the Hedera Faucet](/evm/quickstart/get-test-hbar)
* [x] [Install Foundry](https://getfoundry.sh/)
```bash theme={null}
curl -L https://foundry.paradigm.xyz | bash
foundryup
```
This will install `forge`, `cast`, `anvil`, and `chisel`.
***
## Table of Contents
1. [Step 1: Project Setup](#step-1%3A-project-setup)
2. [Step 2: Creating the ERC20 Contract](#step-2%3A-creating-the-erc20-contract)
3. [Step 3: Create a Deployment Script](#step-3%3A-create-a-deployment-script)
4. [Step 4: Deploy your ERC20 Smart Contract](#step-4%3A-deploy-your-erc20-smart-contract)
5. [Step 5: Interacting with the Contract](#step-5%3A-interacting-with-the-contract)
6. [Step 6: Verify Your Smart Contract with Foundry](#step-6%3A-verify-your-smart-contract-with-foundry)
***
## Step 1: Project Setup
#### Initialize Project
Set up your Foundry project:
```bash theme={null}
forge init hedera-foundry-erc20-tutorial
cd hedera-foundry-erc20-tutorial
```
This creates a new directory with a standard Foundry project structure, including `src`, `test`, and `script` folders.
#### Install Dependencies
Foundry uses git submodules to manage dependencies. We'll install the OpenZeppelin Contracts library, which provides a standard and secure implementation of the ERC20 token.
```bash theme={null}
forge install OpenZeppelin/openzeppelin-contracts
```
This command will download the contracts and add them to your `lib` folder.
#### Create `.env` File
Create a `.env` file in your project's root directory to securely store your private key and the RPC URL for the Hedera Testnet.
```bash theme={null}
touch .env
```
Securely store your sensitive data like the `OPERATOR_KEY` in a `.env` file. For the JSON `RPC_URL`, we'll use the [Hashio RPC endpoint for testnet](https://www.hashgraph.com/hashio/).
```bash .env theme={null}
HEDERA_RPC_URL=https://testnet.hashio.io/api
HEDERA_PRIVATE_KEY=0x-your-private-key
```
Replace the `0x-your-private-key` environment variable with the **HEX Encoded
Private Key** for your **ECDSA** **account** from the [Hedera
Portal](https://portal.hedera.com/).
***Please note**:* *that Hashio is intended for development and testing
purposes only. For production use cases, it's recommended to use
commercial-grade JSON-RPC Relay or host your own instance of the* [*Hiero
JSON-RPC Relay*](https://github.com/hiero-ledger/hiero-json-rpc-relay)*.*
#### Configure Foundry
Foundry uses the `foundry.toml` file for configuration. Open it and add profiles for the Hedera Testnet RPC endpoint.
```toml foundry.toml theme={null}
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
remappings = [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"forge-std/=lib/forge-std/src/"
]
# Add this section for Hedera testnet
[rpc_endpoints]
testnet = "${HEDERA_RPC_URL}"
```
Note the values in `remappings` field. We need this to import prefix to a filesystem path so both Foundry(forge) and our editor can resolve short, package-like imports instead of long relative paths.
***
## Step 2: Creating the ERC20 Contract
Create a new Solidity file (`HederaToken.sol` ) inside the `src` directory:
```solidity src/HederaToken.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract HederaToken is ERC20, Ownable {
constructor(address initialOwner)
ERC20("HederaToken", "HEDT")
Ownable(initialOwner)
{
_mint(msg.sender, 1000 * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
```
The contract uses the OpenZeppelin ERC20 and Ownable implementations.
* The token is named "HederaToken" with the symbol "HEDT"
* The `constructor` mints an initial supply of 1,000 tokens to the deployer of the contract
* An `onlyOwner` `mint` function is included to allow the contract owner to mint more tokens in the future.
Now, compile the contract:
```bash theme={null}
forge build
```
This command compiles your contracts and places the artifacts(including the [ABI](/evm/development/compiling) and bytecode) into the `out` directory. We are now ready to deploy the smart contract.
***
## Step 3: Create a Deployment Script
Using a script is the standard and most reliable way to handle deployments in Foundry. Create a new file named `HederaToken.s.sol` inside the `script` directory.
```solidity script/HederaToken.s.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {Script, console} from "forge-std/Script.sol";
import {HederaToken} from "../src/HederaToken.sol";
contract HederaTokenScript is Script {
function run() external returns (address) {
// Load the private key from the .env file
uint256 deployerPrivateKey = vm.envUint("HEDERA_PRIVATE_KEY");
// Start broadcasting transactions with the loaded private key
vm.startBroadcast(deployerPrivateKey);
// Get the deployer's address to use as the initial owner
address deployerAddress = vm.addr(deployerPrivateKey);
// Deploy the contract
HederaToken hederaToken = new HederaToken(deployerAddress);
// Stop broadcasting
vm.stopBroadcast();
console.log("HederaToken deployed to:", address(hederaToken));
return address(hederaToken);
}
}
```
***
## Step 4: Deploy Your ERC20 Smart Contract
Now, execute the script to deploy your contract. Foundry will automatically load the variables from your `.env` file.
```bash theme={null}
forge script script/HederaToken.s.sol:HederaTokenScript --rpc-url testnet --broadcast
```
After a few moments, you will see the address of your newly deployed contract:
```
[⠒] Compiling...
[⠒] Sending transaction...
[⠒] Waiting for receipt...
== Logs ==
HederaToken deployed to: 0x047f8c7569b9beecab790902ba29daad143041d7
```
Copy the deployed contract address. You'll need this in subsequent steps.
***
## Step 5: Interacting with the Contract
Now that the contract is deployed, you can interact with it using `cast`, Foundry's command-line tool for making RPC calls.
To use `cast` and other command-line tools, you need to load the variables from your `.env` file into your current terminal session.
**Load Environment Variables**
Run the following command to load the `HEDERA_PRIVATE_KEY` and `HEDERA_RPC_URL` into your shell:
```bash theme={null}
source .env
```
Now your shell knows the value of `$HEDERA_PRIVATE_KEY` and `$HEDERA_RPC_URL`.
**Set Up Shell Variables**
Next, set up variables for your contract address and public address to make the next commands easier to read. Please export these variables in your shell.
```bash theme={null}
# Replace with the contract address from the previous step
export CONTRACT_ADDRESS=
# Derive your public address from the private key
export MY_ADDRESS=$(cast wallet address $HEDERA_PRIVATE_KEY)
```
**Check Your Balance**
Let's call the `balanceOf` function to check the token balance of your account.
```bash theme={null}
cast call $CONTRACT_ADDRESS "balanceOf(address)" $MY_ADDRESS --rpc-url $HEDERA_RPC_URL
```
The output will be the balance in its raw form (with 18 decimals):
```
0x00000000000000000000000000000000000000000000003635c9adc5dea00000
```
You can use the following to convert the hexadecimal to a decimal number so it's human readable.
```bash theme={null}
cast --to-dec 0x00000000000000000000000000000000000000000000003635c9adc5dea00000
```
You should see the value `1000000000000000000000`.
**Transfer Tokens**
Next, let's transfer 100 tokens to a new account. For this example, we'll generate a new random private key.
```bash theme={null}
# Generate a new random private key and get its address
export RECIPIENT_ADDRESS=$(cast wallet address $(openssl rand -hex 32))
echo "Recipient Address: $RECIPIENT_ADDRESS"
```
Now, send 100 tokens to this new address. Note that `100e18` is a convenient way to write `100 * 10^18`.
```bash theme={null}
cast send $CONTRACT_ADDRESS "transfer(address,uint256)" $RECIPIENT_ADDRESS 100e18 \
--private-key $HEDERA_PRIVATE_KEY \
--rpc-url $HEDERA_RPC_URL
```
After the transaction confirms, check the recipient's balance:
```bash theme={null}
cast call $CONTRACT_ADDRESS "balanceOf(address)" $RECIPIENT_ADDRESS --rpc-url testnet
```
The output will show the 100 tokens you sent:
```
0x0000000000000000000000000000000000000000000000056bc75e2d63100000
```
***
## Step 6: Verify Your Smart Contract with Foundry
Foundry can verify your contract programmatically straight from the command line via `forge verify-contract`. Programmatic verification is the most reliable and efficient method, especially for complex or upgradeable contracts, because Foundry already knows your exact compilation settings, dependency graph, and deployment artifacts.
`forge verify-contract` submits the verification request to [Sourcify](https://sourcify.dev), which natively supports Hedera Mainnet (chain ID `295`) and Testnet (chain ID `296`). Once Sourcify accepts the match, the verified status surfaces automatically on [HashScan](https://hashscan.io/) and any other explorer that reads from Sourcify.
The constructor for this contract takes one argument (`initialOwner`), which we must provide for successful verification. Run the following command, using the variables you set earlier.
```bash theme={null}
forge verify-contract $CONTRACT_ADDRESS src/HederaToken.sol:HederaToken \
--chain-id 296 \
--verifier sourcify \
--verifier-url "https://sourcify.dev/server" \
--constructor-args $(cast abi-encode "constructor(address)" $MY_ADDRESS)
```
After running the command, you should see a success message.
```
Submitting verification for [HederaToken] "0x047F8c7569B9beECaB790902BA29DaAD143041d7".
Contract successfully verified
```
**Congratulations! 🎉 You have successfully deployed, interacted with, and verified an ERC20 smart contract on the Hedera Testnet using Foundry. Feel free to reach out in** [**Discord**](https://hedera.com/discord)**!**
***
## Next Steps
* Check out [OpenZeppelin ERC-20 Documentation](https://docs.openzeppelin.com/contracts/5.x/erc20)
* See the full code in the [Hedera-Code-Snippets Repository](https://github.com/hedera-dev/hedera-code-snippets/tree/main/foundry-erc20)
* Follow more [Foundry guides with Hedera](/evm/tools/foundry).
[Github](https://github.com/kpachhai) | [Linkedin](https://www.linkedin.com/in/kiranpachhai/)
[GitHub](https://github.com/LukeForrest-Hashgraph) | [X](https://x.com/_LukeForrest)
# Deploy and Verify a Smart Contract with Hardhat
Source: https://docs.hedera.com/evm/quickstart/deploy-with-hardhat
## Deploying a Contract Using Hardhat Scripts
This tutorial will walk you through writing and compiling an ERC-721 Solidity smart contract. You'll then deploy and interact with it on the Hedera network using the [Hedera Smart Contract Service (HSCS)](/support/glossary#hedera-smart-contract-service-hscs) and familiar EVM tools like Ethers.js, connecting via the [JSON-RPC relay](/evm/development/json-rpc).
**Developing or running CI?** [Solo](https://solo.hiero.org/docs/) runs a full Hedera network locally, no testnet rate limits, faucet, or resets, and works with the same EVM tooling. See the [Solo quickstart](https://solo.hiero.org/docs/simple-solo-setup/quickstart/) and [Using Solo with EVM tools](https://solo.hiero.org/docs/using-solo/using-solo-with-evm-tools/).
#### What you will accomplish
By the end of this tutorial, you will be able to:
* Compile and deploy a smart contract using Hardhat
* Interact with a smart contract using Hardhat
**Note:** This tutorial is currently supported only in the Getting Started [JavaScript](https://github.com/hedera-dev/hello-future-world-js) series and is not available for other languages.
***
## Prerequisites
Before you begin, you should have **completed** the following tutorial:
* [Create and Fund a Testnet Account via the Hedera Faucet](/evm/quickstart/get-test-hbar)
***
## Table of Contents
1. [Step 1: Project Setup](#step-1%3A-project-setup)
2. [Step 2: Creating the ERC-721 Contract](#step-2%3A-creating-the-erc-721-contract)
3. [Step 3: Deploy Your ERC-721 Smart Contract](#step-3%3A-deploy-your-erc-721-smart-contract)
4. [Step 4: Minting an ERC-721 Token](#step-4%3A-minting-an-erc-721-token)
5. [Step 5: Verify Your Smart Contract with Hardhat](#step-5%3A-verify-your-smart-contract-with-hardhat)
***
## Step 1: Project Setup
#### Initialize Project
Set up your project by initializing the hardhat project:
```bash theme={null}
mkdir hardhat-erc-721-mint
cd hardhat-erc-721-mint
npx hardhat --init
```
Make sure to select "**Hardhat 3 → Typescript Hardhat Project using Mocha and Ethers.js"** and accept the default values. Hardhat will configure your project correctly and install the required dependencies.
Key differences in Hardhat 3:
* **compile → build**\
`npx hardhat compile` is now `npx hardhat build`. This is the big one. The v3 migration guide explicitly shows using the `build` task.
* **project init switch**\
v2 commonly used `npx hardhat` or `npx hardhat init` to bootstrap. In v3 it’s `npx hardhat --init`.
* **keystore helper commands are new**\
v3’s recommended flow includes a keystore plugin with commands like `npx hardhat keystore set HEDERA_RPC_URL` and `npx hardhat keystore set HEDERA_PRIVATE_KEY`. These weren’t standard in v2.
* **Foundry-compatiable Solidity tests**\
In addition to offering Javascript/Typescript integration tests, Hardhat v3 also integrates Foundry-compatible Solidity tests that allows developers to write unit tests directly in Solidity
* **Enhanced Network Management**\
v3 allows tasks to create and manage multiple network connections simultaneously which is a significant improvement over the single, fixed connection available in version 2. This provides greater flexibility for scripts and tests that interact with multiple networks.
*📚 Learn more from the official* [*Hardhat documentation*](https://hardhat.org/docs/getting-started)*.*
#### Install Dependencies
Next, install the required dependencies:
```bash theme={null}
npm install @openzeppelin/contracts
```
Before we make any changes to our Hardhat configuration file, let's set some configuration variables we will be referring to within the file later.
When you set up your keystore for the first time, you’ll be asked to create a keystore password. Save this password securely because you’ll need it anytime you set or reset a variable.
```bash theme={null}
# If you have already set this before, please use the --force flag
npx hardhat keystore set HEDERA_RPC_URL
```
For `HEDERA_RPC_URL`, we'll have `https://testnet.hashio.io/api`
```bash theme={null}
# If you have already set this before, please use the --force flag
npx hardhat keystore set HEDERA_PRIVATE_KEY
```
For `HEDERA_PRIVATE_KEY`, enter the **HEX Encoded Private Key for your ECDSA account** from the [Hedera Portal.](https://portal.hedera.com/)
#### Note
[*Hashio*](https://www.hashgraph.com/hashio/) *is intended for development and testing purposes only. For production use cases, it's recommended to use commercial-grade JSON-RPC Relay or host your own instance of the* [*Hiero JSON-RPC Relay*](https://github.com/hiero-ledger/hiero-json-rpc-relay)*.*
#### Configure Hardhat
Update your `hardhat.config.ts` file in the root directory of your project. This file contains the network settings so Hardhat knows how to interact with the Hedera Testnet. We'll use the variables you've stored in your `.env` file.
```typescript hardhat.config.ts theme={null}
import type { HardhatUserConfig } from "hardhat/config";
import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import { configVariable } from "hardhat/config";
const config: HardhatUserConfig = {
plugins: [hardhatToolboxMochaEthersPlugin],
solidity: {
profiles: {
default: {
version: "0.8.28",
},
production: {
version: "0.8.28",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
},
},
networks: {
testnet: {
type: "http",
url: configVariable("HEDERA_RPC_URL"),
accounts: [configVariable("HEDERA_PRIVATE_KEY")],
},
},
};
export default config;
```
You can verify the connection by running:
```bash theme={null}
npx hardhat console --network testnet
```
This command launches an interactive JavaScript console connected directly to the Hedera testnet, providing access to the [Ethers.js library](https://docs.ethers.org/v6/) for blockchain interactions. If you successfully enter this interactive environment, your Hardhat configuration is correct. To exit the interactive console, press `ctrl + c` twice.
We won't be using `ignition` and we will be removing the default contracts that come with the Hardhat default project, so we will remove all the unnecessary directories and files first:
```bash theme={null}
rm -rf contracts/* scripts/* test/*
rm -rf ignition
```
***
## Step 2: Creating the ERC-721 Contract
Create a new Solidity file (`MyToken.sol`) in our `contracts` directory:
```solidity contracts/MyToken.sol theme={null}
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.28;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC721, Ownable {
uint256 private _nextTokenId;
constructor(address initialOwner)
ERC721("MyToken", "MTK")
Ownable(initialOwner)
{}
function safeMint(address to) public onlyOwner returns (uint256) {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
return tokenId;
}
}
```
This contract was created using the [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/#erc721) and OpenZeppelin's ERC-721 standard implementation with an ownership model. The ERC-721 token's name has been set to "MyToken." The contract implements the `safeMint` function, which accepts the address of the owner of the new token and uses auto-increment IDs, starting from 0.
Let's compile this contract by running:
```bash theme={null}
npx hardhat build
```
This command will generate the smart contract artifacts, including the [ABI](/evm/development/compiling). We are now ready to deploy the smart contract.
***
## Step 3: Deploy Your ERC-721 Smart Contract
Create a deployment script (`deploy.ts`) in `scripts` directory:
```typescript scripts/deploy.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "testnet",
});
async function main() {
// Get the signer of the tx and address for minting the token
const [deployer] = await ethers.getSigners();
console.log("Deploying contract with the account:", deployer.address);
// The deployer will also be the owner of our NFT contract
const MyToken = await ethers.getContractFactory("MyToken", deployer);
const contract = await MyToken.deploy(deployer.address);
await contract.waitForDeployment();
const address = await contract.getAddress();
console.log("Contract deployed at:", address);
}
main().catch(console.error);
```
In this script, we first retrieve your account (the deployer) using Ethers.js. This account will own the deployed smart contract. Next, we use this account to deploy the contract by calling `MyToken.deploy(deployer.address)`. This passes your account address as the initial owner and signer of the deployment transaction.
Deploy your contract by executing the script:
```bash theme={null}
npx hardhat run scripts/deploy.ts --network testnet
```
Copy the deployed address. You'll need this in subsequent steps.
The output looks like this:
```bash theme={null}
~/projects/hardhat-erc-721-mint-burn >> npx hardhat run scripts/deploy.ts --network testnet
Compiling your Solidity contracts...
Compiled 1 Solidity file with solc 0.8.28 (evm target: cancun)
Deploying contract with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Contract deployed at: 0x6035bA3BCa9595637B463Aa514c3a1cE3f67f3de
```
***
## Step 4: Minting an ERC-721 Token
Create a `mint.ts` script in your `scripts` directory to mint an NFT. Don't forget to replace the `` with the address you've just copied.
```typescript scripts/mint.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "testnet",
});
async function main() {
const [deployer] = await ethers.getSigners();
// Get the ContractFactory of your MyToken ERC-721 contract
const MyToken = await ethers.getContractFactory("MyToken", deployer);
// Connect to the deployed contract
// (REPLACE WITH YOUR CONTRACT ADDRESS)
const contractAddress = "";
const contract = MyToken.attach(contractAddress);
// Mint a token to ourselves
const mintTx = await contract.safeMint(deployer.address);
const receipt = await mintTx.wait();
console.log("receipt: ", JSON.stringify(receipt, null, 2));
const mintedTokenId = receipt?.logs[0].topics[3];
console.log("Minted token ID:", mintedTokenId);
// Check the balance of the token
const balance = await contract.balanceOf(deployer.address);
console.log("Balance:", balance.toString(), "NFTs");
}
main().catch(console.error);
```
The code mints a new NFT to your account ( `deployer.address` ). Then we verify the balance to see if we own an ERC-721 token of type `MyToken`.
**Mint an NFT:**
```bash theme={null}
npx hardhat run scripts/mint.ts --network testnet
```
**Expected output:**
```json wrap theme={null}
~/projects/hardhat-erc-721-mint-burn >> npx hardhat run scripts/mint.ts --network testnet
Compiling your Solidity contracts...
Nothing to compile
receipt: {
"_type": "TransactionReceipt",
"blockHash": "0x110b2de909e2f4d515b76de4ffd7a8a9f4c3e68c79f8aa083f9baf2a7d082a5c",
"blockNumber": 23836191,
"contractAddress": "0x6035bA3BCa9595637B463Aa514c3a1cE3f67f3de",
"cumulativeGasUsed": "800000",
"from": "0xA98556A4deeB07f21f8a66093989078eF86faa30",
"gasPrice": "350000000000",
"blobGasUsed": null,
"blobGasPrice": null,
"gasUsed": "800000",
"hash": "0xb0a67ee89e224208599b29a71bc5de1abc5aba4cf64553893aaf0aeb051f7a91",
"index": 9,
"logs": [
{
"_type": "log",
"address": "0x6035bA3BCa9595637B463Aa514c3a1cE3f67f3de",
"blockHash": "0x110b2de909e2f4d515b76de4ffd7a8a9f4c3e68c79f8aa083f9baf2a7d082a5c",
"blockNumber": 23836191,
"data": "0x",
"index": 0,
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x000000000000000000000000a98556a4deeb07f21f8a66093989078ef86faa30",
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
"transactionHash": "0xb0a67ee89e224208599b29a71bc5de1abc5aba4cf64553893aaf0aeb051f7a91",
"transactionIndex": 9
}
],
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000002001000000000000000000000000000000020000000000000000000800000000000000000000000010000000000000000000000400000000020000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000",
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"status": 1,
"to": "0x6035bA3BCa9595637B463Aa514c3a1cE3f67f3de"
}
Minted token ID: 0x0000000000000000000000000000000000000000000000000000000000000000
Balance: 1 NFTs
```
***
## Step 5: Verify Your Smart Contract with Hardhat
After deploying your smart contract, you can verify the source code programmatically from Hardhat. Programmatic verification is the most reliable and efficient method, especially for complex or upgradeable contracts, because Hardhat already knows your exact compilation settings, dependency graph, and deployment artifacts.
Hardhat submits the verification request to [Sourcify](https://sourcify.dev), which natively supports Hedera Mainnet (chain ID `295`) and Testnet (chain ID `296`). Once Sourcify accepts the match, the verified status surfaces automatically on [HashScan](https://hashscan.io/) and any other explorer that reads from Sourcify.
### Install the Verification Plugin
Hardhat's official [`@nomicfoundation/hardhat-verify`](https://hardhat.org/hardhat-runner/plugins/nomicfoundation-hardhat-verify) plugin supports Sourcify out of the box.
Install the plugin in your project:
```bash theme={null}
npm i -D @nomicfoundation/hardhat-verify
```
### Configure Hardhat for Verification
Import the plugin in your `hardhat.config.ts` file and enable Sourcify verification. Because Hedera is supported on the default Sourcify server (`https://sourcify.dev/server`), no custom `apiUrl` is needed.
```typescript theme={null}
import hardhatVerify from "@nomicfoundation/hardhat-verify"; // <--- ADD THIS LINE
import type { HardhatUserConfig } from "hardhat/config";
import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import { configVariable } from "hardhat/config";
const config: HardhatUserConfig = {
plugins: [hardhatToolboxMochaEthersPlugin, hardhatVerify], // ADD TO PLUGIN LIST
solidity: {
profiles: {
default: {
version: "0.8.28"
},
production: {
version: "0.8.28",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
}
}
},
networks: {
testnet: {
type: "http",
url: configVariable("HEDERA_RPC_URL"),
accounts: [configVariable("HEDERA_PRIVATE_KEY")],
chainId: 296
}
},
sourcify: {
enabled: true
}
};
export default config;
```
### Run the Verification Command
The plugin adds the `verify` task to Hardhat. The basic command structure is:
```bash theme={null}
npx hardhat verify --network [constructor_args...]
```
### Example: Verifying Your MyToken Contract
Using the contract address from your deployment, pass the deployer address (the `initialOwner` constructor argument) at the end of the command:
```bash theme={null}
# Replace with your actual deployed contract address
CONTRACT_ADDRESS="0x6035bA3BCa9595637B463Aa514c3a1cE3f67f3de"
DEPLOYER_ADDRESS="0xA98556A4deeB07f21f8a66093989078eF86faa30"
npx hardhat verify --network testnet "$CONTRACT_ADDRESS" "$DEPLOYER_ADDRESS"
```
The plugin uploads the source code and metadata to Sourcify and you should receive a **Full Match** verification status. The verified contract will then appear on HashScan automatically.
**Troubleshooting Verification**
| **Issue** | **Solution** |
| :------------------------------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Verification Fails/Mismatch | Ensure your local compilation settings (Solidity version, optimizer runs, viaIR) exactly match the settings used for deployment. Run `npx hardhat clean && npx hardhat build` before retrying. |
| Constructor Arguments Error | If your contract has constructor arguments, pass them as positional arguments after the contract address in the verification command. |
| Hardhat Keystore Password Prompt | The task may prompt for your Hardhat keystore password if it needs to sign a transaction to read deployment details. Enter it when prompted. |
**Congratulations! 🎉 You have successfully learned how to deploy and verify an ERC-721 smart contract using Hardhat, OpenZeppelin, and Ethers. Feel free to reach out in** [**Discord**](https://hedera.com/discord)**!**
***
## Next Steps
* Learn how to add [Access Control, Pause, and Transfer ERC-721 ](/evm/tutorials/advanced/erc721-hardhat/part2-access-control)tokens
* Check out [OpenZeppelin ERC-721 Documentation](https://docs.openzeppelin.com/contracts/5.x/erc721)
* See the full code in the [Hedera-Code-Snippets Repository](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hardhat-erc-721-mint-burn)
[Github](https://github.com/kpachhai) | [Linkedin](https://www.linkedin.com/in/kiranpachhai/)
[GitHub](https://github.com/LukeForrest-Hashgraph) | [X](https://x.com/_LukeForrest)
[Github](https://github.com/theekrystallee) | [X](https://x.com/theekrystallee)
[GitHub](https://github.com/quiet-node) | [LinkedIn](https://www.linkedin.com/in/logann131/)
[GitHub](https://github.com/jaycoolh) | [X](https://x.com/jaycoolh)
[GitHub](https://github.com/michielmulders) |
[LinkedIn](https://www.linkedin.com/in/michielmulders/)
# Deploy a Smart Contract with Remix
Source: https://docs.hedera.com/evm/quickstart/deploy-with-remix
A step-by-step tutorial on how to create and deploy a smart contract on the Hedera network using Remix IDE.
## Introduction to Remix IDE
Remix IDE is an open-source tool for developing smart contracts in Solidity. It was originally built for the Ethereum network and supports deploying to EVM-compatible networks like Hedera. Remix includes built-in tools for compiling, debugging, and deploying contracts directly from the browser.
In this tutorial, you’ll use Remix IDE to write and deploy a simple smart contract to the Hedera testnet.
**Developing or running CI?** [Solo](https://solo.hiero.org/docs/) runs a full Hedera network locally, no testnet rate limits, faucet, or resets, and works with the same EVM tooling. See the [Solo quickstart](https://solo.hiero.org/docs/simple-solo-setup/quickstart/) and [Using Solo with EVM tools](https://solo.hiero.org/docs/using-solo/using-solo-with-evm-tools/).
***
## Prerequisites
* Web browser with access to [Remix IDE](https://remix.ethereum.org/)
* [Download](https://metamask.io/download/) the MetaMask wallet browser extension
***
## Add Hedera Testnet to MetaMask
Smart contracts deployed on Hedera are compatible with EVM wallets like MetaMask. To interact with the network, you must first add Hedera’s JSON-RPC endpoint as a custom network in your wallet. Click on the button below for a one-click configuration.
1. Open MetaMask and click the network selection dropdown at the top of the extension.
2. Click **Add Network**, then **Add Network Manually**
3. Enter the following network details:
* **Network Name**: Hedera Testnet
* **New RPC URL**: `https://testnet.hashio.io/api`
* **Chain ID**: `296`
* **Currency Symbol**: `HBAR`
* **Block Explorer URL**: `https://hashscan.io/testnet`
4. Tap the **Save** button to save the Hedera Testnet
***
## Fund Your Hedera Testnet Account
Navigate to the [Hedera Faucet](https://portal.hedera.com/faucet) to get testnet HBAR tokens necessary for deploying a smart contract.
***
## Deploy a Smart Contract Using Remix
Open your web browser and navigate to Remix IDE. Click on the file icon in the **File Explorer** tab to create a new file and name it `HelloHedera.sol` .
Copy and paste this sample contract to the new file you created:
```solidity HelloHedera.sol theme={null}
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
contract SampleContract {
string public myString = "Hello Hedera";
function updateString(string memory _newString) public {
myString = _newString;
}
}
```
Navigate to the **Solidity Compiler** tab in the left sidebar and check that your compiler version is within the versions specified in the `pragma solidity` statement. Then, compile your `HelloHedera.sol` contract.
When a compilation for a Solidity file succeeds, Remix creates three JSON files for each compiled contract. **Files can be seen in the `File Explorers plugin` as:**
* *`artifacts/.json`: contains the link to the libraries, the bytecode, the deployed bytecode, the gas estimation, the method identifiers, and the ABI. It is used for linking a library address to the file.*
* *`artifacts/.json`: contains the metadata from the output of Solidity compilation.*
* *`artifacts/build-info/.json`: contains info about `solc` compiler version, compiler input and output. This file is generated similar to the files generated through Hardhat compilation. You can also try* [*Hardhat compilation*](https://remix-ide.readthedocs.io/en/latest/hardhat.html#enable-hardhat-compilation) *from Remix.*
*Please note that to generate these artifact files, the **Generate contract metadata** box in the **General settings** section of the **Settings** module needs to be checked. By default, it is checked.*
* Go to the **Deploy & Run Transactions** tab and
* Select **Injected Provider - MetaMask** as the environment
If you're not signed into your MetaMask account, a window will pop up prompting you to log in. Sign in and make sure you're connected to the **Hedera Testnet** and verify that the network is configured correctly to **Custom (296) network**.
Once you click **Deploy** in the **Deploy & Run Transactions** tab, hit **Confirm** in the MetaMask notification window to approve and pay for the contract deployment transaction.
Once the transaction is successful, you can interact with the smart contract through Remix. Select the dropdown on the newly deployed contract at the bottom of the left panel to view the contract's functions under **Deployed Contracts**. Write a new message to the `updateString` function using the input and confirm the write transaction in the MetaMask window to pay.
Copy the contract address from the Deployed Contracts window.
Navigate to the [HashScan](https://hashscan.io/) network explorer and use the contract address to search for your contract to view the details.
**Congratulations 🎉 Your smart contract is live on Hedera testnet!**
You've successfully:
* Configured MetaMask for Hedera testnet
* Created and funded a testnet account
* Deployed a smart contract using Remix
* Interacted with it on the Hedera testnet
***
## Next Step: Verify Your Smart Contract
If you're up for it, you can verify your deployed contract using the Smart Contract Verifier tool on HashScan network explorer.
***
## Additional Resources
* [**Remix IDE Documentation**](https://remix-ide.readthedocs.io/en/latest/)
* [**Hedera Contract Builder**](https://portal.hedera.com/contract-builder)
* [**Smart Contracts Documentation**](/learn/core-concepts/services/smart-contracts)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
# Hedera Testnet Faucet
Source: https://docs.hedera.com/evm/quickstart/get-test-hbar
Get testnet HBAR using the Hedera Portal or faucet: fund an EVM wallet address instantly or create a portal account with a stable Account ID and keys.
There are two ways to get testnet HBAR:
* **Hedera Portal**: [create a portal account](https://portal.hedera.com) for a fully provisioned testnet account with a stable Account ID, private key, and access to all developer tools. No EVM wallet required.
* **Faucet**: paste any EVM wallet address to instantly fund an account. No portal account needed.
The faucet auto-creates a Hedera account when you enter an EVM wallet address for the first time.
To use the faucet, head to the [faucet](https://portal.hedera.com/faucet) landing page.
* Enter your EVM wallet address in the **Enter Wallet Address** field and
* Click the **RECEIVE 100 TESTNET HBAR** button to initiate an [auto account creation](/learn/core-concepts/accounts/auto-account-creation) flow that creates and funds a new testnet account
#### ⚠️ **Important**
When you use an EVM wallet address for the first time, [**Auto Account Creation**](/learn/core-concepts/accounts/auto-account-creation) kicks in to establish a new Hedera account linked to your EVM address.
This process creates a **hollow account**, an account with an Account ID and your EVM address set as its alias, but no signing key on record yet. Hollow accounts can receive HBAR and tokens, but cannot transfer funds or modify account properties until completed.
To complete the account, use it as the **fee payer** in a transaction and sign with the ECDSA private key corresponding to your EVM address. Once completed, it behaves like any regular Hedera account.
For a full explanation of the Hedera account model, hollow accounts, and accounts without an ECDSA key, see [Account Model for EVM Developers](/evm/development/accounts).
## Environment Variable Setup (Optional)
This section is for developers who want to set up their environment for production use. If you plan to use Hardhat, Foundry, or other development frameworks, complete this step to configure your environment variable. Skip if you're just getting started.
If you plan to use Hardhat, Foundry, or other development frameworks, you'll want to set up environment variables:
1. **Export your private key from MetaMask:**
1. Click the **three dots menu** → **Account details** → **Show private key**
2. Copy the private key (64-character hex string)
For detailed instructions on exporting your private key, refer to [this how-to
guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/).
Keep your private keys secure. Anyone with access to them can control your
wallet and any funds.
2. **Create a `.env` file** in your project directory with your account credentials
```
# private key exported from MetaMask
OPERATOR_KEY=0xc89f760d43832...
# new testnet account ID
OPERATOR_ID=0.0.1234
# Hedera testnet RPC endpoint
RPC_URL=https://testnet.hashio.io/api
```
#### Warning
Storing private keys in a `.env` file is not considered best practice. There is always a risk of accidentally committing and pushing to a public GitHub repo and exposing your keys. Make it a habit to add `.env` to your `.gitignore` file as a precautionary measure.
We **highly advise against** using a private key with mainnet funds.
**Building locally?** [Solo](https://solo.hiero.org/docs/) lets you run a full Hedera network on your own machine — no faucet, no resets, no throttles. It works with MetaMask, Hardhat, and Foundry out of the box. See the [Solo quickstart](https://solo.hiero.org/docs/simple-solo-setup/quickstart/) or [Using Solo with EVM tools](https://solo.hiero.org/docs/using-solo/using-solo-with-evm-tools/) to get started.
## Next Step
* [Deploy a Smart Contract Using Remix](/evm/quickstart/deploy-with-remix)
* [Deploy your First Contract with Contract Builder](/evm/quickstart/deploy-with-contract-builder)
# Hedera Contract Builder
Source: https://docs.hedera.com/evm/quickstart/portal-contract-builder
# Add Hedera to MetaMask
Source: https://docs.hedera.com/evm/quickstart/setup-metamask
Hedera is fully compatible with web3 wallets like MetaMask. Just add the JSON-RPC endpoint as a custom network.
## Mainnet
# ERC-1363 (Payable Tokens)
Source: https://docs.hedera.com/evm/tokens/erc1363
The [ERC-1363](https://erc1363.org/) standard, also known as the payable token standard, is an upgrade to [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) tokens. While ERC-20 tokens can only be sent from one person to another, ERC-1363 tokens can trigger actions in a smart contract immediately after being sent or approved for spending.
For example, if you use one of these tokens to pay for a subscription, the contract can instantly recognize the payment and activate your subscription with no extra steps. This makes the standard useful for quick transactions like buying services, paying invoices, or managing subscriptions, all in one easy step.
**Note:** Hedera’s system contract functions do not natively support `ERC-1363` functionalities on HTS tokens. However, standard `ERC-1363` functions can still be implemented within a smart contract and deployed on the network, similar to other EVM-compatible chains.
### **Interface `ERC-1363` Functions**
```solidity wrap theme={null}
function transferAndCall(address to, uint256 value, bytes calldata data) external returns (bool);
```
Transfers tokens and calls a function on the recipient contract in a single transaction.
```solidity wrap theme={null}
function approveAndCall(address spender, uint256 value, bytes calldata data) external returns (bool);
```
***
### Additional References
* [ERC-1363](https://erc1363.org/)
* [ERC-165](https://eips.ethereum.org/EIPS/eip-165)
* [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/)
* The original EIP can be found [here](https://eips.ethereum.org/EIPS/eip-1363)
For a more in-depth understanding, please see the below links:
#### 1. Function Implementations and Interactions
* **ERC-1363 Interface Specification**:
*Description*: This section defines the `ERC1363` interface, detailing functions like `transferAndCall` and `approveAndCall`, and explains how they interact with recipient contracts.\
*Link*: [ERC-1363 Interface Specification](https://erc1363.org/#specification)
#### 2. Supporting Contracts
* **IERC1363Receiver Interface**:
*Description*: Defines the interface for contracts that want to handle incoming token transfers, specifying the `onTransferReceived` function.\
*Link*: [IERC1363Receiver Interface](https://github.com/vittominacori/erc1363-payable-token/blob/master/contracts/token/ERC1363/IERC1363Receiver.sol)
* **IERC1363Spender Interface**:
*Description*: Specifies the interface for contracts that intend to handle token approvals, detailing the `onApprovalReceived` function.\
*Link*: [IERC1363Spender Interface](https://github.com/vittominacori/erc1363-payable-token/blob/master/contracts/token/ERC1363/IERC1363Spender.sol)
#### 3. Token Logic Examples
* **ERC-1363 Reference Implementation**:
*Description*: Provides a comprehensive implementation of the ERC-1363 standard, including how token transfers and approvals are handled with immediate contract interactions.\
*Link*: [ERC-1363 Reference Implementation](https://github.com/vittominacori/erc1363-payable-token/blob/master/contracts/token/ERC1363/ERC1363.sol)
#### 4. ERC-165 Compliance
* **ERC-165 Standard Overview**:
*Description*: Offers an understanding of the ERC-165 standard, which ERC-1363 utilizes to ensure recipient contracts implement the necessary interfaces. ERC-165 compliance is essential because it allows contracts to query whether a recipient implements required functions like `onTransferReceived` or `onApprovalReceived`, ensuring seamless interaction and preventing errors.\
*Link*: [ERC-165 Standard Overview](https://eips.ethereum.org/EIPS/eip-165)
#### 5. Practical Examples
* **ERC1363Payable Contract Example**:
*Description*: An example contract demonstrating how to accept ERC-1363 token transfers and approvals, including handling the `onTransferReceived` and `onApprovalReceived` functions.\
*Link*: [ERC1363Payable Contract Example](https://github.com/vittominacori/erc1363-payable-token/blob/master/contracts/examples/ERC1363Payable.sol)
***
**Contributor**: [**@sumanair** ](https://github.com/sumanair)
# ERC-20 (Fungible Tokens)
Source: https://docs.hedera.com/evm/tokens/erc20
The [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/) standard defines a set of functions and events that a token contract on the Ethereum blockchain should implement. ERC-20 tokens are fungible, meaning each token is identical and can be exchanged one-to-one.
**Note**: `ERC-20` Token addresses refer to full Hedera Token Service (HTS) fungible token entities. These tokens can be fully managed by HTS API calls. Additionally, by utilizing [`IERC20`](https://docs.openzeppelin.com/contracts/2.x/api/token/erc721#IERC721) interfaces or system contract functions, these tokens can also be managed by smart contracts on Hedera.
## Supported Functions
#### **From** I**nterface `ERC-20`**
```solidity theme={null}
function name() public view returns (string)
```
Returns the name of the token.
```solidity theme={null}
function symbol() public view returns (string)
```
Returns the symbol of the token.
```solidity theme={null}
function decimals() public view returns (uint8)
```
Returns the number of decimals the token uses.
```solidity theme={null}
function totalSupply() external view returns (uint256)
```
Returns the total supply of the token.
```solidity theme={null}
function balanceOf(address account) external view returns (uint256)
```
Returns of the balance of the token in the specified account. The `account` is the Hedera account ID `0.0.x` in Solidity address format or the evm address of a contract that has been created via the `CREATE2` operation.
```solidity wrap theme={null}
function allowance(address owner, address spender) external view returns (uint256)
```
Returns the remaining number of tokens that `spender` will be allowed to spend on behalf of `owner` through `transferFrom`. This is zero by default. This value changes when `approve` or `transferFrom` are called. This works by loading the owner `FUNGIBLE_TOKEN_ALLOWANCES` from the accounts ledger and returning the allowance approved for `spender` The `owner` and `spender` address are the account IDs (0.0.num) in solidity format.
```solidity theme={null}
function transfer(address recipient, uint256 amount) external returns (bool)
```
Transfer tokens from your account to a recipient account. The `recipient` is the Hedera account ID `0.0.x` in Solidity format or the EVM address of a contract that has been created via `CREATE2` operation.
```solidity wrap theme={null}
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)
```
Moves `amount` tokens from `from` to `to` using the allowance mechanism. `amount` is then deducted from the caller's allowance.
This works by creating a synthetic `CryptoTransferTransaction` with fungible token transfers with the `is_approval` property set to true.
```solidity theme={null}
function approve(address spender, uint256 amount) external returns (bool)
```
Sets `amount` as the allowance of `spender` over the caller's tokens.
This works by creating a synthetic `CryptoApproveAllowanceTransaction` with payer - the account that called the precompile (the message sender property of the message frame in the EVM).
Fires an approval event with the following signature when executed:
event Approval(address indexed owner, address indexed spender, uint256 value);
***
### **Additional References**
* [HIP-376](https://hips.hedera.com/hip/hip-376)
* [HIP-218](https://hips.hedera.com/hip/hip-218)
* [EIP-20](https://eips.ethereum.org/EIPS/eip-20)
# ERC-3643 (Real World Assets)
Source: https://docs.hedera.com/evm/tokens/erc3643
The [ERC-3643](https://docs.erc3643.org/erc-3643) token standard is variously known as "permissioned tokens", "real world asset tokens" or "T-REX (Token for Regulated EXchanges)". As the names suggest, ERC-3643 is designed to turn [real world assets (RWAs)](/support/glossary#real-world-asset-rwa), like company shares, loans, or real estate, into digital tokens that can be traded on the blockchain. Unlike regular tokens, it follow strict rules to make sure they meet legal requirements. With this standard, every token holder’s identity is verified to comply with regulations like [Know Your Customer (KYC)](/support/glossary#know-your-customer-kyc) and [Anti-Money Laundering (AML)](/support/glossary#anti-money-laundering-aml) laws, making it ideal for assets that need extra security and regulatory approval.
ERC-3643 tokens integrate identity management through ONCHAINID, where verified participant identities are securely stored on-chain. Token transfers follow strict compliance rules, ensuring regulatory requirements are met before execution. While enhancing security and compliance, ERC-3643 remains interoperable with existing ERC-20 platforms, enabling seamless integration into blockchain ecosystems.
**Note**: Hedera’s system contract functions do not natively support `ERC-3643` functionalities on HTS tokens. However, standard `ERC-3643` functions can still be implemented within a smart contract and deployed on the network, similar to other EVM-compatible chains.
### **Interface `ERC-3643` Functions**
```solidity theme={null}
function setOnchainID(address _onchainID) external;
```
Sets the token's onchain ID. Only the owner of the token contract can call this function.
```solidity theme={null}
function setIdentityRegistry(address _identityRegistry) external;
```
RWA tokens link to verified identities on-chain managed through a decentralized identity system.
```solidity theme={null}
function setIdentityRegistry(address _identityRegistry) external
```
`setIdentityRegistry` allow contract owners additional administrative functions to manage compliance and identity registry settings.
```solidity theme={null}
function setComplianceContract(address _compliance) external
```
`setComplianceContract` allow contract owners additional administrative functions to manage compliance and identity registry settings.
```solidity theme={null}
function forcedTransfer(
address _from,
address _to,
uint256 _amount
) external returns (bool);
```
Forces a transfer of tokens between two whitelisted addresses. Only an agent of the token can call this function.
***
### **Additional References**
* [ERC-3643](https://docs.erc3643.org/erc-3643)
* [ERC-20](https://ethereum.org/en/developers/docs/standards/tokens/erc-20/)
* The original EIP can be found [here](https://eips.ethereum.org/EIPS/eip-3643)
To get a deeper understanding of ERC-3643, see the following:
#### 1. Implementation Details and Function Interactions
For comprehensive implementations of functions like `setIdentityRegistry` and their interactions with compliance modules or identity registries, refer to the [Tokens Interface](https://docs.erc3643.org/erc-3643/smart-contracts-library/permissioned-tokens/tokens-interface) section.\
This section provides detailed function definitions and their roles within the ERC-3643 framework.
#### 2. Supporting Contracts
* **Identity Registry:**\
The [Identity Registry Interface](https://docs.erc3643.org/erc-3643/smart-contracts-library/onchain-identities/identity-registry/identity-registry-interface) section details the contract responsible for managing and verifying investor identities, ensuring compliance with KYC/AML regulations.
* **Compliance Management:**\
The [Compliance Interface](https://docs.erc3643.org/erc-3643/smart-contracts-library/compliance-management/compliance-interface) section outlines the contract that enforces compliance rules during token transfers, ensuring adherence to regulatory requirements.
#### 3. Token Logic Examples
For insights into token operations:
* Refer to the [Tokens Interface](https://docs.erc3643.org/erc-3643/smart-contracts-library/permissioned-tokens/tokens-interface) section for details on the `transfer` function implementation.\
This demonstrates how compliance rules are integrated into standard ERC-20-like operations.
#### 4. Forced Transfer Logic
For details on the `forcedTransfer` function, including permission checks and enforcement of whitelisting, refer to the [Tokens Interface](https://docs.erc3643.org/erc-3643/smart-contracts-library/permissioned-tokens/tokens-interface) section.
***
**Contributor**: [**@sumanair** ](https://github.com/sumanair)
# ERC-721 (Non-Fungible Tokens)
Source: https://docs.hedera.com/evm/tokens/erc721
The [ERC-721](https://ethereum.org/en/developers/docs/standards/tokens/erc-721/) standard introduces a [non-fungible token (NFT)](/support/glossary#non-fungible-token-nft) in which each issued token is unique and distinct from others. This standard defines functions and events that enable the creation, ownership, and transfer of non-fungible assets.
**Note**:`ERC-721` Token addresses refer to full Hedera Token Service (HTS) fungible token entities. These tokens can be fully managed by HTS API calls. Additionally, by utilizing [`IERC721`](https://docs.openzeppelin.com/contracts/2.x/api/token/erc721#IERC721) interfaces or system contract functions, these tokens can also be managed by smart contracts on Hedera.
## Supported Functions
#### **From** I**nterface `ERC-721`**
```solidity theme={null}
function ownerOf(uint256 _tokenId) external view returns (address)
```
Returns the account ID of the specified HTS token owner. The `_tokenId` is the Hedera serial number of the NFT.
```solidity theme={null}
function approve(address _approved, uint256 _tokenId) external payable
```
Gives the spender permission to transfer a token (`_tokenId`) to another account from the owner. The approval is cleared when the token is transferred. The `_tokenId` is the Hedera serial number of the NFT.
This works by creating a synthetic `CryptoApproveAllowanceTransaction` with payer - the account that called the precompile (the message sender property of the message frame in the EVM).
If the `spender` address is 0, this creates a `CryptoDeleteAllowanceTransaction` instead and removes any allowances previously approved on the token.
Fires an approval event with the following signature when executed:
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
`function setApprovalForAll(address _operator, bool _approved) external`
Approve or remove an `operator` as an operator for the caller. Operators can call `transferFrom` for any token owned by the caller.
This works by creating a synthetic `CryptoApproveAllowanceTransaction` with payer - the account that called the precompile (the message sender property of the message frame in the EVM).
`function getApproved(uint256 _tokenId) external view returns (address)`
```solidity theme={null}
```
Returns the account approved for the specified `_tokenId`. The `_tokenId` is the Hedera serial number of the NFT.
This works by loading the `SPENDER` property of the token from the NFTs ledger.
`function isApprovedForAll(address _owner, address _operator) external view returns (bool)`
```solidity theme={null}
```
Returns if the `operator` is allowed to manage all of the assets of `owner`.
This works by loading the `APPROVE_FOR_ALL_NFTS_ALLOWANCES` property of the owner account and verifying if the list of approved for all accounts contains the account id of the `operator`.
```solidity wrap theme={null}
function transferFrom(address _from, address _to, uint256 _tokenId) external payable
```
Transfers a token (`_tokenId`) from a Hedera account (`from`) to another Hedera account (`to`) in Solidity format. The `_tokenId` is the Hedera serial number of the NFT.
This works by creating a synthetic `CryptoTransferTransaction` with nft token transfers with the `is_approval` property set to true.
#### **From Interface `ERC721Metadata`**
```solidity theme={null}
function name() external view returns (string _name)
```
Returns the name of the HTS non-fungible token.
```solidity theme={null}
function symbol() external view returns (string _symbol)
```
Returns the symbol of the HTS non-fungible token.
```solidity theme={null}
function tokenURI(uint256 _tokenId) external view returns (string)
```
Returns the token metadata of the HTS non-fungible token. This corresponds to the NFT metadata field when minting an NFT using HTS. The `_tokenId` is the Hedera serial number of the NFT.
#### **From Interface `ERC721Enumerable`**
```solidity theme={null}
function totalSupply() external view returns (uint256)
```
Returns the total supply of the HTS non-fungible token.
***
## Unsupported Functions
The following ERC-721 operations will not be natively supported on Hedera and will return a failure if they're called. Advanced functionality is achievable only through custom implementations within smart contracts.
#### **From interface `ERC-721`**
```solidity wrap theme={null}
function safeTransferFrom(address token, address from, address to, uint256 tokenId)
```
#### **From interface `ERC721Enumerable`**
```solidity wrap theme={null}
function tokenByIndex(uint256 _index) external view returns (uint256)
```
```solidity wrap theme={null}
function tokenOfOwnerByIndex(address _owner, uint256 _index) external view returns (uint256)
```
#### **All semantics of Interface `ERC721TokenReceiver`**
* Existing Hedera token association rules will take the place of such checks.
***
### **Additional References**
* [HIP-376](https://hips.hedera.com/hip/hip-376)
* [HIP-218](https://hips.hedera.com/hip/hip-218)
* [EIP-721](https://eips.ethereum.org/EIPS/eip-721)
# Tokens Managed by Smart Contracts
Source: https://docs.hedera.com/evm/tokens/index
A [smart contract](/support/glossary#smart-contract) is a programmable, self-executing agreement designed to create, manage, or enforce the conditions of digital assets, also known as tokens. Tokens managed by smart contracts serve as digital representations of various asset types, such as artwork, cryptocurrency, and carbon credits on the blockchain. These tokens allow assets to be securely transferred between users or contracts and interact with others, adding functionality and interoperability within the blockchain ecosystem.
The [ERC-20](/support/glossary#erc-20) and [ERC-721](/support/glossary#erc-721) standards provide common interfaces for token contracts to standardize how tokens function across platforms. These interfaces enable tokens to be easily recognized by wallets, exchanges, and decentralized applications (dApps) in the Ethereum ecosystem. By conforming to these standards, tokens gain a predictable structure, simplifying integration for developers and ensuring users experience consistent functionality across compatible smart contract platforms.
"ERC" stands for [Ethereum Request for Comments](/support/glossary#ethereum-request-for-comments-erc), a protocol developers can follow to propose improvements or introduce new guidelines to the Ethereum blockchain. Hiero Contracts are compatible with several ERC standards, allowing developers to implement these standardized interfaces. This compatibility simplifies the token integration and provides a consistent user experience with token contracts across different platforms.
With [HIP-218](https://hips.hedera.com/hip/hip-218) and [HIP-376](https://hips.hedera.com/hip/hip-376), Hedera provides the ability to treat native HTS tokens as if they were ERC-20 (if fungible) or ERC-721 (if non-fungible) contracts. This ensures that developers have predictable functionality and minimal to not changes when bring their smart contracts to Hedera.
## Hedera-Compatible ERC Token Standards
Explore some of the token standards supported and compatible with Hedera:
**➡** [**ERC-20 (Fungible Tokens)**](/evm/tokens/erc20)
**➡** [**ERC-721 Non-Fungible Tokens (NFTs)**](/evm/tokens/erc721)
**➡** [**ERC-3643 Real World Assets (RWAs)** ](/evm/tokens/erc3643)
**➡** [**ERC-1363 Payable Tokens** ](/evm/tokens/erc1363)
***
## **Token Associations**
Before sending a token to a smart contract, you need to confirm whether you need to associate the token with the smart contract before transferring it. The transfer will fail if you transfer a token to a smart contract that was not associated with it first or does not have an open auto-association slot.
You can associate a smart contract with a token in the following ways:
* Use the `TokenAssociationTransaction` in the supported Hedera SDKs
* Use the `associateToken()` or `associateTokens()` from [HIP-206](https://hips.hedera.com/hip/hip-206).
**Note:** `Token association` is for HTS tokens only.
***
## Synthetic Events
Smart contract tokens like ERC-20 and ERC-721 emit events, creating contract logs that developers can query or subscribe to. Hedera Token Service (HTS) tokens are not inherently equipped with such event logs. As a solution to this limitation, Hedera Mirror Nodes now generates synthetic event logs for HTS tokens. Learn more [here](/learn/core-concepts/mirror-nodes#synthetic-smart-contract-contract-logs).
***
## FAQs
**Speed:** HTS transactions are native and offer faster execution time than a smart contract execution.
**Pricing:** Native services should be cheaper than the equivalent smart contract scenario.
No, you do not need to modify your existing smart contract deployed to another EVM compatible chain.
# Wrapped HBAR (WHBAR)
Source: https://docs.hedera.com/evm/tokens/whbar
## WHBAR (ERC) in the Hedera Ecosystem
Wrapped HBAR (WHAR) is an ERC-compatible wrapper that follows the ERC20 standard for Hedera's native HBAR token. Built on widely adopted wrapper contract principles, WHBAR makes it easier for developers and users to integrate Hedera’s native token into decentralized applications (dApps). This contract enables users to seamlessly convert HBAR into an ERC20 token and vice versa, making it easier to integrate with the broader web3 and DeFi ecosystems.
***
## Core Functionalities
* **Deposit & Mint:**
When you call the `deposit()` function and send HBAR, the contract mints an equivalent amount of WHBAR. Each unit of HBAR (represented in tinybars with 8 decimals) is matched with one unit of WHBAR. This ensures that the wrapped token maintains parity with the native token.
* **Withdraw & Burn:**
To redeem your underlying HBAR, you call the `withdraw(amount)` function. The contract burns the specified WHBAR tokens and releases the corresponding HBAR back to your wallet. This burn mechanism is crucial for maintaining the correct token supply and preserving the peg between HBAR and WHBAR.
* **ERC20 Standard Compliance:**
WHBAR implements all standard ERC20 functions (e.g., `transfer`, `approve`, `transferFrom`), ensuring seamless interaction with wallets, exchanges, and various DeFi protocols that support ERC20 tokens.
***
## Implementation Guide
Developers can integrate WHBAR into their applications by leveraging the following functions.
### Wrapping HBAR
To convert HBAR into its ERC20 representation (WHBAR), use the `deposit()` function. Keep in mind that:
* **Native HBAR:** Uses **8** decimal places (**tinybars**).
* **WHBAR (ERC20):** Uses **8** decimal places (**tinybars**)and *ONLY for deposits* (wrapping) uses 18 decimal places (weibars).
The conversion from HBAR to WHBAR involves adjusting for these decimal differences. For example, to wrap native HBAR into WHBAR, call `deposit()` and send your HBAR as `msg.value` in weibars (10¹⁸ per HBAR):
```solidity wrap theme={null}
/**
* @notice Deposits HBAR and mints an equivalent amount of WHBAR
* @dev This is the only supported method for obtaining WHBAR
*/
function deposit() public payable {
// To wrap 10 HBAR into WHBAR
// Note: 1 HBAR = 10^18 weibars; conversion handles the decimal difference.
whbarContract.deposit{value: 10 * 10**18}();
}
```
### Unwrapping WHBAR
When you want to convert back to redeem WHBAR for native HBAR, call the `withdraw()` function with `amount` in **tinybars** (10⁸ per HBAR). The value in WHBAR is directly mapped back to HBAR with the same 8 decimal places:
```solidity wrap theme={null}
/**
* @notice Burns WHBAR tokens and returns the equivalent HBAR
* @param amount The amount of WHBAR to burn
*/
function withdraw(uint256 amount) public {
// To unwrap 5 WHBAR back to HBAR
whbarContract.withdraw(5 * 10**8);
}
```
This burns 5 WHBAR and sends back 5 HBAR to the wallet.
#### **Important Note: Decimal Nuance**
When depositing HBAR, remember the conversion nuances between decimal places.
* **Native HBAR & WHBAR Token:** 8 decimals (tinybars).
* **RPC `msg.value`:** 18 decimals (weibars).
Although the `deposit()` function requires input in 18 decimal weibars, WHBAR tokens and all related transfers and balances use 8 decimals, identical to native HBAR in tinybars.
***
## Standard ERC20 Functions
WHBAR supports all standard ERC20 operations:
Function
Description
Example
transfer
Send WHBAR directly to another address
whbar.transfer(recipient, amount)
approve
Authorize a third party to spend your WHBAR
whbar.approve(spender, amount)
transferFrom
Transfer WHBAR as an authorized spender
whbar.transferFrom(owner, recipient, amount)
balanceOf
Check WHBAR balance of an address
whbar.balanceOf(address)
totalSupply
Get the total amount of WHBAR in circulation
whbar.totalSupply()
***
## Contract Deployments
The WHBAR contract implementation is available on GitHub in the [Hedera Smart Contracts repository](https://github.com/hashgraph/hedera-smart-contracts/blob/main/contracts/wrapped-tokens/WHBAR.sol).
***Source Code:*** [*WHBAR.sol*](https://github.com/hashgraph/hedera-smart-contracts/blob/main/contracts/wrapped-tokens/WHBAR.sol)
***
## Security Considerations:Audit and Testing
* **Audit and Review:**\
Although the [WHBAR contract has been independently reviewed](https://hedera.com/audits-and-standards), developers and users should conduct their own security assessments. Even small oversights in smart contracts may lead to vulnerabilities.
* **Test in a Sandbox:**\
Always test interactions in a testnet environment before deploying or integrating with mainnet contracts. This helps ensure the behavior matches expectations.
* **Follow Best Practices:**\
Double-check function inputs and transaction amounts. Always use the designated functions (`deposit()` and `withdraw()`) to prevent unintended fund loss.
***
## Integration Best Practices
* **Check allowances**: Before attempting `transferFrom` operations, verify that sufficient allowance has been granted.
* **Verify contract addresses**: Always double-check you're interacting with the official WHBAR contract addresses listed in the documentation.
* **Handle decimals properly**: Since both HBAR and WHBAR use 8 decimals, calculations are straightforward. Only for deposits, use 18 decimals to represent weibars.
**Critical**: HBAR sent directly to the contract address through methods other than the **`deposit()`** function will be permanently locked in the contract due to Hedera’s CryptoTransfer mechanics.
# Hedera Contract Builder
Source: https://docs.hedera.com/evm/tools/contract-builder
Scaffold, compile, deploy, and verify Solidity contracts on Hedera testnet all from your browser with this interactive playground. No CLI, no local setup. Built in collaboration with Kabila and open source.
# How to Fork the Hedera Network with Foundry - Basic ERC-20 Contract (Part 1)
Source: https://docs.hedera.com/evm/tools/foundry/forking
In this tutorial, you'll fork Hedera testnet using Foundry and interact with a basic ERC-20 token on the forked network. This is an introductory guide to local fork testing with Foundry.
This guide shows how to:
* Fork Hedera testnet using Foundry
* Deploy an ERC-20 contract to Hedera testnet
* Run Foundry tests on a fork of Hedera testnet
* Read and interact with an existing ERC-20 contract by its EVM address (e.g., `balanceOf`, `name`, `symbol`, `transfer`), with minimal setup
* The process to set up and run tests is similar for mainnet as well
References:
* Repo: [hashgraph/hedera-forking](https://github.com/hashgraph/hedera-forking)
* Readme sections: Foundry library, Running your Tests
* Examples: [`examples/foundry-hts/`](https://github.com/hashgraph/hedera-forking/tree/main/examples/foundry-hts)
For a deeper understanding of how Hedera forking works and its limitations,
see [Forking Hedera Network for Local
Testing](/evm/development/forking).
You can take a look at the complete code in the
[**basic-erc20-fork-test-foundry
repository**](https://github.com/hedera-dev/tutorial-hedera-fork-testing/tree/main/foundry/basic-erc20-fork-test-foundry).
***
## Prerequisites
* [Foundry](https://book.getfoundry.sh/getting-started/installation) installed
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/)
* Basic understanding of Solidity
* 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](#step-1:-project-setup)
2. [Step 2: Create the ERC-20 Contract and Deploy to Testnet](#step-2:-create-the-erc-20-contract-and-deploy-to-testnet)
3. [Step 3: Write Tests for the Forked Network](#step-3:-write-tests-for-the-forked-network)
4. [Step 4: Run Tests on 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:
```bash theme={null}
mkdir basic-erc20-fork-test-foundry
cd basic-erc20-fork-test-foundry
forge init
```
#### Install Dependencies
Install OpenZeppelin contracts and the Hedera forking library:
```bash theme={null}
forge install OpenZeppelin/openzeppelin-contracts
forge install hashgraph/hedera-forking
```
### Configure Remappings
Create or update `remappings.txt` in your project root:
```txt remappings.txt theme={null}
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
hedera-forking/=lib/hedera-forking/contracts/
forge-std/=lib/forge-std/src/
```
We need this to import prefix to a filesystem path so both Foundry(forge) and our editor can resolve short, package-like imports instead of long relative paths.
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:
```bash . env theme={null}
HEDERA_RPC_URL=https://testnet.hashio.io/api
HEDERA_PRIVATE_KEY=0x-your-private-key
```
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** as we're dealing with the testnet for this exercise.
Also, ensure it has sufficient HBAR for deployment.
Note that these variables will only be used for the original deployment of the contract to the testnet. The private key is not needed for the forked tests since we will be impersonating accounts.
Now, let's load these to the terminal:
```bash theme={null}
source .env
```
#### Configure Foundry
Update your `foundry.toml` file in the root directory of your project. Open it and add profiles for the Hedera RPC endpoints.
```toml foundry.toml theme={null}
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
ffi = true
solc = "0.8.33"
# Add this section for Hedera testnet
[rpc_endpoints]
testnet = "${HEDERA_RPC_URL}"
```
Note that we have `ffi` to be true because on forked tests, the library uses curl (or PowerShell) to query Hedera Mirror Node for token state so that EVM calls like `IERC721.ownerOf()` can work as if the token were a normal EVM contract.
We will be removing the default contracts that comes with foundry default project:
```bash theme={null}
rm -f script/Counter.s.sol src/Counter.sol test/Counter.t.sol
```
***
## Step 2: Create the ERC-20 Contract and Deploy to Testnet
### Create the Contract
Create a new file `src/ERC20Token.sol`:
```solidity src/ERC20Token.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract ERC20Token is ERC20, Ownable {
constructor(address initialOwner, address recipient)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
{
_mint(recipient, 10000 * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
```
This contract:
* Creates a basic ERC-20 token named "MyToken" with symbol "MTK"
* Mints 10,000 tokens to a recipient on deployment
* Has an `onlyOwner` `mint` function for additional minting
### Compile the Contract
```bash theme={null}
forge build
```
### Create Deployment Script
Create a new file `script/Deploy.s.sol`:
```solidity script/Deploy.s.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;
import {Script, console} from "forge-std/Script.sol";
import {ERC20Token} from "../src/ERC20Token.sol";
contract DeployScript is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("HEDERA_PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
console.log("Deploying contracts with the account:", deployer);
console.log("Account balance:", deployer.balance / 1e18, "HBAR");
vm.startBroadcast(deployerPrivateKey);
// Deploy ERC20Token with deployer as both owner and initial recipient
ERC20Token token = new ERC20Token(deployer, deployer);
vm.stopBroadcast();
console.log("ERC20Token deployed to:", address(token));
console.log(
"View on HashScan: https://hashscan.io/testnet/contract/%s",
address(token)
);
// Get deployment block number for fork testing reference
uint256 blockNumber = block.number;
console.log("Deployed at block number:", blockNumber);
console.log("");
console.log("=== IMPORTANT ===");
console.log("Save this contract address for your fork tests!");
console.log(
"Update DEPLOYED_CONTRACT in your test file with this address"
);
}
}
```
### Deploy to Testnet
Deploy your contract to Hedera testnet:
```bash theme={null}
forge script script/Deploy.s.sol:DeployScript --rpc-url testnet --broadcast
```
You should see output similar to:
```bash theme={null}
Deploying contracts with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Account balance: 67028 HBAR
ERC20Token deployed to: 0xfC7D2FB1D5a9Be5D6182cBf3F283140d007CdcD4
View on HashScan: https://hashscan.io/testnet/contract/0xfC7D2FB1D5a9Be5D6182cBf3F283140d007CdcD4
Deployed at block number: 29970059
=== IMPORTANT ===
Save this contract address for your fork tests!
Update DEPLOYED_CONTRACT in your test file with this address
```
We have already deployed this ERC-20 contract on testnet at [https://hashscan.io/testnet/contract/0xfC7D2FB1D5a9Be5D6182cBf3F283140d007CdcD4](https://hashscan.io/testnet/contract/0xfC7D2FB1D5a9Be5D6182cBf3F283140d007CdcD4) 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 already deployed contract on the forked testnet. This is the real power of fork testing - you can test against real deployed contracts without spending gas or affecting the live network.
Create a new file `test/ERC20Token.t.sol`:
Make sure to update the `DEPLOYED_CONTRACT` constant below with the contract
address from your deployment.
```solidity test/ERC20Token.t.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;
import {Test, console} from "forge-std/Test.sol";
import {ERC20Token} from "../src/ERC20Token.sol";
contract ERC20TokenForkTest is Test {
// Your deployed testnet contract:
address constant DEPLOYED_CONTRACT =
YOUR_CONTRACT_ADDRESS; // <-- Update this!
ERC20Token public token;
address public owner;
address public alice;
address public bob;
function setUp() public {
// Bind to the deployed contract on the forked network
token = ERC20Token(DEPLOYED_CONTRACT);
// Get the real owner from the deployed contract
owner = token.owner();
// Create test accounts
alice = makeAddr("alice");
bob = makeAddr("bob");
// Fund test accounts
vm.deal(owner, 100 ether);
vm.deal(alice, 100 ether);
vm.deal(bob, 100 ether);
}
/* =========================
Basic Info
========================= */
function test_ReadNameAndSymbol() public view {
assertEq(token.name(), "MyToken");
assertEq(token.symbol(), "MTK");
}
function test_ReadDecimals() public view {
assertEq(token.decimals(), 18);
}
function test_ReadTotalSupply() public view {
uint256 totalSupply = token.totalSupply();
console.log("Total supply on testnet:", totalSupply);
assertGt(totalSupply, 0);
}
function test_ReadOwnerBalance() public view {
uint256 balance = token.balanceOf(owner);
console.log("Owner balance:", balance);
assertGt(balance, 0);
}
/* =========================
Ownership
========================= */
function test_RejectMintingFromNonOwner() public {
// Alice (not the owner) tries to mint → should revert
vm.prank(alice);
vm.expectRevert();
token.mint(alice, 100 ether);
}
function test_AllowOwnerToMint() public {
uint256 balanceBefore = token.balanceOf(alice);
// Impersonate the real owner to mint
vm.prank(owner);
token.mint(alice, 500 ether);
uint256 balanceAfter = token.balanceOf(alice);
assertEq(balanceAfter, balanceBefore + 500 ether);
}
/* =========================
Transfers
========================= */
function test_TransferFromOwnerToAlice() public {
uint256 amount = 100 ether;
uint256 balanceBefore = token.balanceOf(alice);
// Transfer from owner
vm.prank(owner);
token.transfer(alice, amount);
uint256 balanceAfter = token.balanceOf(alice);
assertEq(balanceAfter, balanceBefore + amount);
}
function test_HandleMultipleTransfers() public {
// Mint tokens to alice first
vm.prank(owner);
token.mint(alice, 1000 ether);
uint256 aliceInitial = token.balanceOf(alice);
uint256 bobInitial = token.balanceOf(bob);
// Alice transfers to bob
vm.prank(alice);
token.transfer(bob, 300 ether);
assertEq(token.balanceOf(alice), aliceInitial - 300 ether);
assertEq(token.balanceOf(bob), bobInitial + 300 ether);
}
function test_FailTransferWithInsufficientBalance() public {
// Bob has no tokens initially, should fail
vm.prank(bob);
vm.expectRevert();
token.transfer(alice, 100 ether);
}
/* =========================
Allowances
========================= */
function test_ApproveAndCheckAllowance() public {
// Mint tokens to alice
vm.prank(owner);
token.mint(alice, 1000 ether);
// Alice approves bob
vm.prank(alice);
token.approve(bob, 500 ether);
assertEq(token.allowance(alice, bob), 500 ether);
}
function test_TransferFromAfterApproval() public {
// Mint tokens to alice
vm.prank(owner);
token.mint(alice, 1000 ether);
// Alice approves bob
vm.prank(alice);
token.approve(bob, 500 ether);
uint256 aliceBefore = token.balanceOf(alice);
// Bob transfers from alice to himself
vm.prank(bob);
token.transferFrom(alice, bob, 200 ether);
assertEq(token.balanceOf(bob), 200 ether);
assertEq(token.balanceOf(alice), aliceBefore - 200 ether);
assertEq(token.allowance(alice, bob), 300 ether);
}
function test_FailTransferFromWithoutApproval() public {
// Mint tokens to alice but no approval for bob
vm.prank(owner);
token.mint(alice, 1000 ether);
vm.prank(bob);
vm.expectRevert();
token.transferFrom(alice, bob, 100 ether);
}
/* =========================
Supply Changes
========================= */
function test_TrackSupplyChangesAfterMinting() public {
uint256 supplyBefore = token.totalSupply();
vm.prank(owner);
token.mint(alice, 5000 ether);
uint256 supplyAfter = token.totalSupply();
assertEq(supplyAfter, supplyBefore + 5000 ether);
}
/* =========================
Fork Verification
========================= */
function test_ConnectedToForkedNetwork() public view {
uint256 blockNumber = block.number;
console.log("Current fork block number:", blockNumber);
assertGt(blockNumber, 0);
}
function test_InteractingWithRealDeployedContract() public view {
// Verify we're reading from the actual deployed contract
uint256 codeSize;
address contractAddr = DEPLOYED_CONTRACT;
assembly {
codeSize := extcodesize(contractAddr)
}
assertGt(codeSize, 0);
console.log("Contract code size:", codeSize);
}
}
```
**Key points about these tests:**
* **Uses deployed contract** - Tests bind to the already deployed contract address
* **Impersonation with `vm.prank`** - Uses Foundry's cheatcode to act as the real owner
* **Reads real state** - Token info, balances, etc. come from the actual testnet deployment
* **Local modifications** - All transfers, mints happen only on the local fork
* **No testnet changes** - The real testnet is never modified
***
## Step 4: Run Tests on the Forked Network
Run your tests against the forked Hedera testnet:
```bash theme={null}
forge test --fork-url $HEDERA_RPC_URL
```
You should see output similar to:
```bash theme={null}
Ran 15 tests for test/ERC20Token.t.sol:ERC20TokenForkTest
[PASS] test_AllowOwnerToMint() (gas: 49526)
[PASS] test_ApproveAndCheckAllowance() (gas: 77316)
[PASS] test_ConnectedToForkedNetwork() (gas: 3725)
[PASS] test_FailTransferFromWithoutApproval() (gas: 53875)
[PASS] test_FailTransferWithInsufficientBalance() (gas: 16977)
[PASS] test_HandleMultipleTransfers() (gas: 83137)
[PASS] test_InteractingWithRealDeployedContract() (gas: 6315)
[PASS] test_ReadDecimals() (gas: 5930)
[PASS] test_ReadNameAndSymbol() (gas: 18696)
[PASS] test_ReadOwnerBalance() (gas: 14130)
[PASS] test_ReadTotalSupply() (gas: 11401)
[PASS] test_RejectMintingFromNonOwner() (gas: 14536)
[PASS] test_TrackSupplyChangesAfterMinting() (gas: 48071)
[PASS] test_TransferFromAfterApproval() (gas: 112142)
[PASS] test_TransferFromOwnerToAlice() (gas: 47497)
Suite result: ok. 15 passed; 0 failed; 0 skipped; finished in 1.21ms (3.15ms CPU time)
Ran 1 test suite in 225.61ms (1.21ms CPU time): 15 tests passed, 0 failed, 0 skipped (15 total tests)
```
### Pin to a Specific Block
For reproducible tests, you can pin to a specific block number:
```bash theme={null}
forge test --fork-url $HEDERA_RPC_URL --fork-block-number 29970059
```
This ensures your tests always run against the same blockchain state.
We are using block number `29970059` for this testing because the contract from above(i.e. `0xfC7D2FB1D5a9Be5D6182cBf3F283140d007CdcD4` was deployed on block `29970059`. If we tried to run our tests with block below this, it would fail such as:
```bash theme={null}
forge test --fork-url $HEDERA_RPC_URL --fork-block-number 29970058
```
This would fail with something like:
```bash theme={null}
Ran 1 test for test/ERC20Token.t.sol:ERC20TokenForkTest
[FAIL: EvmError: Revert] setUp() (gas: 0)
Suite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 3.60s (0.00ns CPU time)
Ran 1 test suite in 3.87s (3.60s CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)
Failing tests:
Encountered 1 failing test in test/ERC20Token.t.sol:ERC20TokenForkTest
[FAIL: EvmError: Revert] setUp() (gas: 0)
Encountered a total of 1 failing tests, 0 tests succeeded
```
***
## 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 in Foundry
Foundry provides cheatcodes for impersonation:
```solidity theme={null}
// Impersonate an address for the next call
vm.prank(someAddress);
token.transfer(recipient, amount);
// Impersonate an address for multiple calls
vm.startPrank(someAddress);
token.transfer(recipient1, amount1);
token.transfer(recipient2, amount2);
vm.stopPrank();
```
### Funding Accounts with `vm.deal`
Fund test accounts with native tokens:
```solidity theme={null}
// Fund an account with 100 ETH/HBAR
vm.deal(accountAddress, 100 ether);
```
### Local vs. Remote State
| Action | Affects Local Fork | Affects Testnet |
| -------------------------- | ------------------ | --------------- |
| Read balances | ✅ (cached) | ❌ (read-only) |
| Transfer tokens | ✅ | ❌ |
| Mint new tokens | ✅ | ❌ |
| Deploy new contracts | ✅ | ❌ |
| Impersonate accounts | ✅ | ❌ |
| Changes persist after test | ❌ (reset) | N/A |
***
## Further Learning & Next Steps
1. [**How to Fork Hedera with Foundry - Advanced HTS (Part 2)**](/evm/tools/foundry/forking-advanced-hts)\
Continue with HTS System Contracts and the hedera-forking emulation layer
2. [**Forking Hedera Network for Local Testing**](/evm/development/forking)\
Deep dive into how Hedera forking works under the hood
3. [**How to Fork Hedera with Hardhat (Part 1)**](/evm/tools/hardhat/forking-basic)\
Learn fork testing with Hardhat framework
4. [**How to Fork Hedera with Hardhat - Advanced HTS**](/evm/tools/hardhat/forking-advanced)\
Compare the Hardhat approach to HTS fork testing
5. [**hedera-forking Repository**](https://github.com/hashgraph/hedera-forking)\
Explore examples and documentation
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
# How to Fork the Hedera Network with Foundry - Advanced HTS Contract (Part 2)
Source: https://docs.hedera.com/evm/tools/foundry/forking-advanced-hts
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](https://github.com/hashgraph/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:
* Repo: [hashgraph/hedera-forking](https://github.com/hashgraph/hedera-forking)
* HTS System Contracts: [hiero-contracts](https://github.com/hiero-ledger/hiero-contracts)
* Supported methods: [README - Supported Methods](https://github.com/hashgraph/hedera-forking#hedera-token-service-supported-methods)
For a deeper understanding of how Hedera forking works and its limitations,
see [Forking Hedera Network for Local
Testing](/evm/development/forking).
You can take a look at the complete code in the [**advanced-hts-fork-test-foundry
repository**](https://github.com/hedera-dev/tutorial-hedera-fork-testing/tree/main/foundry/advanced-hts-fork-test-foundry).
***
## Prerequisites
* Completed [Part 1](/evm/tools/foundry/forking) of this tutorial series
* [Foundry](https://book.getfoundry.sh/getting-started/installation) installed
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/) with at least **20 HBAR** (15 HBAR for HTS token creation fee + gas)
* Familiarity with Hedera System Contracts - more specifically [HTS System Contracts precompiles](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/token-service)
* 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](#step-1-project-setup)
2. [Step 2: Create the HTS Contract and Deploy to Testnet](#step-2-create-the-hts-contract-and-deploy-to-testnet)
3. [Step 3: Write Tests for the Forked Network](#step-3-write-tests-for-the-forked-network)
4. [Step 4: Run Tests on 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:
```bash theme={null}
mkdir advanced-hts-fork-test-foundry
cd advanced-hts-fork-test-foundry
forge init
```
### Install Dependencies
Install OpenZeppelin contracts and the Hedera forking library:
```bash theme={null}
forge install OpenZeppelin/openzeppelin-contracts
forge install hashgraph/hedera-forking
```
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:
```txt remappings.txt theme={null}
@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/
hedera-forking/=lib/hedera-forking/contracts/
forge-std/=lib/forge-std/src/
```
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:
```bash .env theme={null}
HEDERA_RPC_URL=https://testnet.hashio.io/api
HEDERA_PRIVATE_KEY=0x-your-private-key
```
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:
```bash theme={null}
source .env
```
### Configure Foundry
Update your `foundry.toml` file:
```toml foundry.toml theme={null}
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
ffi = true
solc = "0.8.33"
# Add this section for Hedera testnet
[rpc_endpoints]
testnet = "${HEDERA_RPC_URL}"
```
**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`:
```bash theme={null}
rm -f script/Counter.s.sol src/Counter.sol test/Counter.t.sol
```
***
## Step 2: Create the HTS Contract and Deploy to Testnet
### Create the HTS Interaction Contract
Create a new file `src/HTSTokenManager.sol`:
```solidity src/HTSTokenManager.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;
import {IHederaTokenService} from "hedera-forking/IHederaTokenService.sol";
/// @title HTSTokenManager
/// @notice Manages HTS fungible tokens via the Hedera Token Service precompile (0x167).
/// @dev The HTS precompile at address(0x167) is a Hedera-native system contract.
/// In fork testing, the hedera-forking library provides a Solidity emulation
/// layer that responds to the same function signatures at the same address.
contract HTSTokenManager {
address constant HTS_PRECOMPILE = address(0x167);
int32 constant SUCCESS = 22;
address public tokenAddress;
event ResponseCode(int256 responseCode);
event CreatedToken(address tokenAddress);
event MintedToken(int64 newTotalSupply, int64[] serialNumbers);
event TransferToken(address tokenAddress, address receiver, int64 amount);
event TokenInfo(IHederaTokenService.TokenInfo tokenInfo);
event FungibleTokenInfo(IHederaTokenService.FungibleTokenInfo tokenInfo);
receive() external payable {}
/// @notice Creates an HTS fungible token with this contract as treasury.
function createFungibleTokenPublic(
string memory _name,
string memory _symbol
) public payable {
IHederaTokenService.HederaToken memory token;
token.name = _name;
token.symbol = _symbol;
token.treasury = address(this);
token.memo = "Created via HTSTokenManager";
// Assign supply key and admin key to this contract
IHederaTokenService.TokenKey[]
memory keys = new IHederaTokenService.TokenKey[](2);
keys[0] = IHederaTokenService.TokenKey({
keyType: 0x10, // SUPPLY
key: IHederaTokenService.KeyValue({
inheritAccountKey: false,
contractId: address(this),
ed25519: bytes(""),
ECDSA_secp256k1: bytes(""),
delegatableContractId: address(0)
})
});
keys[1] = IHederaTokenService.TokenKey({
keyType: 0x01, // ADMIN
key: IHederaTokenService.KeyValue({
inheritAccountKey: false,
contractId: address(this),
ed25519: bytes(""),
ECDSA_secp256k1: bytes(""),
delegatableContractId: address(0)
})
});
token.tokenKeys = keys;
token.expiry = IHederaTokenService.Expiry({
second: 0,
autoRenewAccount: address(this),
autoRenewPeriod: 7_776_000 // 90 days
});
(int256 responseCode, address createdToken) = IHederaTokenService(
HTS_PRECOMPILE
).createFungibleToken{value: msg.value}(token, 0, 8);
emit ResponseCode(responseCode);
if (responseCode != SUCCESS) {
revert("HTS: token creation failed");
}
tokenAddress = createdToken;
emit CreatedToken(createdToken);
}
/// @notice Mints additional fungible tokens.
function mintTokenPublic(
address token,
int64 amount
)
public
returns (
int256 responseCode,
int64 newTotalSupply,
int64[] memory serialNumbers
)
{
bytes[] memory metadata;
(responseCode, newTotalSupply, serialNumbers) = IHederaTokenService(
HTS_PRECOMPILE
).mintToken(token, amount, metadata);
emit ResponseCode(responseCode);
if (responseCode != SUCCESS) {
revert("HTS: mint failed");
}
emit MintedToken(newTotalSupply, serialNumbers);
}
/// @notice Transfers HTS tokens between accounts.
function transferTokenPublic(
address token,
address sender,
address receiver,
int64 amount
) public returns (int256 responseCode) {
responseCode = IHederaTokenService(HTS_PRECOMPILE).transferToken(
token, sender, receiver, amount
);
emit ResponseCode(responseCode);
if (responseCode != SUCCESS) {
revert("HTS: transfer failed");
}
emit TransferToken(token, receiver, amount);
}
/// @notice Gets full token info for an HTS token.
function getTokenInfoPublic(
address token
)
public
returns (
int256 responseCode,
IHederaTokenService.TokenInfo memory tokenInfo
)
{
(responseCode, tokenInfo) = IHederaTokenService(HTS_PRECOMPILE)
.getTokenInfo(token);
emit ResponseCode(responseCode);
emit TokenInfo(tokenInfo);
}
/// @notice Gets fungible-specific token info.
function getFungibleTokenInfoPublic(
address token
)
public
returns (
int256 responseCode,
IHederaTokenService.FungibleTokenInfo memory tokenInfo
)
{
(responseCode, tokenInfo) = IHederaTokenService(HTS_PRECOMPILE)
.getFungibleTokenInfo(token);
emit ResponseCode(responseCode);
emit FungibleTokenInfo(tokenInfo);
}
}
```
**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
```bash theme={null}
forge build
```
### Create Deployment Script
Create a new file `script/DeployHTS.s.sol`:
```solidity script/DeployHTS.s.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;
import {Script, console} from "forge-std/Script.sol";
import {HTSTokenManager} from "../src/HTSTokenManager.sol";
/// @title DeployHTSScript
/// @notice Deploys HTSTokenManager to Hedera testnet.
/// @dev This script ONLY deploys the contract. HTS token creation must be done
/// separately using `cast send` because forge script simulates locally first,
/// and the HTS precompile at 0x167 has no EVM bytecode to simulate against.
contract DeployHTSScript is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("HEDERA_PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
console.log("=== HTSTokenManager Deployment ===");
console.log("Deployer address:", deployer);
console.log("Deployer balance:", deployer.balance / 1e18, "HBAR");
vm.startBroadcast(deployerPrivateKey);
HTSTokenManager manager = new HTSTokenManager();
vm.stopBroadcast();
console.log("");
console.log("=== Deployment Successful ===");
console.log("HTSTokenManager deployed to:", address(manager));
console.log("Block number:", block.number);
}
}
```
### 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:
```bash theme={null}
forge script script/DeployHTS.s.sol:DeployHTSScript --rpc-url $HEDERA_RPC_URL --broadcast -vvv
```
You should see output similar to:
```bash theme={null}
=== Deployment Successful ===
HTSTokenManager deployed to: 0x22723B710D0A1Bdc83706Dd8085414c0570FaB8b
Block number: 33427480
```
Save the contract address - you'll need it for the next step.
**Step 2:** Create the HTS token using `cast send`:
```bash theme={null}
export CONTRACT_ADDRESS=0x22723B710D0A1Bdc83706Dd8085414c0570FaB8b
```
```bash theme={null}
cast send $CONTRACT_ADDRESS \
'createFungibleTokenPublic(string,string)' 'DemoHTS' 'DHTS' \
--value 15ether \
--rpc-url $HEDERA_RPC_URL \
--private-key $HEDERA_PRIVATE_KEY
```
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:
```bash theme={null}
cast abi-decode 'tokenAddress()(address)' $(cast call $CONTRACT_ADDRESS 'tokenAddress()' --rpc-url $HEDERA_RPC_URL)
```
**Step 4:** Note the block number for fork testing:
```bash theme={null}
cast block-number --rpc-url $HEDERA_RPC_URL
```
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](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.
```solidity test/HTSForkTest.t.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;
import {Test, console} from "forge-std/Test.sol";
import {htsSetup} from "hedera-forking/htsSetup.sol";
import {IHederaTokenService} from "hedera-forking/IHederaTokenService.sol";
import {IERC20} from "hedera-forking/IERC20.sol";
import {HTSTokenManager} from "../src/HTSTokenManager.sol";
contract HTSForkTest is Test {
int32 constant SUCCESS = 22;
// UPDATE THESE with your deployed addresses
address payable constant DEPLOYED_HTS_CONTRACT =
payable(0x22723B710D0A1Bdc83706Dd8085414c0570FaB8b);
address constant HTS_TOKEN =
0x000000000000000000000000000000000080d4f4;
HTSTokenManager public htsManager;
IERC20 public token;
address public alice;
address public bob;
function setUp() public {
// CRITICAL: Initialize the HTS emulation layer FIRST.
// This deploys the emulation contract at 0x167 so HTS calls work.
// Without this, all HTS calls revert with InvalidFEOpcode.
htsSetup();
// Bind to deployed contracts on the fork
htsManager = HTSTokenManager(DEPLOYED_HTS_CONTRACT);
token = IERC20(HTS_TOKEN);
// Create and fund test accounts
alice = makeAddr("alice");
bob = makeAddr("bob");
vm.deal(alice, 100 ether);
vm.deal(bob, 100 ether);
vm.deal(DEPLOYED_HTS_CONTRACT, 100 ether);
}
/* =========================
Token Info Tests
========================= */
function test_GetTokenInfo() public {
(int256 responseCode, IHederaTokenService.TokenInfo memory info) =
htsManager.getTokenInfoPublic(HTS_TOKEN);
assertEq(responseCode, int256(SUCCESS), "getTokenInfo should succeed");
assertTrue(bytes(info.token.name).length > 0, "name not empty");
assertTrue(bytes(info.token.symbol).length > 0, "symbol not empty");
console.log("Token name:", info.token.name);
console.log("Token symbol:", info.token.symbol);
}
function test_GetFungibleTokenInfo() public {
(int256 responseCode, IHederaTokenService.FungibleTokenInfo memory info) =
htsManager.getFungibleTokenInfoPublic(HTS_TOKEN);
assertEq(responseCode, int256(SUCCESS), "getFungibleTokenInfo should succeed");
console.log("Fungible token decimals:", info.decimals);
}
/* =========================
ERC-20 Interface Tests
========================= */
function test_ReadNameAndSymbol() public view {
string memory name = token.name();
string memory symbol = token.symbol();
console.log("Token name:", name);
console.log("Token symbol:", symbol);
assertEq(name, "DemoHTS");
assertEq(symbol, "DHTS");
}
function test_ReadDecimals() public view {
uint8 decimals = token.decimals();
console.log("Token decimals:", decimals);
assertEq(decimals, 8);
}
function test_ReadTotalSupply() public view {
uint256 totalSupply = token.totalSupply();
console.log("Total supply:", totalSupply);
assertGe(totalSupply, 0);
}
function test_ReadTreasuryBalance() public view {
uint256 balance = token.balanceOf(DEPLOYED_HTS_CONTRACT);
console.log("Treasury balance:", balance);
assertGe(balance, 0);
}
/* =========================
Transfer Tests
========================= */
function test_DealAndTransfer() public {
// Give alice tokens using Foundry's deal cheatcode
uint256 amount = 1000;
deal(HTS_TOKEN, alice, amount);
assertEq(token.balanceOf(alice), amount);
// Alice transfers to bob via ERC-20 interface
vm.prank(alice);
token.transfer(bob, 400);
assertEq(token.balanceOf(alice), 600);
assertEq(token.balanceOf(bob), 400);
}
function test_ApproveAndTransferFrom() public {
deal(HTS_TOKEN, alice, 2000);
vm.prank(alice);
token.approve(bob, 1000);
vm.prank(bob);
token.transferFrom(alice, bob, 500);
assertEq(token.balanceOf(alice), 1500);
assertEq(token.balanceOf(bob), 500);
}
function test_TransferToMultipleRecipients() public {
deal(HTS_TOKEN, alice, 5000);
vm.prank(alice);
token.transfer(bob, 2000);
address charlie = makeAddr("charlie");
vm.prank(alice);
token.transfer(charlie, 1000);
assertEq(token.balanceOf(alice), 2000);
assertEq(token.balanceOf(bob), 2000);
assertEq(token.balanceOf(charlie), 1000);
}
/* =========================
Fork State Verification
========================= */
function test_ConnectedToForkedNetwork() public view {
uint256 blockNumber = block.number;
console.log("Fork block number:", blockNumber);
assertGt(blockNumber, 0);
}
function test_ContractHasBytecode() public view {
uint256 codeSize;
address contractAddr = DEPLOYED_HTS_CONTRACT;
assembly { codeSize := extcodesize(contractAddr) }
assertGt(codeSize, 0, "HTSTokenManager should have bytecode");
}
function test_HTSPrecompileHasEmulation() public view {
uint256 htsCodeSize;
address hts = address(0x167);
assembly { htsCodeSize := extcodesize(hts) }
assertGt(htsCodeSize, 0, "0x167 should have emulation bytecode");
}
function test_TokenHasBytecode() public view {
uint256 tokenCodeSize;
address tokenAddr = HTS_TOKEN;
assembly { tokenCodeSize := extcodesize(tokenAddr) }
assertGt(tokenCodeSize, 0, "HTS token should have proxy bytecode");
}
}
```
**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:
```bash theme={null}
forge test --fork-url $HEDERA_RPC_URL -vvv
```
Pin to a specific block for reproducible tests:
```bash theme={null}
forge test --fork-url $HEDERA_RPC_URL --fork-block-number 33427481 -vvv
```
You should see output similar to:
```bash theme={null}
Ran 13 tests for test/HTSForkTest.t.sol:HTSForkTest
[PASS] test_ApproveAndTransferFrom() (gas: 1788900)
[PASS] test_ConnectedToForkedNetwork() (gas: 3768)
[PASS] test_ContractHasBytecode() (gas: 6379)
[PASS] test_DealAndTransfer() (gas: 1688928)
[PASS] test_GetFungibleTokenInfo() (gas: 1413178)
[PASS] test_GetTokenInfo() (gas: 1403795)
[PASS] test_HTSPrecompileHasEmulation() (gas: 6422)
[PASS] test_ReadDecimals() (gas: 1204125)
[PASS] test_ReadNameAndSymbol() (gas: 1216338)
[PASS] test_ReadTotalSupply() (gas: 1204165)
[PASS] test_ReadTreasuryBalance() (gas: 2055646)
[PASS] test_TokenHasBytecode() (gas: 6425)
[PASS] test_TransferToMultipleRecipients() (gas: 1853280)
Suite result: ok. 13 passed; 0 failed; 0 skipped
```
### 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](https://github.com/hashgraph/hedera-forking#hedera-token-service-supported-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](https://www.saucerswap.finance/) - swapping WHBAR for USDC at the current mainnet exchange rate, using real liquidity pools.
### Run the SaucerSwap Tests
```bash theme={null}
forge test --match-contract SaucerSwapForkTest \
--fork-url https://mainnet.hashio.io/api \
-vvv
```
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:
```solidity theme={null}
function test_SwapWHBARForUSDCViaSaucerSwap() public {
// Give the trader 10 WHBAR using deal() - no real tokens needed
uint256 whbarAmount = 10 * 1e8;
deal(WHBAR, trader, whbarAmount);
// Approve the SaucerSwap router
vm.startPrank(trader);
whbar.approve(SAUCERSWAP_ROUTER, whbarAmount);
// Encode the swap path: WHBAR -> 0.15% fee tier -> USDC
bytes memory path = abi.encodePacked(
WHBAR,
uint24(1500), // 0.15% fee tier for WHBAR/USDC pool
USDC
);
// Execute the swap
ExactInputParams memory params = ExactInputParams({
path: path,
recipient: trader,
deadline: block.timestamp + 300,
amountIn: whbarAmount,
amountOutMinimum: 0
});
(bool success, bytes memory returnData) = SAUCERSWAP_ROUTER.call(
abi.encodeWithSignature(
"exactInput((bytes,address,uint256,uint256,uint256))",
params
)
);
require(success, "Swap failed");
uint256 amountOut = abi.decode(returnData, (uint256));
vm.stopPrank();
// Trader received real USDC at mainnet exchange rate
assertGt(amountOut, 0, "Should have received USDC from swap");
}
```
### 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:
```solidity theme={null}
address whale = 0x...; // A real mainnet account
vm.prank(whale);
whbar.transfer(trader, 50000 * 1e8); // Uses the whale's real balance
```
**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
| Contract/Token | Hedera ID | EVM Address | Decimals |
| -------------------- | ------------- | -------------------------------------------- | -------- |
| SaucerSwap V2 Router | `0.0.3949434` | `0x00000000000000000000000000000000003c437A` | - |
| WHBAR | `0.0.1456986` | `0x0000000000000000000000000000000000163B5a` | 8 |
| USDC (Native) | `0.0.456858` | `0x000000000000000000000000000000000006f89a` | 6 |
> **Source:** [SaucerSwap Contract Deployments](https://docs.saucerswap.finance/developerx/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](https://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
```bash theme={null}
forge test --match-contract BonzoForkTest \
--fork-url https://mainnet.hashio.io/api \
-vvv
```
### What It Tests
| Test | What It Does |
| -------------------------------- | --------------------------------------------------------------------------------------- |
| `test_DepositWHBAR` | Deposits 5000 WHBAR as collateral, receives aWHBAR tokens |
| `test_DepositWHBARAndBorrowUSDC` | Full flow: deposit collateral, check account data, borrow 10 USDC, verify debt position |
| `test_ReadBonzoUSDCLiquidity` | Reads real USDC liquidity in Bonzo (\~7M USDC) |
### How the Deposit + Borrow Works
```
deal(WHBAR, depositor, 5000e8) → Create 5000 WHBAR on the fork
whbar.approve(LENDING_POOL, amount) → Approve LendingPool to pull WHBAR
LendingPool.deposit(WHBAR, ...) → Deposit as collateral → receive aWHBAR
LendingPool.getUserAccountData(...) → Check collateral, LTV (62.72%), borrow capacity
LendingPool.borrow(USDC, 10e6, 2, ..) → Borrow 10 USDC at variable rate
→ Receive USDC + variable debt token minted
```
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
| Contract | Address |
| ------------------ | -------------------------------------------- |
| LendingPool | `0x236897c518996163E7b313aD21D1C9fCC7BA1afc` |
| aWHBAR | `0x6e96a607F2F5657b39bf58293d1A006f9415aF32` |
| Variable Debt USDC | `0x8a90C2f80Fc266e204cb37387c69EA2ed42A3cc1` |
> **Source:** [Bonzo Lend Contracts](https://docs.bonzo.finance/hub/developer/bonzo-lend/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](https://hips.hedera.com/hip/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
| Aspect | Foundry | Hardhat |
| -------------------- | ------------------------------------------------------ | ------------------------------------------------- |
| Emulation approach | Proactive: deploys Solidity emulation via `htsSetup()` | Reactive: worker thread intercepts JSON-RPC calls |
| Data fetch mechanism | FFI + curl to Mirror Node | Node.js fetch to Mirror Node |
| Required config | `ffi = true` in `foundry.toml` | `chainId` + `workerPort` in hardhat config |
| HTS read operations | Fully supported via emulation | Fully supported via interception |
| HTS write operations | Use `deal()` + ERC-20 methods | Direct precompile calls work |
| Test language | Solidity | TypeScript |
### Local vs. Remote State
| Action | Affects Local Fork | Affects Testnet |
| -------------------------- | ------------------ | --------------- |
| Read balances | ✅ (cached) | ❌ (read-only) |
| Transfer tokens (ERC-20) | ✅ | ❌ |
| Query token info (HTS) | ✅ | ❌ |
| `deal()` set balances | ✅ | ❌ |
| Impersonate accounts | ✅ | ❌ |
| Changes persist after test | ❌ (reset) | N/A |
***
## Further Learning & Next Steps
1. [**Forking Hedera Network for Local Testing**](/evm/development/forking)\
Deep dive into how Hedera forking works under the hood
2. [**How to Fork Hedera with Foundry (Part 1)**](/evm/tools/foundry/forking)\
Start with basic ERC-20 fork testing
3. [**How to Fork Hedera with Hardhat - Advanced HTS**](/evm/tools/hardhat/forking-advanced)\
Compare the Hardhat approach to HTS fork testing
4. [**hedera-forking Repository**](https://github.com/hashgraph/hedera-forking)\
Explore examples and documentation
5. [**Hiero Contracts Repository**](https://github.com/hiero-ledger/hiero-contracts)\
Explore HTS System Contracts interfaces
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# Foundry on Hedera
Source: https://docs.hedera.com/evm/tools/foundry/index
Foundry empowers developers with tools for smart contract development. One of the three main components of Foundry is Forge. Forge is a Foundry command-line tool that allows developers to run tests, build, and deploy smart contracts.
Foundry Key benefits:
* Write tests in Solidity & limit your context switching.
* EVM cheatcodes give you more control over smart contract development.
This series of mini-tutorials demonstrates how to set up Foundry and use Forge for seamless integration with your Hedera project to test your smart contracts & how to fork Hedera Mainnet to test against deployed contracts.
The tutorials are self-contained and can be done in any order.
***
[GitHub](https://github.com/a-ridley) | [X](https://X.com/ridley___)
# Configuring Foundry with Hedera Localnet/Testnet: A Step-by-Step Guide
Source: https://docs.hedera.com/evm/tools/foundry/setup
Developers building smart contracts on Hedera often use the **Hedera JSON-RPC Relay** to enable EVM tools like **Foundry**. In this post, we'll walk through how to set up Foundry to work with the **Hiero Local Node**, allowing for local deployment, debugging, and testing of smart contracts without using testnet resources.
Not sure whether to use the Hiero Local Node, Hashio, or a custom relay setup?
Read this [blog
post](/native/local-dev/setup-local-node)
comparing the different options.
You can take a look at the **complete code** in the [**Hedera-Code-Snippets
repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/tutorial-local-hardhat).
This guide shows you how to configure Foundry to deploy, interact with, and test Solidity smart contracts on:
* Hedera Localnet (via the Hiero Local Node + JSON-RPC Relay)
* Hedera Testnet (via Hashio JSON-RPC Relay)
You’ll set up environment variables, configure `foundry.toml`, write a simple contract and a Foundry deployment script, and learn how to switch between Localnet and Testnet with a single flag.
If you’re looking for an end-to-end “Hello, ERC-20” tutorial, see: “[Getting started with Foundry](/evm/quickstart/deploy-with-foundry)” (deploy, interact, verify on Hashscan). This guide focuses on environment configuration and workflow for Localnet/Testnet.
***
## What you will accomplish
* Configure Foundry to talk to the Hedera JSON-RPC Relay (Localnet and Testnet)
* Deploy and interact with a sample contract using `forge script` and `cast`
* Verify contracts on Hashscan (Testnet)
***
## Prerequisites
* Foundry installed (forge, cast, anvil, chisel):
* `curl -L https://foundry.paradigm.xyz | bash`
* `foundryup`
* ECDSA account and private key
* For Testnet: create/fund an account via the [Hedera Portal](https://portal.hedera.com/)
* Basic Solidity / CLI familiarity
***
## Table of Contents
1. [Option A: Run Hedera Localnet](#option-a%3A-run-hedera-localnet-hiero-local-node)
2. [Option B: Use Hedera Testnet](#option-b%3A-use-hedera-testnet-hashio)
3. [Step 1: Initialize a Foundry project](#step-1%3A-initialize-a-foundry-project)
4. [Step 2: Add environment variables](#step-2%3A-add-environment-variables)
5. [Step 3: Configure foundry](#step-3%3A-configure-foundry.toml)
6. [Step 4: Add a simple contract](#step-4%3A-add-a-simple-contract)
7. [Step 5: Update the deploy script](#step-5%3A-update-the-deploy-script)
8. [Step 6: Deploy the contract](#step-6%3A-deploy-the-contract)
9. [Step 7: Run tests](#step-7%3A-run-tests)
10. [Step 8: Verifying the contract on Hashscan](#step-8%3A-verifying-the-contract-on-hashscan)
11. [Interact using cast](#interact-using-cast)
***
## Option A: Run Hedera Localnet (Hiero Local Node)
Hedera provides a local node configuration that includes a mirror node, a consensus node, and the JSON-RPC relay. You can run it via `npm`.
**Clone, install, and run the node:**
```bash theme={null}
git clone https://github.com/hiero-ledger/hiero-local-node.git
cd hiero-local-node
npm install
npm run start
```
Once all the containers have started, the Hiero Local Node is up and running. This includes a Consensus Node, Mirror Node and explorer, JSON RPC Relay, Block Node, Grafana UI, and Prometheus UI.
* On startup, the Local Node prints funded ECDSA accounts to the console logs.
You can use one of these for local development. Make sure to only use accounts
listed under "Accounts list (Alias ECDSA keys)" \* The JSON-RPC Relay is
typically on port 7546. So, the URL would be `http://localhost:7546`
***
## Option B: Use Hedera Testnet (Hashio)
Hashio is a community relay node suitable for development/testing:
* RPC URL: `https://testnet.hashio.io/api`
**Note**: For production, prefer a commercial-grade JSON-RPC relay or host your own Hiero JSON-RPC Relay.
***
## Step 1: Initialize a Foundry project
```bash theme={null}
forge init foundry-hello-world
cd foundry-hello-world
```
This creates `src`, `script`, `test`, and `lib`.
### Project Structure
The Foundry project initialization creates the following file structure:
```
foundry.toml
lib
└── forge-std
src
└── Counter.sol
script
└── Counter.s.sol
test
└── Counter.t.sol
```
Here's a quick overview of these files and directories:
* `foundry.toml`: You can configure Foundry's behavior using this file such as defining RPC URLs
* `src`: Serves as the default for storing your smart contract source code
* `script`: This is where you store Solidity scripts for deploying contracts and performing other on-chain operations
* `test`: Serves as the dedicated location for Solidity-based unit and integration tests for your smart contracts
***
## Step 2: Add environment variables
Create an `.env` for your RPC URL and private key.
```bash theme={null}
touch .env
```
Put the following into your environment file.
```bash .env theme={null}
HEDERA_RPC_URL=your-rpc-url
HEDERA_PRIVATE_KEY=0x-your-private-key
```
Now, let's also load these to the terminal:
```bash theme={null}
source .env
```
Replace the `your-rpc-url` environment variable with:
* For Testnet: `https://testnet.hashio.io/api`, or
* For Localnet: `http://localhost:7546`
Replace the `0x-your-private-key` environment variable with:
* For Testnet: the **HEX Encoded Private Key** for your **ECDSA** **account** from the [Hedera Portal](https://portal.hedera.com/), or
* For Localnet: the ECDSA private key from the terminal where you are running Hiero Local Node(eg. `0x105d050185ccb907fba04dd92d8de9e32c18305e097ab41dadda21489a211524` )
***Please note**:* *that Hashio is intended for development and testing
purposes only. For production use cases, it's recommended to use
commercial-grade JSON-RPC Relay or host your own instance of the* [*Hiero
JSON-RPC Relay*](https://github.com/hiero-ledger/hiero-json-rpc-relay)*.*
***
## Step 3: Configure "foundry.toml"
Foundry uses the `foundry.toml` file for configuration. Open it and add profiles for the Hedera RPC endpoint.
```toml foundry.toml theme={null}
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
remappings = [
"forge-std/=lib/forge-std/src/"
]
# Add this section for Hedera Testnet
[rpc_endpoints]
hedera = "${HEDERA_RPC_URL}"
```
***
## Step 4: Add a simple contract
Use a tiny `Counter` contract to focus on configuration rather than external dependencies for this exercise.
Compile with:
```bash theme={null}
forge build
```
***
## Step 5: Update the deploy script
We are going to update our deploy script a little bit so we can use our private key instead of passing it as a flag every time:
```solidity script/Counter.s.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {Script, console} from "forge-std/Script.sol";
import {Counter} from "../src/Counter.sol";
contract CounterScript is Script {
Counter public counter;
function run() external returns (address) {
// Load the private key from the .env file
uint256 deployerPrivateKey = vm.envUint("HEDERA_PRIVATE_KEY");
// Start broadcasting transactions with the loaded private key
vm.startBroadcast(deployerPrivateKey);
// Deploy the contract
counter = new Counter();
// Stop broadcasting
vm.stopBroadcast();
console.log("Counter Contract deployed to:", address(counter));
return address(counter);
}
}
```
***
## Step 6: Deploy the contract
Now, execute the script to deploy your contract:
```bash theme={null}
forge script script/Counter.s.sol:CounterScript --rpc-url hedera --broadcast
```
After a few moments, you will see the address of your newly deployed contract:
```
[⠊] Compiling...
No files changed, compilation skipped
Script ran successfully.
== Return ==
0: address 0x061A1DdE963792192eA823C2F57285111812630b
== Logs ==
Counter Contract deployed to: 0x061A1DdE963792192eA823C2F57285111812630b
## Setting up 1 EVM.
==========================
Chain 296
Estimated gas price: 720.000000001 gwei
Estimated total gas used for script: 203856
Estimated amount required: 0.146776320000203856 ETH
==========================
##### 296
✅ [Success] Hash: 0x279d667c3a31bb0956449d819bdd4b99619a25b8387a0765f28c9407d158069b
Contract Address: 0x061A1DdE963792192eA823C2F57285111812630b
Block: 25089799
Paid: 0.0554489 ETH (163085 gas * 340 gwei)
✅ Sequence #1 on 296 | Total Paid: 0.0554489 ETH (163085 gas * avg 340 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL
```
Note that Foundry hardcodes “ETH” in its summary. However, even if it says
`ETH`, because we're connected to Hedera, the currency used is `HBAR`.
Now, go ahead and update your `.env` values to point to another Hedera Network(Localnet or Testnet) and try again.
***
## Step 7: Run tests
You can also run the test suite that's included as part of `test/` directory:
```bash theme={null}
forge test
```
***
## Step 8: Verifying the Contract on Hashscan
Verifying your smart contract publishes its source code to [Sourcify](https://sourcify.dev), and HashScan picks up the verified status automatically.
Sourcify only verifies contracts on registered networks. Hedera Mainnet (chain ID `295`) and Testnet (chain ID `296`) are supported, but **Hedera Localnet is not**. Skip this step when working against Localnet and rerun verification once your contract is deployed on Testnet or Mainnet. Make sure to replace `` with the address you got after the deployment above.
Run the following command, using the variables you set earlier.
```bash theme={null}
forge verify-contract src/Counter.sol:Counter \
--chain-id 296 \
--verifier sourcify \
--verifier-url "https://sourcify.dev/server"
```
After running the command, you should see a success message.
```
Submitting verification for [Counter] "0x061A1DdE963792192eA823C2F57285111812630b".
Contract successfully verified
```
**Congratulations! 🎉 You have successfully deployed, interacted with, and verified a smart contract on the Hedera Testnet using Foundry. Feel free to reach out in** [**Discord**](https://hedera.com/discord)**!**
***
## Interact using cast
Set helpers:
```bash theme={null}
export CONTRACT_ADDRESS=
export MY_ADDRESS=$(cast wallet address $HEDERA_PRIVATE_KEY)
```
Read:
```bash theme={null}
cast call $CONTRACT_ADDRESS "number()(uint256)" --rpc-url hedera
```
Write:
```bash theme={null}
cast send $CONTRACT_ADDRESS "setNumber(uint256)" 42 \
--private-key $HEDERA_PRIVATE_KEY \
--rpc-url hedera
cast send $CONTRACT_ADDRESS "increment()" \
--private-key $HEDERA_PRIVATE_KEY \
--rpc-url hedera
```
Read again:
```bash theme={null}
cast call $CONTRACT_ADDRESS "number()(uint256)" --rpc-url hedera
```
You should get an output of `43` since the counter is at 43 now after having added 42 + 1.
***
## Further Learning & Next Steps
Want to take your local development setup even further? Here are some excellent tutorials to help you dive deeper into smart contract development on Hedera using Foundry:
1. [**How to Mint and Burn an ERC-721 Token (Part 1)**](/evm/tutorials/advanced/erc721-foundry/part1-mint-burn)\
Learn how to create a basic ERC-721 NFT, mint it, and burn it on Hedera.
2. [How to Write Tests in Solidity (Part 2)](/evm/tutorials/advanced/erc721-foundry/part2-testing)\
Learn how to start writing tests in Foundry using Solidity
3. [How to Fork the Hedera Network for Local Testing](/evm/development/forking)\
Learn how to fork hedera network(testnet/mainnet) locally so you can start testing against the forked network
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
[GitHub](https://github.com/LukeForrest-Hashgraph) |
[X](https://x.com/_LukeForrest)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
# How to Fork the Hedera Network with Hardhat - Advanced HTS Contract (Part 2)
Source: https://docs.hedera.com/evm/tools/hardhat/forking-advanced
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:
* Repo: [hashgraph/hedera-forking](https://github.com/hashgraph/hedera-forking)
* HTS System Contracts: [hiero-contracts](https://github.com/hiero-ledger/hiero-contracts)
* Supported methods: [README - Supported Methods](https://github.com/hashgraph/hedera-forking#hedera-token-service-supported-methods)
For a deeper understanding of how Hedera forking works and its limitations,
see [Forking Hedera Network for Local
Testing](/evm/development/forking).
You can take a look at the complete code in the [**advanced-hts-fork-test
repository**](https://github.com/hedera-dev/tutorial-hedera-fork-testing/tree/main/hardhat/advanced-hts-fork-test).
***
## Prerequisites
* Completed [Part 1](/evm/tools/hardhat/forking-basic) of this tutorial series
* Familiarity with Hedera System Contracts - more specifically [HTS System Contracts precompiles](https://github.com/hiero-ledger/hiero-contracts/tree/main/contracts/token-service)
***
## Table of Contents
1. [Step 1: Project Setup](#step-1:-project-setup)
2. [Step 2: Create the HTS Contract and Deploy to Testnet](#step-2:-create-the-hts-contract-and-deploy-to-testnet)
3. [Step 3: Write Tests for Supported HTS Methods](#step-3:-write-tests-for-supported-hts-methods)
4. [Step 4: Run Tests on the Forked Network](#step-4:-run-tests-on-the-forked-network)
***
## Step 1: Project Setup
### Initialize Project
Create a new directory and initialize the project:
```bash theme={null}
mkdir advanced-hts-fork-test
cd advanced-hts-fork-test
npm init -y
```
### Install Dependencies
Create or update your `package.json` with all required dependencies:
```json package.json theme={null}
{
"name": "advanced-hts-fork-test",
"version": "1.0.0",
"description": "Advanced Hedera HTS Fork Testing with Hardhat",
"private": true,
"scripts": {
"compile": "hardhat compile",
"test": "hardhat test",
"deploy:testnet": "hardhat run scripts/deploy.ts --network hederaTestnet"
},
"license": "MIT",
"devDependencies": {
"@hashgraph/smart-contracts": "github:hashgraph/hedera-smart-contracts",
"@hashgraph/system-contracts-forking": "0.1.2",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
"@nomicfoundation/hardhat-ethers": "^3.0.0",
"@nomicfoundation/hardhat-ignition": "^0.15.16",
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "5.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@nomicfoundation/ignition-core": "^0.15.15",
"@openzeppelin/contracts": "^5.0.0",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
"@types/mocha": ">=9.1.0",
"@types/node": "^20.0.0",
"chai": "^4.2.0",
"hardhat": "2.22.19",
"hardhat-gas-reporter": "^1.0.8",
"solidity-coverage": "^0.8.1",
"ts-node": "^10.9.0",
"typechain": "^8.3.0",
"typescript": "^5.0.0"
}
}
```
Note the addition of `@hashgraph/smart-contracts` which provides the HTS System Contracts interfaces and helper contracts.
Then install all dependencies:
```bash theme={null}
npm install --legacy-peer-deps
```
**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:
```bash theme={null}
npx hardhat --version
# Should output: 2.22.19
```
### Create Project Structure
Create the necessary directories:
```bash theme={null}
mkdir contracts test scripts
```
### Configure TypeScript
Create `tsconfig.json` in your project root:
```json tsconfig.json theme={null}
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"resolveJsonModule": true
},
"include": ["./scripts", "./test", "./typechain-types"],
"files": ["./hardhat.config.ts"]
}
```
### Configure Hardhat
Create `hardhat.config.ts` in your project root:
```typescript hardhat.config.ts theme={null}
import { HardhatUserConfig, vars } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "@hashgraph/system-contracts-forking/plugin";
// Load configuration variables
const HEDERA_RPC_URL = vars.get("HEDERA_RPC_URL");
const HEDERA_PRIVATE_KEY = vars.get("HEDERA_PRIVATE_KEY");
const config: HardhatUserConfig = {
solidity: {
version: "0.8.33",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
networks: {
// Network for deploying to real testnet
hederaTestnet: {
url: HEDERA_RPC_URL,
accounts: [HEDERA_PRIVATE_KEY],
chainId: 296
},
// Local fork of testnet for testing
hardhat: {
forking: {
url: HEDERA_RPC_URL,
// Pin to a specific block for reproducible tests
// Update this after deploying your contract
blockNumber: 29900000,
// @ts-ignore - custom properties for hedera-forking plugin
chainId: 296,
// @ts-ignore
workerPort: 10001
}
}
}
};
export default config;
```
**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:
```bash theme={null}
npx hardhat vars set HEDERA_RPC_URL
```
When prompted, enter: `https://testnet.hashio.io/api`
```bash theme={null}
npx hardhat vars set HEDERA_PRIVATE_KEY
```
When prompted, enter the **HEX Encoded Private Key** for your **ECDSA account** from the [Hedera Portal](https://portal.hedera.com/).
Make sure your ECDSA account exists on **testnet** and has sufficient HBAR for
deployment. You can fund your testnet account using the [Hedera
Portal](https://portal.hedera.com/).
***
## Step 2: Create the HTS Contract and Deploy to Testnet
### Create the HTS Interaction Contract
Create a new file `contracts/HTSTokenManager.sol`:
```solidity contracts/HTSTokenManager.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.33;
import "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/HederaTokenService.sol";
import "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/ExpiryHelper.sol";
import "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/KeyHelper.sol";
import "@hashgraph/smart-contracts/contracts/system-contracts/HederaResponseCodes.sol";
import "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/IHederaTokenService.sol";
import "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/FeeHelper.sol";
contract HTSTokenManager is
HederaTokenService,
ExpiryHelper,
KeyHelper,
FeeHelper
{
bool finiteTotalSupplyType = true;
event ResponseCode(int256 responseCode);
event CreatedToken(address tokenAddress);
event FungibleTokenInfo(IHederaTokenService.FungibleTokenInfo tokenInfo);
event TransferToken(address tokenAddress, address receiver, int64 amount);
event MintedToken(int64 newTotalSupply, int64[] serialNumbers);
/**
* @notice Creates a new fungible token using HTS
*/
function createFungibleTokenPublic(
string memory _name,
string memory _symbol
) public payable {
// Build token definition
IHederaTokenService.HederaToken memory token;
token.name = _name;
token.symbol = _symbol;
token.treasury = address(this);
token.memo = "This is a fungible token";
// Keys: SUPPLY + ADMIN -> contractId
IHederaTokenService.TokenKey[]
memory keys = new IHederaTokenService.TokenKey[](2);
keys[0] = getSingleKey(
KeyType.SUPPLY,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[1] = getSingleKey(
KeyType.ADMIN,
KeyValueType.CONTRACT_ID,
address(this)
);
token.tokenKeys = keys;
(int256 responseCode, address tokenAddress) = HederaTokenService
.createFungibleToken(token, 0, 0);
if (responseCode != HederaResponseCodes.SUCCESS) {
revert();
}
emit CreatedToken(tokenAddress);
}
/**
* @notice Mints tokens
*/
function mintTokenPublic(
address token,
int64 amount,
bytes[] memory metadata
)
public
returns (
int256 responseCode,
int64 newTotalSupply,
int64[] memory serialNumbers
)
{
(responseCode, newTotalSupply, serialNumbers) = HederaTokenService
.mintToken(token, amount, metadata);
emit ResponseCode(responseCode);
if (responseCode != HederaResponseCodes.SUCCESS) {
revert();
}
emit MintedToken(newTotalSupply, serialNumbers);
}
/**
* @notice Transfers tokens using HTS transferToken
* @dev This is a SUPPORTED method in hedera-forking
*/
function transferTokenPublic(
address token,
address sender,
address receiver,
int64 amount
) public returns (int256 responseCode) {
responseCode = HederaTokenService.transferToken(
token,
sender,
receiver,
amount
);
emit ResponseCode(responseCode);
if (responseCode != HederaResponseCodes.SUCCESS) {
revert();
}
}
/**
* @notice Gets token info
*/
function getTokenInfoPublic(
address token
)
public
returns (
int256 responseCode,
IHederaTokenService.TokenInfo memory tokenInfo
)
{
(responseCode, tokenInfo) = HederaTokenService.getTokenInfo(token);
emit ResponseCode(responseCode);
}
/**
* @notice Gets fungible token info
*/
function getFungibleTokenInfoPublic(
address token
)
public
returns (
int256 responseCode,
IHederaTokenService.FungibleTokenInfo memory tokenInfo
)
{
(responseCode, tokenInfo) = HederaTokenService.getFungibleTokenInfo(
token
);
emit ResponseCode(responseCode);
emit FungibleTokenInfo(tokenInfo);
}
}
```
**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
```bash theme={null}
npx hardhat compile
```
### Create Deployment Script
Create a new file `scripts/deploy.ts`:
```typescript scripts/deploy.ts theme={null}
import { ethers } from "hardhat";
async function main(): Promise {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
const balance = await ethers.provider.getBalance(deployer.address);
console.log("Account balance:", ethers.formatEther(balance), "HBAR");
// 1) Deploy the HTSTokenManager contract
console.log("\n--- Deploying HTSTokenManager ---");
const HTSTokenManager = await ethers.getContractFactory("HTSTokenManager");
const htsManager = await HTSTokenManager.deploy();
await htsManager.waitForDeployment();
const contractAddress = await htsManager.getAddress();
console.log("HTSTokenManager deployed to:", contractAddress);
console.log(
"View on HashScan: https://hashscan.io/testnet/contract/" + contractAddress
);
// 2) Create a fungible token using the contract
console.log("\n--- Creating HTS Fungible Token ---");
const TOKEN_NAME = "TestForkToken";
const TOKEN_SYMBOL = "TFT";
const HBAR_TO_SEND = "15"; // HBAR to send for token creation
console.log(`Creating token "${TOKEN_NAME}" (${TOKEN_SYMBOL})...`);
console.log(`Sending ${HBAR_TO_SEND} HBAR for token creation...`);
const createTx = await htsManager.createFungibleTokenPublic(
TOKEN_NAME,
TOKEN_SYMBOL,
{
gasLimit: 1_000_000,
value: ethers.parseEther(HBAR_TO_SEND)
}
);
const createReceipt = await createTx.wait();
console.log("createFungibleTokenPublic() tx hash:", createTx.hash);
// 3) Extract token address from CreatedToken event
let tokenAddress: string | null = null;
for (const log of createReceipt?.logs || []) {
try {
const parsed = htsManager.interface.parseLog({
topics: log.topics as string[],
data: log.data
});
if (parsed?.name === "CreatedToken") {
tokenAddress = parsed.args[0];
break;
}
} catch {
// Not our event, skip
}
}
if (!tokenAddress) {
throw new Error("Failed to extract token address from CreatedToken event");
}
console.log("HTS Token created at:", tokenAddress);
console.log(
"View token on HashScan: https://hashscan.io/testnet/token/" + tokenAddress
);
// 4) Get deployment block number
const blockNumber = await ethers.provider.getBlockNumber();
console.log("\nDeployed at block number:", blockNumber);
// 5) Summary
console.log("\n" + "=".repeat(60));
console.log("DEPLOYMENT SUMMARY");
console.log("=".repeat(60));
console.log("HTSTokenManager Contract:", contractAddress);
console.log("HTS Token Address: ", tokenAddress);
console.log("Block Number: ", blockNumber);
console.log("=".repeat(60));
console.log("\n=== IMPORTANT ===");
console.log("Update your hardhat.config.ts with:");
console.log(` blockNumber: ${blockNumber}`);
console.log("\nUpdate your test file with:");
console.log(` DEPLOYED_CONTRACT = "${contractAddress}"`);
console.log(` TOKEN_ADDRESS = "${tokenAddress}"`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
```
### Deploy to Testnet
Deploy your contract to Hedera testnet:
```bash theme={null}
npx hardhat run scripts/deploy.ts --network hederaTestnet
```
You should see output similar to:
```bash theme={null}
Deploying contracts with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Account balance: 67044.71699545 HBAR
--- Deploying HTSTokenManager ---
HTSTokenManager deployed to: 0x525F2a20563A052F7dC65df59106EC82f0584102
View on HashScan: https://hashscan.io/testnet/contract/0x525F2a20563A052F7dC65df59106EC82f0584102
--- Creating HTS Fungible Token ---
Creating token "TestForkToken" (TFT)...
Sending 15 HBAR for token creation...
createFungibleTokenPublic() tx hash: 0xe71eb1253d11120dc9db1c764070fdb13db0b25374c30f2f0bd2792d1eead3fb
HTS Token created at: 0x000000000000000000000000000000000073E8dC
View token on HashScan: https://hashscan.io/testnet/token/0x000000000000000000000000000000000073E8dC
Deployed at block number: 29968809
============================================================
DEPLOYMENT SUMMARY
============================================================
HTSTokenManager Contract: 0x525F2a20563A052F7dC65df59106EC82f0584102
HTS Token Address: 0x000000000000000000000000000000000073E8dC
Block Number: 29968809
============================================================
=== IMPORTANT ===
Update your hardhat.config.ts with:
blockNumber: 29968809
Update your test file with:
DEPLOYED_CONTRACT = "0x525F2a20563A052F7dC65df59106EC82f0584102"
TOKEN_ADDRESS = "0x000000000000000000000000000000000073E8dC"
```
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:
```typescript theme={null}
blockNumber: 29966796, // <-- Update this with your deployment block or higher
```
We have already deployed this HTS contract on testnet at [https://hashscan.io/testnet/contract/0xdC6F13e9Bb740593ffacdB7510548FD2E62bc035](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.
```typescript test/HTSTokenManager.test.ts theme={null}
import { expect } from "chai";
import { ethers, network } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { HTSTokenManager } from "../typechain-types";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
// HTS Success response code
const SUCCESS = 22n;
describe("HTSTokenManager - HTS Forking Tests", function () {
// Increase timeout for network operations
this.timeout(120000); // 2 minutes
// Add delay between tests to avoid rate limiting
afterEach(async function () {
await new Promise((resolve) => setTimeout(resolve, 1000)); // 1 second delay
});
// ============================================
// UPDATE THESE VALUES AFTER RUNNING deploy.ts
// ============================================
// Your deployed testnet contract address
const DEPLOYED_CONTRACT = "YOUR_CONTRACT_ADDRESS"; // <--- UPDATE THIS>
// The HTS token created during deployment
const TOKEN_ADDRESS = "YOUR_TOKEN_ADDRESS"; // <--- UPDATE THIS>
// ============================================
let htsManager: HTSTokenManager;
let alice: HardhatEthersSigner;
let bob: HardhatEthersSigner;
async function setupFixture(): Promise<{
htsManager: HTSTokenManager;
alice: HardhatEthersSigner;
bob: HardhatEthersSigner;
}> {
// Bind to the deployed contract on the forked network
const contract = await ethers.getContractAt(
"HTSTokenManager",
DEPLOYED_CONTRACT
);
// Get local test accounts
const [, aliceSigner, bobSigner] = await ethers.getSigners();
// Fund local accounts
await network.provider.send("hardhat_setBalance", [
aliceSigner.address,
"0x56BC75E2D63100000" // 100 ETH in hex
]);
await network.provider.send("hardhat_setBalance", [
bobSigner.address,
"0x56BC75E2D63100000"
]);
// Fund the contract (it's the treasury and needs gas for operations)
await network.provider.send("hardhat_setBalance", [
DEPLOYED_CONTRACT,
"0x56BC75E2D63100000"
]);
return {
htsManager: contract as HTSTokenManager,
alice: aliceSigner,
bob: bobSigner
};
}
beforeEach(async function () {
const fixture = await loadFixture(setupFixture);
htsManager = fixture.htsManager;
alice = fixture.alice;
bob = fixture.bob;
});
/**
* Helper function to get response code from receipt
*/
function getResponseCodeFromReceipt(
receipt: ContractTransactionReceipt | null
): bigint | null {
const responseEvent = receipt?.logs.find((log: any) => {
try {
const parsed = htsManager.interface.parseLog({
topics: log.topics as string[],
data: log.data
});
return parsed?.name === "ResponseCode";
} catch {
return false;
}
});
if (responseEvent) {
const parsed = htsManager.interface.parseLog({
topics: responseEvent.topics as string[],
data: responseEvent.data
});
return parsed?.args[0];
}
return null;
}
/**
* Helper function to get minted token info from receipt
*/
function getMintedTokenInfoFromReceipt(
receipt: any
): { newTotalSupply: bigint } | null {
const mintedEvent = receipt?.logs.find((log: any) => {
try {
const parsed = htsManager.interface.parseLog({
topics: log.topics as string[],
data: log.data
});
return parsed?.name === "MintedToken";
} catch {
return false;
}
});
if (mintedEvent) {
const parsed = htsManager.interface.parseLog({
topics: mintedEvent.topics as string[],
data: mintedEvent.data
});
return { newTotalSupply: parsed?.args[0] };
}
return null;
}
/* =========================
Token Info Tests
========================= */
describe("Token Info", function () {
it("should get token info for the pre-created token", async function () {
const tx = await htsManager.getTokenInfoPublic(TOKEN_ADDRESS);
const receipt = await tx.wait();
const responseCode = getResponseCodeFromReceipt(receipt);
expect(responseCode).to.equal(SUCCESS);
console.log("Successfully retrieved token info");
});
it("should get fungible token info for the pre-created token", async function () {
const tx = await htsManager.getFungibleTokenInfoPublic(TOKEN_ADDRESS);
const receipt = await tx.wait();
const infoEvent = receipt?.logs.find((log) => {
try {
const parsed = htsManager.interface.parseLog({
topics: log.topics as string[],
data: log.data
});
return parsed?.name === "FungibleTokenInfo";
} catch {
return false;
}
});
expect(infoEvent).to.not.be.undefined;
console.log("Successfully retrieved fungible token info");
});
it("should read token properties via ERC-20 interface", async function () {
const token = await ethers.getContractAt(
[
"function name() view returns (string)",
"function symbol() view returns (string)",
"function decimals() view returns (uint8)",
"function totalSupply() view returns (uint256)",
"function balanceOf(address) view returns (uint256)"
],
TOKEN_ADDRESS
);
const name = await token.name();
const symbol = await token.symbol();
const decimals = await token.decimals();
const totalSupply = await token.totalSupply();
console.log(`Token Name: ${name}`);
console.log(`Token Symbol: ${symbol}`);
console.log(`Token Decimals: ${decimals}`);
console.log(`Token Total Supply: ${totalSupply}`);
expect(name).to.equal("TestForkToken");
expect(symbol).to.equal("TFT");
});
});
/* =========================
Token Minting Tests
========================= */
describe("Token Minting", function () {
it("should mint tokens successfully", async function () {
const mintAmount = 1000n;
const tx = await htsManager.mintTokenPublic(
TOKEN_ADDRESS,
mintAmount,
[]
);
const receipt = await tx.wait();
const responseCode = getResponseCodeFromReceipt(receipt);
expect(responseCode).to.equal(SUCCESS);
const mintInfo = getMintedTokenInfoFromReceipt(receipt);
expect(mintInfo).to.not.be.null;
console.log(
`Minted ${mintAmount} tokens. New total supply: ${mintInfo?.newTotalSupply}`
);
});
it("should mint tokens multiple times and track total supply", async function () {
// First mint
const tx1 = await htsManager.mintTokenPublic(TOKEN_ADDRESS, 500n, []);
const receipt1 = await tx1.wait();
const mintInfo1 = getMintedTokenInfoFromReceipt(receipt1);
console.log(
`First mint - New total supply: ${mintInfo1?.newTotalSupply}`
);
// Second mint
const tx2 = await htsManager.mintTokenPublic(TOKEN_ADDRESS, 300n, []);
const receipt2 = await tx2.wait();
const mintInfo2 = getMintedTokenInfoFromReceipt(receipt2);
console.log(
`Second mint - New total supply: ${mintInfo2?.newTotalSupply}`
);
// Verify supply increased
expect(mintInfo2?.newTotalSupply).to.be.gt(
mintInfo1?.newTotalSupply || 0n
);
});
it("should increase treasury balance after minting", async function () {
// Get ERC-20 interface
const token = await ethers.getContractAt(
["function balanceOf(address) view returns (uint256)"],
TOKEN_ADDRESS
);
// Check balance before mint
const balanceBefore = await token.balanceOf(DEPLOYED_CONTRACT);
console.log(`Treasury balance before mint: ${balanceBefore}`);
// Mint tokens
const mintAmount = 2000n;
const tx = await htsManager.mintTokenPublic(
TOKEN_ADDRESS,
mintAmount,
[]
);
await tx.wait();
// Check balance after mint
const balanceAfter = await token.balanceOf(DEPLOYED_CONTRACT);
console.log(`Treasury balance after mint: ${balanceAfter}`);
expect(balanceAfter).to.equal(balanceBefore + mintAmount);
});
});
/* =========================
Token Transfer Tests
========================= */
describe("Token Transfers", function () {
it("should transfer tokens from treasury to alice", async function () {
// First mint some tokens
const mintAmount = 5000n;
await htsManager.mintTokenPublic(TOKEN_ADDRESS, mintAmount, []);
// Get ERC-20 interface
const token = await ethers.getContractAt(
["function balanceOf(address) view returns (uint256)"],
TOKEN_ADDRESS
);
// Check alice's balance before transfer
const aliceBalanceBefore = await token.balanceOf(alice.address);
console.log(`Alice balance before transfer: ${aliceBalanceBefore}`);
// Transfer tokens from treasury (contract) to alice
const transferAmount = 1000n;
const tx = await htsManager.transferTokenPublic(
TOKEN_ADDRESS,
DEPLOYED_CONTRACT, // sender (treasury/contract)
alice.address, // receiver
transferAmount
);
const receipt = await tx.wait();
const responseCode = getResponseCodeFromReceipt(receipt);
expect(responseCode).to.equal(SUCCESS);
// Check alice's balance after transfer
const aliceBalanceAfter = await token.balanceOf(alice.address);
console.log(`Alice balance after transfer: ${aliceBalanceAfter}`);
expect(aliceBalanceAfter).to.equal(aliceBalanceBefore + transferAmount);
});
it("should transfer tokens to multiple recipients", async function () {
// Mint tokens first
const mintAmount = 10000n;
await htsManager.mintTokenPublic(TOKEN_ADDRESS, mintAmount, []);
const token = await ethers.getContractAt(
["function balanceOf(address) view returns (uint256)"],
TOKEN_ADDRESS
);
// Transfer to alice
const aliceAmount = 2000n;
await htsManager.transferTokenPublic(
TOKEN_ADDRESS,
DEPLOYED_CONTRACT,
alice.address,
aliceAmount
);
console.log(`Transferred ${aliceAmount} to alice`);
// Transfer to bob
const bobAmount = 3000n;
await htsManager.transferTokenPublic(
TOKEN_ADDRESS,
DEPLOYED_CONTRACT,
bob.address,
bobAmount
);
console.log(`Transferred ${bobAmount} to bob`);
// Verify balances
const aliceBalance = await token.balanceOf(alice.address);
const bobBalance = await token.balanceOf(bob.address);
expect(aliceBalance).to.be.gte(aliceAmount);
expect(bobBalance).to.be.gte(bobAmount);
console.log(`Alice final balance: ${aliceBalance}`);
console.log(`Bob final balance: ${bobBalance}`);
});
it("should mint and then transfer in sequence", async function () {
const token = await ethers.getContractAt(
[
"function balanceOf(address) view returns (uint256)",
"function totalSupply() view returns (uint256)"
],
TOKEN_ADDRESS
);
// Get initial state
const initialSupply = await token.totalSupply();
console.log(`Initial total supply: ${initialSupply}`);
// Mint tokens
const mintAmount = 3000n;
const mintTx = await htsManager.mintTokenPublic(
TOKEN_ADDRESS,
mintAmount,
[]
);
await mintTx.wait();
console.log(`Minted ${mintAmount} tokens`);
// Verify supply increased
const supplyAfterMint = await token.totalSupply();
console.log(`Supply after mint: ${supplyAfterMint}`);
expect(supplyAfterMint).to.equal(initialSupply + mintAmount);
// Transfer some tokens
const transferAmount = 1500n;
const transferTx = await htsManager.transferTokenPublic(
TOKEN_ADDRESS,
DEPLOYED_CONTRACT,
alice.address,
transferAmount
);
await transferTx.wait();
console.log(`Transferred ${transferAmount} to alice`);
// Verify alice received tokens
const aliceBalance = await token.balanceOf(alice.address);
expect(aliceBalance).to.be.gte(transferAmount);
console.log(`Alice balance: ${aliceBalance}`);
// Total supply should remain same after transfer
const supplyAfterTransfer = await token.totalSupply();
expect(supplyAfterTransfer).to.equal(supplyAfterMint);
});
});
/* =========================
Token Creation Tests
========================= */
describe("Token Creation", function () {
it("should create a new token via the contract", async function () {
const tx = await htsManager.createFungibleTokenPublic(
"New Test Token",
"NTT",
{ value: ethers.parseEther("15") }
);
const receipt = await tx.wait();
const createdEvent = receipt?.logs.find((log) => {
try {
const parsed = htsManager.interface.parseLog({
topics: log.topics as string[],
data: log.data
});
return parsed?.name === "CreatedToken";
} catch {
return false;
}
});
expect(createdEvent).to.not.be.undefined;
if (createdEvent) {
const parsed = htsManager.interface.parseLog({
topics: createdEvent.topics as string[],
data: createdEvent.data
});
const newTokenAddress = parsed?.args[0];
console.log(`Created new token at: ${newTokenAddress}`);
expect(newTokenAddress).to.not.equal(ethers.ZeroAddress);
}
});
});
/* =========================
Fork Verification
========================= */
describe("Fork Network Verification", function () {
it("should be connected to a forked network", async function () {
const blockNumber = await ethers.provider.getBlockNumber();
console.log(`Current fork block number: ${blockNumber}`);
expect(blockNumber).to.be.gt(0);
});
it("should be interacting with real deployed contract", async function () {
const contractCode = await ethers.provider.getCode(DEPLOYED_CONTRACT);
expect(contractCode).to.not.equal("0x");
console.log(
`Contract at ${DEPLOYED_CONTRACT} has ${contractCode.length} bytes of code`
);
});
it("should be able to access the pre-created token", async function () {
const tokenCode = await ethers.provider.getCode(TOKEN_ADDRESS);
expect(tokenCode).to.not.equal("0x");
console.log(`Token at ${TOKEN_ADDRESS} exists on the forked network`);
});
it("should verify contract is the token treasury", async function () {
const token = await ethers.getContractAt(
["function balanceOf(address) view returns (uint256)"],
TOKEN_ADDRESS
);
// The contract should be the treasury (where minted tokens go)
// After we mint, the contract's balance should be > 0
const mintTx = await htsManager.mintTokenPublic(TOKEN_ADDRESS, 100n, []);
await mintTx.wait();
const contractBalance = await token.balanceOf(DEPLOYED_CONTRACT);
expect(contractBalance).to.be.gt(0n);
console.log(`Contract (treasury) balance: ${contractBalance}`);
});
});
});
```
**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:
```bash theme={null}
npx hardhat test
```
You should see output similar to:
```bash theme={null}
HTSTokenManager - HTS Forking Tests
Token Info
Successfully retrieved token info
✔ should get token info for the pre-created token (870ms)
Successfully retrieved fungible token info
✔ should get fungible token info for the pre-created token (108ms)
Token Name: TestForkToken
Token Symbol: TFT
Token Decimals: 0
Token Total Supply: 0
✔ should read token properties via ERC-20 interface (196ms)
Token Minting
Minted 1000 tokens. New total supply: 1000
✔ should mint tokens successfully (602ms)
First mint - New total supply: 500
Second mint - New total supply: 800
✔ should mint tokens multiple times and track total supply
Treasury balance before mint: 0
Treasury balance after mint: 2000
✔ should increase treasury balance after minting
Token Transfers
Alice balance before transfer: 0
Alice balance after transfer: 1000
✔ should transfer tokens from treasury to alice (302ms)
Transferred 2000 to alice
Transferred 3000 to bob
Alice final balance: 2000
Bob final balance: 3000
✔ should transfer tokens to multiple recipients (275ms)
Initial total supply: 0
Minted 3000 tokens
Supply after mint: 3000
Transferred 1500 to alice
Alice balance: 1500
✔ should mint and then transfer in sequence
Token Creation
Created new token at: 0x0000000000000000000000000000000000000408
✔ should create a new token via the contract (487ms)
Fork Network Verification
Current fork block number: 29968809
✔ should be connected to a forked network
Contract at 0x525F2a20563A052F7dC65df59106EC82f0584102 has 17144 bytes of code
✔ should be interacting with real deployed contract
Token at 0x000000000000000000000000000000000073E8dC exists on the forked network
✔ should be able to access the pre-created token
Contract (treasury) balance: 100
✔ should verify contract is the token treasury
14 passing (18s)
```
### 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:
```typescript theme={null}
// Impersonate an address
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [someAddress]
});
// Get a signer for that address
const impersonatedSigner = await ethers.getSigner(someAddress);
// Act as that account
await token.connect(impersonatedSigner).transfer(recipient, amount);
```
**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](/evm/tools/hardhat/forking-basic) 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:
```typescript theme={null}
// Fund an account with 100 HBAR (hex wei)
await network.provider.send("hardhat_setBalance", [
accountAddress,
"0x56BC75E2D63100000" // 100 HBAR in hex
]);
```
***
## Further Learning & Next Steps
1. [**Forking Hedera Network for Local Testing**](/evm/development/forking)\
Deep dive into how Hedera forking works under the hood
2. [**How to Fork Hedera with Foundry**](/evm/tools/foundry/forking)\
Learn fork testing with Foundry framework
3. [**hedera-forking Repository**](https://github.com/hashgraph/hedera-forking)\
Explore examples and documentation
4. [**Hiero Contracts Repository**](https://github.com/hiero-ledger/hiero-contracts)\
Explore HTS System Contracts interfaces
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
```
```
# How to Fork the Hedera Network with Hardhat - Basic ERC-20 Contract (Part 1)
Source: https://docs.hedera.com/evm/tools/hardhat/forking-basic
In this tutorial, you'll fork Hedera testnet using Hardhat and interact with a basic ERC-20 token on the forked network. This is an introductory guide to local fork testing with Hardhat using TypeScript.
This guide shows how to:
* Fork Hedera testnet using Hardhat
* Deploy an ERC-20 contract to Hedera testnet
* Run Hardhat tests on a fork of Hedera testnet
* Read and interact with an existing ERC-20 contract by its EVM address (e.g., `balanceOf`, `name`, `symbol`, `transfer`), with minimal setup
* The process to set up and run tests is similar for mainnet as well
References:
* Repo: [hashgraph/hedera-forking](https://github.com/hashgraph/hedera-forking)
* Readme sections: Hardhat plugin, Running your Tests
* Examples: [`examples/hardhat-hts/`](https://github.com/hashgraph/hedera-forking/tree/main/examples/hardhat-hts)
For a deeper understanding of how Hedera forking works and its limitations,
see [Forking Hedera Network for Local
Testing](/evm/development/forking).
You can take a look at the complete code in the [**basic-erc20-fork-test-hardhat
repository**](https://github.com/hedera-dev/tutorial-hedera-fork-testing/tree/main/hardhat/basic-erc20-fork-test-hardhat).
***
## Prerequisites
* Node.js (v18 or later) and npm
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/)
* Basic understanding of Solidity and TypeScript
* 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](#step-1:-project-setup)
2. [Step 2: Create the ERC-20 Contract and Deploy to Testnet](#step-2:-create-the-erc-20-contract-and-deploy-to-testnet)
3. [Step 3: Write Tests for the Forked Network](#step-3:-write-tests-for-the-forked-network)
4. [Step 4: Run Tests on the Forked Network](#step-4:-run-tests-on-the-forked-network)
***
## Step 1: Project Setup
### Initialize Project
Create a new directory and initialize the project:
```bash theme={null}
mkdir basic-erc20-fork-test-hardhat
cd basic-erc20-fork-test-hardhat
npm init -y
```
### Install Dependencies
Create or update your `package.json` with all required dependencies:
```json package.json theme={null}
{
"name": "basic-erc20-fork-test-hardhat",
"version": "1.0.0",
"description": "Hedera Fork Testing with Hardhat",
"private": true,
"scripts": {
"compile": "hardhat compile",
"test": "hardhat test",
"deploy:testnet": "hardhat run scripts/deploy.ts --network hederaTestnet"
},
"license": "MIT",
"devDependencies": {
"@hashgraph/system-contracts-forking": "0.1.2",
"@nomicfoundation/hardhat-chai-matchers": "^2.0.0",
"@nomicfoundation/hardhat-ethers": "^3.0.0",
"@nomicfoundation/hardhat-ignition": "^0.15.16",
"@nomicfoundation/ignition-core": "^0.15.15",
"@nomicfoundation/hardhat-ignition-ethers": "^0.15.0",
"@nomicfoundation/hardhat-network-helpers": "^1.0.0",
"@nomicfoundation/hardhat-toolbox": "5.0.0",
"@nomicfoundation/hardhat-verify": "^2.0.0",
"@openzeppelin/contracts": "^5.0.0",
"@typechain/ethers-v6": "^0.5.0",
"@typechain/hardhat": "^9.0.0",
"@types/chai": "^4.2.0",
"@types/mocha": ">=9.1.0",
"@types/node": "^20.0.0",
"chai": "^4.2.0",
"hardhat": "2.22.19",
"hardhat-gas-reporter": "^1.0.8",
"solidity-coverage": "^0.8.1",
"ts-node": "^10.9.0",
"typechain": "^8.3.0",
"typescript": "^5.0.0"
}
}
```
Then install all dependencies:
```bash theme={null}
npm install --legacy-peer-deps
```
**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
* **`--legacy-peer-deps`** - Required to resolve dependency conflicts between these versions
Verify Hardhat is installed correctly:
```bash theme={null}
npx hardhat --version
# Should output: 2.22.19
```
### Create Project Structure
Create the necessary directories:
```bash theme={null}
mkdir contracts test scripts
```
### Configure TypeScript
Create `tsconfig.json` in your project root:
```json tsconfig.json theme={null}
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"outDir": "dist",
"resolveJsonModule": true
},
"include": ["./scripts", "./test", "./typechain-types"],
"files": ["./hardhat.config.ts"]
}
```
### Configure Hardhat
Create `hardhat.config.ts` in your project root. This file must exist before you can run any Hardhat commands:
```typescript hardhat.config.ts theme={null}
import { HardhatUserConfig, vars } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "@hashgraph/system-contracts-forking/plugin";
// Load configuration variables
const HEDERA_RPC_URL = vars.get("HEDERA_RPC_URL");
const HEDERA_PRIVATE_KEY = vars.get("HEDERA_PRIVATE_KEY");
const config: HardhatUserConfig = {
solidity: "0.8.33",
networks: {
// Network for deploying to real testnet
hederaTestnet: {
url: HEDERA_RPC_URL,
accounts: [HEDERA_PRIVATE_KEY],
chainId: 296
},
// Local fork of testnet for testing
hardhat: {
forking: {
url: HEDERA_RPC_URL,
// Pin to a specific block for reproducible tests
// Update this after deploying your contract
blockNumber: 29900000,
// @ts-ignore - custom properties for hedera-forking plugin
chainId: 296,
// @ts-ignore
workerPort: 10001
}
}
}
};
export default config;
```
**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
### 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:
```bash theme={null}
npx hardhat vars set HEDERA_RPC_URL
```
When prompted, enter: `https://testnet.hashio.io/api`
```bash theme={null}
npx hardhat vars set HEDERA_PRIVATE_KEY
```
When prompted, enter the **HEX Encoded Private Key** for your **ECDSA account** from the [Hedera Portal](https://portal.hedera.com/).
Make sure your ECDSA account exists on **testnet** and has sufficient HBAR for
deployment. You can fund your testnet account using the [Hedera
Portal](https://portal.hedera.com/).
You can verify your variables are set correctly:
```bash theme={null}
npx hardhat vars list
```
***
## Step 2: Create the ERC-20 Contract and Deploy to Testnet
### Create the Contract
Create a new file `contracts/ERC20Token.sol`:
```solidity contracts/ERC20Token.sol theme={null}
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.33;
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract ERC20Token is ERC20, Ownable {
constructor(address initialOwner, address recipient)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
{
_mint(recipient, 10000 * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
```
This contract:
* Creates a basic ERC-20 token named "MyToken" with symbol "MTK"
* Mints 10,000 tokens to a recipient on deployment
* Has an `onlyOwner` `mint` function for additional minting
### Compile the Contract
```bash theme={null}
npx hardhat compile
```
This will also generate TypeScript types in the `typechain-types` directory.
### Create Deployment Script
Create a new file `scripts/deploy.ts`:
```typescript scripts/deploy.ts theme={null}
import { ethers } from "hardhat";
async function main(): Promise {
const [deployer] = await ethers.getSigners();
console.log("Deploying contracts with the account:", deployer.address);
const balance = await ethers.provider.getBalance(deployer.address);
console.log("Account balance:", ethers.formatEther(balance), "HBAR");
// Deploy ERC20Token with deployer as both owner and initial recipient
const ERC20Token = await ethers.getContractFactory("ERC20Token");
const token = await ERC20Token.deploy(deployer.address, deployer.address);
await token.waitForDeployment();
const tokenAddress = await token.getAddress();
console.log("ERC20Token deployed to:", tokenAddress);
console.log(
"View on HashScan: https://hashscan.io/testnet/contract/" + tokenAddress
);
// Get deployment block number for fork testing reference
const blockNumber = await ethers.provider.getBlockNumber();
console.log("Deployed at block number:", blockNumber);
console.log("\n=== IMPORTANT ===");
console.log("Save this contract address for your fork tests!");
console.log(
"Update blockNumber in hardhat.config.ts to >=",
blockNumber,
"when forking"
);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
```
### Deploy to Testnet
Deploy your contract to Hedera testnet:
```bash theme={null}
npx hardhat run scripts/deploy.ts --network hederaTestnet
```
You should see output similar to:
```bash theme={null}
Deploying contracts with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Account balance: 63051.15495643 HBAR
ERC20Token deployed to: 0xea606E2D68Ff9F211756b8cfd9026a7Eb76845C9
View on HashScan: https://hashscan.io/testnet/contract/0xea606E2D68Ff9F211756b8cfd9026a7Eb76845C9
Deployed at block number: 29965248
=== IMPORTANT ===
Save this contract address for your fork tests!
Update blockNumber in hardhat. config.ts to >= 29965248 when forking
```
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 from the deployment output:
```typescript theme={null}
blockNumber: 29965248, // <-- Update this with your deployment block or higher
```
We have already deployed this ERC-20 contract on testnet at [0xea606E2D68Ff9F211756b8cfd9026a7Eb76845C9](https://hashscan.io/testnet/contract/0xea606E2D68Ff9F211756b8cfd9026a7Eb76845C9) 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 already deployed contract on the forked testnet. This is the real power of fork testing - you can test against real deployed contracts without spending gas or affecting the live network.
Create a new file `test/ERC20Token.test.ts`:
Make sure to update the `DEPLOYED_CONTRACT` constant below with the
address of your deployed contract from Step 2.
```typescript test/ERC20Token.test.ts theme={null}
import { expect } from "chai";
import { ethers, network } from "hardhat";
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { ERC20Token } from "../typechain-types";
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
describe("ERC20Token - Forked Network Tests", function () {
// Your deployed testnet contract:
const DEPLOYED_CONTRACT = "YOUR_CONTRACT_ADDRESS"; // <-- Update with your deployed address
let token: ERC20Token;
let realOwner: HardhatEthersSigner;
let alice: HardhatEthersSigner;
let bob: HardhatEthersSigner;
/**
* Fixture to set up the test environment.
* Using fixtures ensures each test starts with a clean state.
*/
async function setupFixture(): Promise<{
token: ERC20Token;
realOwner: HardhatEthersSigner;
ownerAddress: string;
alice: HardhatEthersSigner;
bob: HardhatEthersSigner;
}> {
// Bind to the deployed contract on the forked network
const tokenContract = await ethers.getContractAt(
"ERC20Token",
DEPLOYED_CONTRACT
);
// Discover the real on-chain owner (from Ownable)
const ownerAddress = await tokenContract.owner();
// Impersonate the real owner so we can call onlyOwner functions
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [ownerAddress]
});
const impersonatedOwner = await ethers.getSigner(ownerAddress);
// Fund the impersonated account with ETH for gas
await network.provider.send("hardhat_setBalance", [
ownerAddress,
"0x56BC75E2D63100000" // 100 ETH in hex
]);
// Get local test accounts for recipients
const [, aliceSigner, bobSigner] = await ethers.getSigners();
// Fund local accounts
await network.provider.send("hardhat_setBalance", [
aliceSigner.address,
"0x56BC75E2D63100000"
]);
await network.provider.send("hardhat_setBalance", [
bobSigner.address,
"0x56BC75E2D63100000"
]);
return {
token: tokenContract as ERC20Token,
realOwner: impersonatedOwner,
ownerAddress,
alice: aliceSigner,
bob: bobSigner
};
}
beforeEach(async function () {
const fixture = await loadFixture(setupFixture);
token = fixture.token;
realOwner = fixture.realOwner;
alice = fixture.alice;
bob = fixture.bob;
});
/* =========================
Basic Info
========================= */
describe("Token Information (Reading from Forked State)", function () {
it("should read name and symbol from deployed contract", async function () {
expect(await token.name()).to.equal("MyToken");
expect(await token.symbol()).to.equal("MTK");
});
it("should read decimals from deployed contract", async function () {
expect(await token.decimals()).to.equal(18n);
});
it("should read total supply from deployed contract", async function () {
const totalSupply = await token.totalSupply();
console.log(
`Total supply on testnet: ${ethers.formatEther(totalSupply)} MTK`
);
expect(totalSupply).to.be.gt(0n);
});
it("should read owner balance from deployed contract", async function () {
const ownerAddress = await token.owner();
const balance = await token.balanceOf(ownerAddress);
console.log(
`Owner (${ownerAddress}) balance: ${ethers.formatEther(balance)} MTK`
);
expect(balance).to.be.gt(0n);
});
});
/* =========================
Ownership
========================= */
describe("Ownership (Testing with Impersonation)", function () {
it("should reject minting from non-owner", async function () {
// Alice (not the owner) tries to mint → should revert
await expect(
token.connect(alice).mint(alice.address, ethers.parseEther("100"))
).to.be.revertedWithCustomError(token, "OwnableUnauthorizedAccount");
});
it("should allow real owner to mint new tokens", async function () {
const balanceBefore = await token.balanceOf(alice.address);
// Use the impersonated real owner to mint
await token
.connect(realOwner)
.mint(alice.address, ethers.parseEther("500"));
const balanceAfter = await token.balanceOf(alice.address);
expect(balanceAfter).to.equal(balanceBefore + ethers.parseEther("500"));
});
});
/* =========================
Transfers
========================= */
describe("Transfers (Modifying Forked State)", function () {
it("should transfer tokens from owner to alice", async function () {
const amount = ethers.parseEther("100");
const balanceBefore = await token.balanceOf(alice.address);
// Transfer from impersonated owner
await token.connect(realOwner).transfer(alice.address, amount);
const balanceAfter = await token.balanceOf(alice.address);
expect(balanceAfter).to.equal(balanceBefore + amount);
});
it("should handle multiple transfers correctly", async function () {
// Mint tokens to alice first
await token
.connect(realOwner)
.mint(alice.address, ethers.parseEther("1000"));
const aliceInitial = await token.balanceOf(alice.address);
const bobInitial = await token.balanceOf(bob.address);
// Alice transfers to bob
await token
.connect(alice)
.transfer(bob.address, ethers.parseEther("300"));
expect(await token.balanceOf(alice.address)).to.equal(
aliceInitial - ethers.parseEther("300")
);
expect(await token.balanceOf(bob.address)).to.equal(
bobInitial + ethers.parseEther("300")
);
});
it("should fail transfer with insufficient balance", async function () {
// Bob has no tokens initially, should fail
await expect(
token.connect(bob).transfer(alice.address, ethers.parseEther("100"))
).to.be.revertedWithCustomError(token, "ERC20InsufficientBalance");
});
});
/* =========================
Allowances
========================= */
describe("Allowances", function () {
it("should approve and check allowance", async function () {
// Mint tokens to alice
await token
.connect(realOwner)
.mint(alice.address, ethers.parseEther("1000"));
// Alice approves bob
await token.connect(alice).approve(bob.address, ethers.parseEther("500"));
expect(await token.allowance(alice.address, bob.address)).to.equal(
ethers.parseEther("500")
);
});
it("should transfer using transferFrom after approval", async function () {
// Mint tokens to alice
await token
.connect(realOwner)
.mint(alice.address, ethers.parseEther("1000"));
// Alice approves bob
await token.connect(alice).approve(bob.address, ethers.parseEther("500"));
const aliceBefore = await token.balanceOf(alice.address);
// Bob transfers from alice to himself
await token
.connect(bob)
.transferFrom(alice.address, bob.address, ethers.parseEther("200"));
expect(await token.balanceOf(bob.address)).to.equal(
ethers.parseEther("200")
);
expect(await token.balanceOf(alice.address)).to.equal(
aliceBefore - ethers.parseEther("200")
);
expect(await token.allowance(alice.address, bob.address)).to.equal(
ethers.parseEther("300")
);
});
it("should fail transferFrom without approval", async function () {
// Mint tokens to alice but no approval for bob
await token
.connect(realOwner)
.mint(alice.address, ethers.parseEther("1000"));
await expect(
token
.connect(bob)
.transferFrom(alice.address, bob.address, ethers.parseEther("100"))
).to.be.revertedWithCustomError(token, "ERC20InsufficientAllowance");
});
});
/* =========================
Supply Changes
========================= */
describe("Supply Changes", function () {
it("should track supply changes after minting", async function () {
const supplyBefore = await token.totalSupply();
await token
.connect(realOwner)
.mint(alice.address, ethers.parseEther("5000"));
const supplyAfter = await token.totalSupply();
expect(supplyAfter).to.equal(supplyBefore + ethers.parseEther("5000"));
});
});
/* =========================
Fork Verification
========================= */
describe("Fork Network Verification", function () {
it("should be connected to a forked network", async function () {
const blockNumber = await ethers.provider.getBlockNumber();
console.log(`Current fork block number: ${blockNumber}`);
expect(blockNumber).to.be.gt(0);
});
it("should be interacting with real deployed contract", async function () {
// Verify we're reading from the actual deployed contract
const contractCode = await ethers.provider.getCode(DEPLOYED_CONTRACT);
expect(contractCode).to.not.equal("0x");
console.log(
`Contract at ${DEPLOYED_CONTRACT} has ${contractCode.length} bytes of code`
);
});
it("should preserve original state for each test (via fixtures)", async function () {
// Each test starts fresh because of loadFixture's snapshot/revert
const ownerAddress = await token.owner();
const originalBalance = await token.balanceOf(ownerAddress);
// This change only affects this test
await token
.connect(realOwner)
.transfer(alice.address, ethers.parseEther("100"));
// In the next test, the balance will be back to original
console.log(
`Original owner balance: ${ethers.formatEther(originalBalance)} MTK`
);
});
});
});
```
**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 contract using `getContractAt`
* **Impersonation** - Uses `hardhat_impersonateAccount` to act as the real owner
* **Reads real state** - Token info, balances, etc. come from the actual testnet deployment
* **Local modifications** - All transfers, mints happen only on the local fork
* **No testnet changes** - The real testnet is never modified
* **Uses fixtures** - `loadFixture` ensures each test starts with a clean state
***
## Step 4: Run Tests on the Forked Network
Run your tests against the forked Hedera testnet:
```bash theme={null}
npx hardhat test
```
You should see output similar to:
```bash theme={null}
ERC20Token - Forked Network Tests
Token Information (Reading from Forked State)
✔ should read name and symbol from deployed contract (371ms)
✔ should read decimals from deployed contract
Total supply on testnet: 10000.0 MTK
✔ should read total supply from deployed contract (73ms)
Owner (0xA98556A4deeB07f21f8a66093989078eF86faa30) balance: 10000.0 MTK
✔ should read owner balance from deployed contract (78ms)
Ownership (Testing with Impersonation)
✔ should reject minting from non-owner (89ms)
✔ should allow real owner to mint new tokens (72ms)
Transfers (Modifying Forked State)
✔ should transfer tokens from owner to alice
✔ should handle multiple transfers correctly (76ms)
✔ should fail transfer with insufficient balance
Allowances
✔ should approve and check allowance (85ms)
✔ should transfer using transferFrom after approval
✔ should fail transferFrom without approval
Supply Changes
✔ should track supply changes after minting
Fork Network Verification
Current fork block number: 29965248
✔ should be connected to a forked network
Contract at 0xea606E2D68Ff9F211756b8cfd9026a7Eb76845C9 has 9016 bytes of code
✔ should be interacting with real deployed contract
Original owner balance: 10000.0 MTK
✔ should preserve original state for each test (via fixtures)
16 passing (2s)
```
### 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.
***
## 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
```typescript theme={null}
// Tell Hardhat to let us sign as this address
await network.provider.request({
method: "hardhat_impersonateAccount",
params: [someAddress]
});
// Now we can get a signer for that address
const impersonatedSigner = await ethers.getSigner(someAddress);
// Use it to call functions as if we were that account
await token.connect(impersonatedSigner).transfer(recipient, amount);
```
### Local vs. Remote State
| Action | Affects Local Fork | Affects Testnet |
| -------------------------- | ------------------ | --------------- |
| Read balances | ✅ (cached) | ❌ (read-only) |
| Transfer tokens | ✅ | ❌ |
| Mint new tokens | ✅ | ❌ |
| Deploy new contracts | ✅ | ❌ |
| Impersonate accounts | ✅ | ❌ |
| Changes persist after test | ❌ (reset) | N/A |
***
## Next Steps
Now that you understand fork testing with deployed contracts, you can:
1. **Test contract upgrades** - Fork, deploy upgraded version, compare behavior
2. **Simulate user interactions** - Impersonate real users to test edge cases
3. **Move to Part 2** - Learn how to work with HTS System Contracts
In [Part
2](/evm/tools/hardhat/forking-advanced),
you'll learn how to interact with the Hedera Token Service (HTS) using system
contract precompiles, including interacting with existing HTS tokens and
understanding the limitations of the forking emulation layer.
***
## Further Learning & Next Steps
1. [**How to Fork Hedera with Hardhat (Part 2)**](/evm/tools/hardhat/forking-advanced)\
Learn to work with HTS System Contracts and understand emulation limitations
2. [**Forking Hedera Network for Local Testing**](/evm/development/forking)\
Deep dive into how Hedera forking works under the hood
3. [**How to Fork Hedera with Foundry**](/evm/tools/foundry/forking)\
Learn fork testing with Foundry framework
4. [**hedera-forking Repository**](https://github.com/hashgraph/hedera-forking)\
Explore examples and documentation
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# Configuring Hardhat with Hedera Localnet/Testnet: A Step-by-Step Guide
Source: https://docs.hedera.com/evm/tools/hardhat/index
**Hiero Local Node Deprecation (September 2026)**
Hiero Local Node is entering a 6-month deprecation period. Support ends September 2026. Migrate local testing and CI workflows to [Solo](https://solo.hiero.org/docs/) before then. [Learn more](https://hedera.com/blog/hiero-local-node-deprecation-6-month-transition-to-solo/).
Developers building smart contracts on Hedera often use the **Hedera JSON-RPC Relay** to enable EVM tools like **Hardhat**. In this post, we'll walk through how to set up Hardhat to work with the **Hiero Local Node**, allowing for local deployment, debugging, and testing of smart contracts without using testnet resources.
Not sure whether to use the Hiero Local Node, Hashio, or a custom relay setup?
Read this [blog
post](/native/local-dev/setup-local-node)
comparing the different options.
You can take a look at the **complete code** in the [**Hedera-Code-Snippets
repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/tutorial-local-hardhat).
## Prerequisites
* Basic understanding of smart contracts.
* Basic understanding of [Node.js](https://nodejs.org/en/download) and JavaScript/TypeScript.
* Have [nodejs](https://nodejs.org/en/download) installed on your local machine.
* Basic understanding of [Hardhat EVM Development Tool](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3) and [Ethers](https://docs.ethers.org/v6/).
* Have [hardhat](https://www.npmjs.com/package/hardhat) and [ethers](https://www.npmjs.com/package/ethers) installed on your local machine.
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/).
***
## Video Tutorial
You can watch the video tutorial (which uses **Hardhat version 2**) or follow the step-by-step tutorial below (which uses **Hardhat version 3**).
Key differences in Hardhat 3:
* **compile → build**\
`npx hardhat compile` is now `npx hardhat build`. This is the big one. The v3 migration guide explicitly shows using the `build` task.
* **project init switch**\
v2 commonly used `npx hardhat` or `npx hardhat init` to bootstrap. In v3 it’s `npx hardhat --init`.
- **keystore helper commands are new**\
v3’s recommended flow includes a keystore plugin with commands like `npx hardhat keystore set HEDERA_RPC_URL` and `npx hardhat keystore set HEDERA_PRIVATE_KEY`. These weren’t standard in v2.
- **Foundry-compatiable Solidity tests**\
In addition to offering Javascript/Typescript integration tests, Hardhat v3 also integrates Foundry-compatible Solidity tests that allows developers to write unit tests directly in Solidity
* **Enhanced Network Management**\
v3 allows tasks to create and manage multiple network connections simultaneously which is a significant improvement over the single, fixed connection available in version 2. This provides greater flexibility for scripts and tests that interact with multiple networks.
*📚 Learn more from the official* [*Hardhat documentation*](https://hardhat.org/docs/getting-started)*.*
***
## Step 1: Set Up the Hiero Local Node
Hedera provides a local node configuration that includes a mirror node, a consensus node, and the JSON-RPC relay. You can run it via `npm`.
If you are use Testnet instead of a local node, you can skip this step.
**Clone, install, and run the node:**
```bash theme={null}
git clone https://github.com/hiero-ledger/hiero-local-node.git
cd hiero-local-node
npm install
npm run start
```
Once all the containers have started, the Hiero Local Node is up and running. This includes a Consensus Node, Mirror Node and explorer, JSON RPC Relay, Block Node, Grafana UI, and Prometheus UI.
This command will also generate accounts on startup, which we will use later in our `hardhat.config.ts`.
***
## Step 2: Create a Hardhat Project
If you don’t already have a Hardhat project, create one:
```bash theme={null}
mkdir tutorial-local-hardhat
cd tutorial-local-hardhat
npx hardhat --init
```
Make sure to select "**Hardhat 3 -> Typescript Hardhat Project using Mocha and Ethers.js"** and accept the default values. Hardhat will configure your project correctly and install the required dependencies like `hardhat` and `@nomicfoundation/hardhat-ethers` (This includes plugins for Ethers, Mocha, Chai, and more).
### Project Structure
The Hardhat project initialization from the previous section creates the following file structure:
```
hardhat.config.ts
contracts
├── Counter.sol
└── Counter.t.sol
test
└── Counter.ts
ignition
└── modules
└── Counter.ts
scripts
└── send-op-tx.ts
```
Here's a quick overview of these files and directories:
* `hardhat.config.ts`: The main configuration file for your project. It defines settings like the Solidity compiler version, network configurations, and the plugins and tasks your project uses.
* `contracts`: Contains your project's Solidity contracts. You can also include Solidity test files here by using the `.t.sol` extension.
* `test`: Used for TypeScript integration tests. You can also include Solidity test files here.
* `ignition`: Holds your [Hardhat Ignition](https://hardhat.org/ignition) deployment modules, which describe how your contracts should be deployed.
* `scripts`: A place for any custom scripts that automate parts of your workflow. Scripts have full access to Hardhat's runtime and can use plugins, connect to networks, deploy contracts, and more.
***
## Step 3: Configure "hardhat.config.ts"
Before we make any changes to our Hardhat configuration file, let's set some configuration variables we will be referring to within the file later.
```bash theme={null}
# If you have already set this before, please use the --force flag
npx hardhat keystore set HEDERA_RPC_URL
```
If you are using a local node, set the `HEDERA_RPC_URL` to `https://localhost:7546`
If you are use Testnet set `HEDERA_RPC_URL` to `https://testnet.hashio.io/api`.
```bash theme={null}
# If you have already set this before, please use the --force flag
npx hardhat keystore set HEDERA_PRIVATE_KEY
```
If you are using a local node, for the `HEDERA_PRIVATE_KEY`, enter `0x105d050185ccb907fba04dd92d8de9e32c18305e097ab41dadda21489a211524`
**Note:** We got this private key from our console when we started our Hiero
Local Node.
If you are using Testnet, for the `HEDERA_PRIVATE_KEY`, enter your testnet **HEX Encoded Private Key for your ECDSA account** which you can get by signing up on the [Hedera Developer Portal](https://portal.hedera.com/dashboard)
Now, we can update our Hardhat config file to include the Hiero Local Node as a custom network:
```typescript hardhat.config.ts theme={null}
import type { HardhatUserConfig } from "hardhat/config";
import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
import { configVariable } from "hardhat/config";
const config: HardhatUserConfig = {
plugins: [hardhatToolboxViemPlugin],
solidity: {
profiles: {
default: {
version: "0.8.28",
},
production: {
version: "0.8.28",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
},
},
networks: {
hedera: {
type: "http",
url: configVariable("HEDERA_RPC_URL"),
accounts: [configVariable("HEDERA_PRIVATE_KEY")],
},
hardhatMainnet: {
type: "edr-simulated",
chainType: "l1",
},
hardhatOp: {
type: "edr-simulated",
chainType: "op",
},
},
};
export default config;
```
🔍 **Key Highlights**
* **Network config: `local`**
* `url`:
➤ This is the **Hedera JSON-RPC Relay** endpoint provided by the local node (via Docker). It enables EVM-compatible tools like Hardhat and Ethers.js to interact with the Hedera network.
* `accounts`:
➤ One predefined **ECDSA private key** provided by the Hiero Local Node.\
➤ This account is already **funded**, and can be used immediately to deploy and interact with smart contracts.
* **Set paths**:
* `./cache` & `./artifacts`: Manage build outputs and compilation caching to speed up repeated runs.
***
## Step 4: Build and Deploy
You can build the project using:
```bash theme={null}
# You can also do `npx hardhat compile`
npx hardhat build
```
You can run all the tests in your project—both Solidity and TypeScript—using the `test` task:
```bash theme={null}
npx hardhat test
# If you only want to run your Solidity tests, you can use this instead:
npx hardhat test solidity
# If you only want to run your mocha tests, you can use this instead:
npx hardhat test mocha
```
We won't be using `ignition` so we will remove all the unnecessary directories and files first:
```bash theme={null}
rm -rf ignition
# Let's rename the existing script
mv scripts/send-op-tx.ts scripts/send-tx.ts
```
Update the script `scripts/send-tx.ts`
```typescript theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "hedera",
});
console.log("Sending transaction on Hedera network");
const [sender] = await ethers.getSigners();
console.log("Sending 10_000_000_000 wei from", sender.address, "to itself");
console.log("Sending transaction");
const tx = await sender.sendTransaction({
to: sender.address,
value: 10_000_000_000n,
});
await tx.wait();
console.log("Transaction sent successfully");
```
Let's run our script to make sure everything works:
```bash theme={null}
~/projects/tutorial-local-hardhat >> npx hardhat run scripts/send-tx.ts
Compiling your Solidity contracts...
Sending transaction on Hedera network
Sending 10_000_000_000 wei from 0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69 to itself
Sending transaction
Transaction sent successfully
```
Let's write a deploy script that we will use to deploy the `Counter` contract.
```bash theme={null}
touch scripts/deploy.ts
```
The `scripts/deploy.ts` will have the following code:
```typescript theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "hedera",
});
async function main(): Promise {
// Get the signer of the tx and address for minting the token
const [deployer] = await ethers.getSigners();
console.log("Deploying contract with the account:", deployer.address);
const Counter = await ethers.getContractFactory("Counter", deployer);
const contract = await Counter.deploy();
await contract.waitForDeployment();
const address = await contract.getAddress();
console.log("Contract deployed at:", address);
}
main().catch(console.error);
```
Now, let's go ahead and deploy the contract:
```bash theme={null}
~/projects/tutorial-local-hardhat >> npx hardhat run scripts/deploy.ts
Compiling your Solidity contracts...
Compiled 1 Solidity file with solc 0.8.28 (evm target: cancun)
Deploying contract with the account: 0x67D8d32E9Bf1a9968a5ff53B87d777Aa8EBBEe69
Contract deployed at: 0xB3e022eBC7D5C5B1f4ca50b3D4A55173b34ceD49
```
***
## Further Learning & Next Steps
Want to take your local development setup even further? Here are some excellent tutorials to help you dive deeper into smart contract development on Hedera using Hardhat and Ethers.js:
1. [**How to Mint and Burn an ERC-721 Token** (Part 1)](/evm/tutorials/advanced/erc721-hardhat/part1-mint-burn)\
Learn how to create a basic ERC-721 NFT, mint it, and burn it on Hedera.
2. [**Access Control, Token URI, Pause & Transfer** (Part 2)](/evm/tutorials/advanced/erc721-hardhat/part2-access-control)\
Extend your NFT with features like pausing, setting token URIs, and restricting minting to specific roles.
3. [**Upgrade Your NFT with UUPS Proxies** (Part 3)](/evm/tutorials/advanced/erc721-hardhat/part3-upgradeable)\
Learn how to add upgradability to your smart contracts using OpenZeppelin’s UUPS proxy pattern on Hedera.
[GitHub](https://github.com/michielmulders) |
[LinkedIn](https://www.linkedin.com/in/michielmulders/)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# Development Tools
Source: https://docs.hedera.com/evm/tools/index
Tools and frameworks for building smart contracts on Hedera.
Hedera is fully EVM-compatible, so the same Solidity tooling you'd use on Ethereum works here including Hardhat, Foundry, Remix, ethers.js, web3.js, and so on. Point them at the [JSON-RPC relay](/evm/quickstart/setup-metamask) to use your existing workflow. The pages below cover the tools used most often on Hedera, plus a browser-based contract builder scaffold from the Hedera developer playgrounnd for projects that don't need a local environment.
## Browser tools
No install required. Author, compile, and deploy directly from the browser.
Scaffold ERC-20, ERC-721, ERC-1155, and HTS system-contract templates, then compile, deploy, and verify from the browser.
Familiar online Solidity IDE wired up for Hedera testnet via MetaMask and the JSON-RPC relay.
## Local development frameworks
Full-featured Solidity toolchains for serious development, testing, and CI.
The most widely used smart contract framework. Covers setup, testing, and forking workflows on Hedera.
Rust-based, fast, Solidity-native toolchain. Covers setup, testing, and HTS-aware forking on Hedera.
## Other tools
Legacy framework still supported on Hedera, useful for projects already invested in the Truffle workflow.
Index and query on-chain Hedera data using subgraphs.
## Which should I use?
* **Just exploring or building a quick demo?** Start with the [Contract Builder](/evm/tools/contract-builder) or [Remix](/evm/tools/remix) for zero setup and deploy in minutes.
* **Building a production dApp in JavaScript / TypeScript?** Use [Hardhat](/evm/tools/hardhat).
* **Prefer a Solidity-native test framework with fast execution?** Use [Foundry](/evm/tools/foundry).
* **Need to index contract data for a frontend?** Add [The Graph](/evm/tools/other/the-graph) on top of whichever framework you choose.
## Need help?
Join `#developer-general` for real-time help from engineers and the community.
File issues or contribute to the Hedera repos.
# Deploy a Subgraph Using The Graph and Hedera JSON-RPC Relay
Source: https://docs.hedera.com/evm/tools/other/the-graph
In this tutorial, you'll learn how to create and deploy a subgraph using The Graph protocol. By indexing specific network data using user-defined data structures called "subgraphs," developers can easily query the indexed data through a GraphQL API, creating robust backends for dApps. Subgraphs simplify the process of obtaining blockchain/network data for developers building dApps. This approach removes the complexities of interacting directly with the network, allowing developers to focus on building. Although Hedera supports subgraphs, its hosted service is currently unavailable, so we'll need to set up and run a local graph node to deploy our subgraph.
By the end of this tutorial, you'll be able to configure a mirror node, query data from your subgraph using the GraphQL API, and integrate it into your dApp. You'll also have a better understanding of how to define custom data schemas, indexing rules, and queries for your subgraph, allowing you to tailor it to your specific use case.
**Note:** While it is possible to present and interact with HTS tokens in a
similar manner as ERC-20/721 tokens, the network is presently unable to
capture all the expected ERC-20/721 event logs. In other words, if ERC-like
operations are conducted on HTS tokens, not all of them will be captured in
smart contract event logging.
***
## Prerequisites
* Basic understanding of JavaScript and NPM installed.
* Basic understanding of subgraphs and the [Graph CLI](#graph-cli-installation) installed.
* The deployed Greeter smart contract address from the [Hardhat tutorial](/evm/development/json-rpc).
* The [start block](#find-start-block) number of when the Greeter smart contract was first deployed.
* [Docker](https://www.docker.com/) `>= v20.10.x` installed and open on your machine. Run `docker -v` in your terminal to check the version you have installed.
1. Go to HashScan explorer [here](https://hashscan.io/).
2. Enter your public contract address or contract ID in the search bar.
3. Click on the `Create Transaction` ID ([0.0.902@1676712828.922009885](https://hashscan.io/testnet/transaction/1676712839.177574708?tid=0.0.902-1676712828-922009885)).
**\*Note:** When searching for contract addresses, there are two types with different formats - the **public** smart contract address (0x....) or **contract ID** (0.0.12345).\*
Open your terminal and run the following command:
```bash theme={null}
npm install -g @graphprotocol/graph-cli
```
Test to see if it was installed correctly by running:
```bash theme={null}
graph -v
```
**\*Note**: The Graph CLI will be installed globally, so you can run the command in any directory.\*
***
## Table of Contents
1. [Project Setup](#project-setup)
2. [Project Configuration](#project-configuration)
3. [Deploy Subgraph](#deploy-subgraph)
4. [Code Check](#code-check)
5. [Additional Resources](#additional-resources)
***
## Project Setup
Open a terminal window and navigate to the directory where you want your subgraph project stored. Clone the `hedera-subgraph-example` repo, change directories, and install dependencies:
```bash theme={null}
git clone https://github.com/hashgraph/hedera-subgraph-example.git
cd hedera-subgraph-example
npm install
```
Rename the `subgraph.template.yaml` file to `subgraph.yaml` before moving on to the next step. The subgraph project structure should look something like this:
```
subgraph-name
└───abis
│ | IGreeter.json
└───config
│ └──testnet.json
└───graph-node
│ └──docker-compose.yaml
└───src
│ │ mappings.ts
│ package-lock.json
│ package.json
│ README.md
│ schema.graphql
│ subgraph.yaml
```
In the `testnet.json` file, under the `config` folder, replace the `startBlock` and `Greeter` fields with your start block number and contract address. The JSON file should look something like this:
```json testnet.json theme={null}
{
"startBlock": 1050018,
"Greeter": "0xCc0d40EA9d2Dd16Ab5565ae91b121960d5e19e4e"
}
```
***
## Project Configuration
In this step, you will use the `Greeter` contract from the [Hardhat tutorial](/evm/development/json-rpc) as an example subgraph, to configure four main project files: the subgraph manifest, GraphQL schema, event mappings, and Docker compose configuration. The manifest specifies which events the subgraph will listen for, while mappings map each event emitted by the smart contract into entities that can be indexed.
#### Subgraph Manifest
The subgraph manifest (`subgraph.yaml`) contains important information about your subgraph, such as its name, description, and data sources. To specify the data sources your subgraph will index, you need to define the `dataSources` field in the manifest. It's also recommended to add the start block number to the `startBlock` property to reduce the indexing time. [Here](#find-start-block)'s a guide on how to find the start block number.
1. Add your deployed Greeter public smart contract address to the `address` property.
2. Add your start block number of the deployed contract in the `startBlock` property.
```yaml subgraph.yaml theme={null}
dataSources:
- kind: ethereum/contract
name: Greeter
network: testnet
source:
# Step 1
address: "0xCc0d40EA9d2Dd16Ab5565ae91b121960d5e19e4e"
abi: IGreeter
# Step 2
startBlock: 1050018
```
The `eventHandlers` field specifies how each mapping connects to various event triggers. Whenever an event defined in this section is emitted from your contract, the corresponding mapping function designated as the handler will be executed.
```yaml subgraph.yaml theme={null}
eventHandlers:
- event: GreetingSet(string)
handler: handleGreetingSet
file: ./src/mappings.ts
```
#### GraphQL Schema
The GraphQL schema (`schema.graphql`) defines the structure of the data you want to index in your subgraph. You will need to specify the entity properties that you want to index. For this example, the schema defines a GraphQL entity type called "Greeting" with two entity fields: `id` and `currentGreeting`.
```graphql schema.graphql theme={null}
type Greeting @entity {
id: ID!
currentGreeting: String!
}
```
#### Event Mappings
The `mappings.ts` file maps events emitted by your smart contract into entities that can be indexed by a subgraph. It uses *AssemblyScript* to connect the events to the data schema. AssemblyScript types for entities and events can be generated in the terminal (by running the `codegen` command) and imported into the mappings file. This allows easy access to the event object's properties in the code editor.
```typescript mappings.ts theme={null}
import { GreetingSet } from "../generated/Greeter/IGreeter";
import { Greeting } from "../generated/schema";
export function handleGreetingSet(event: GreetingSet): void {
// Entities can be loaded from the store using a string ID; this ID
// needs to be unique across all entities of the same type
let entity = Greeting.load(event.transaction.hash.toHexString());
// Entities only exist after they have been saved to the store;
// `null` checks allow to create entities on demand
if (!entity) {
entity = new Greeting(event.transaction.hash.toHex());
}
// Entity fields can be set based on event parameters
entity.currentGreeting = event.params._greeting;
// Entities can be written to the store with `.save()`
entity.save();
}
```
#### Graph Node Configuration
To connect a local graph node to a remote network, such as testnet, mainnet, or previewnet, use a [docker-compose](https://github.com/graphprotocol/graph-node/tree/master/docker#docker-compose) setup. The API endpoint that connects the graph node to the network is specified within the `environment` object of the `docker-compose.yaml` file [here](https://github.com/hashgraph/hedera-subgraph-example/blob/main/graph-node/docker-compose.yaml). Add the API endpoint URL in the `ethereum` field in the `environment` object. For this tutorial, we will use the [Hashio Testnet ](https://www.hashgraph.com/hashio/)instance of the Hedera JSON-RPC relay, but *any* [JSON-RPC provider](/evm/development/json-rpc) supported by the community can be used.
This is what the `ethereum` field should look like after you enter your API endpoint URL:
```yaml theme={null}
ethereum: 'testnet:https://testnet.hashio.io/api
```
**\*Note:** For more info on how to set up an indexer, check out\* T\_he Graph\_ [*docs*](https://thegraph.com/docs/en/) *and the* [*official graph-node GitHub repository*](https://github.com/graphprotocol/graph-node)*. For a full subgraph project example, check out* [*this*](https://github.com/hashgraph/hedera-json-rpc-relay/tree/main/tools/subgraph-example) \*repo.\*
***
## Deploy Subgraph
In this step, you will create the subgraph and deploy it to your local graph node. If everything runs without errors, your terminal should resemble the console check at the end of each subsection.
#### 1. Start Graph Node
To start your local graph node, have the Docker engine running before executing the below command in your project directory:
```bash theme={null}
npm run graph-node
```
The first time you run the command:
```bash theme={null}
Creating graph-node_postgres_1 ... done
Creating graph-node_ipfs_1 ... done
Creating graph-node_graph-node_1 ... done
```
What your console will return if you run the command more than once:
```bash theme={null}
[+] Running 3/0
⠿ Container graph-node-postgres-1 Running 0.0s
⠿ Container graph-node-ipfs-1 Running 0.0s
⠿ Container graph-node-graph-node-1 Running 0.0s
```
#### 2. Generate Types
In the same directory, run the following command to generate AssemblyScript types for entities and events:
```bash theme={null}
graph codegen
```
```
Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4
✔ Apply migrations
✔ Load subgraph from subgraph.yaml
Load contract ABI from abis/IGreeter.json
✔ Load contract ABIs
Generate types for contract ABI: IGreeter (abis/IGreeter.json)
Write types to generated/Greeter/IGreeter.ts
✔ Generate types for contract ABIs
✔ Generate types for data source templates
✔ Load data source template ABIs
✔ Generate types for data source template ABIs
✔ Load GraphQL schema from schema.graphql
Write types to generated/schema.ts
✔ Generate types for GraphQL schema
Types generated successfully
```
You should have a new folder named `generated` in your project directory. This is what your updated subgraph project structure should look like:
```
subgraph-name
└───abis
│ | IGreeter.json
└───config
│ └──testnet.json
└───generated
│ └───Greeter
│ │ IGreeter.ts
│ │ schema.ts
└───graph-node
│ └──docker-compose.yaml
└───src
│ │ mappings.ts
│ package-lock.json
│ package.json
│ README.md
│ schema.graphql
│ subgraph.yaml
```
#### 3. Create and Deploy
To create and deploy your subgraph to your local graph node, run:
```bash theme={null}
// create the subgraph
npm run create-local
```
```bash theme={null}
> hedera-subgraph-repo-example@1.0.0 create-local
> graph create --node http://localhost:8020/ Greeter
Created subgraph: Greeter
```
```bash theme={null}
// deploy the subgraph
npm run deploy-local
```
When you run the `deploy-local` command, your console will prompt you to provide a `Version Label`. Enter any version number you'd like. This is just a way to keep track of different versions of your subgraph. For instance, if you started with version v0.0.1 today, but then made some changes and wanted to deploy an upgraded version, you bump up the version number to v0.0.2.
*For example: ✔ Version Label (e.g. v0.0.1) · v0.0.1*
```bash theme={null}
> hedera-subgraph-repo-example@1.0.0 deploy-local
> graph deploy --node http://localhost:8020/ --ipfs http://localhost:5001 Greeter
✔ Version Label (e.g. v0.0.1) · v0.0.1
✔ Apply migrations
✔ Load subgraph from subgraph.yaml
Compile data source: Greeter => build/Greeter/Greeter.wasm
✔ Compile subgraph
Copy schema file build/schema.graphql
Write subgraph file build/Greeter/abis/IGreeter.json
Write subgraph manifest build/subgraph.yaml
✔ Write compiled subgraph to build/
Add file to IPFS build/schema.graphql
.. QmVtZMzbjU6QHEFfrCJ5NhbP5vUNrukaussxXZ4Esf3qCm
Add file to IPFS build/Greeter/abis/IGreeter.json
.. QmZQbrdhaR2p2EZR6raiLbpgX5hjKW4S5cDgy1VvHKmjtH
Add file to IPFS build/Greeter/Greeter.wasm
.. QmYz3qFZ4KHiHXhgbTKFxbCNvE9Serhq8yvGuJPK12K5qf
✔ Upload subgraph to IPFS
Build completed: QmbGuuuqtEEqFxjdwSdhiKKpb4GCzqbh3oASAnVVEXRoVW
Deployed to http://localhost:8000/subgraphs/name/Greeter/graphql
Subgraph endpoints:
Queries (HTTP): http://localhost:8000/subgraphs/name/Greeter
```
After the subgraph is successfully deployed, open the [GraphQL playground](http://localhost:8000/subgraphs/name/Greeter/graphql?query=%0A++++%23%0A++++%23+Welcome+to+The+GraphiQL%0A++++%23%0A++++%23+GraphiQL+is+an+in-browser+tool+for+writing%2C+validating%2C+and%0A++++%23+testing+GraphQL+queries.%0A++++%23%0A++++%23+Type+queries+into+this+side+of+the+screen%2C+and+you+will+see+intelligent%0A++++%23+typeaheads+aware+of+the+current+GraphQL+type+schema+and+live+syntax+and%0A++++%23+validation+errors+highlighted+within+the+text.%0A++++%23%0A++++%23+GraphQL+queries+typically+start+with+a+%22%7B%22+character.+Lines+that+start%0A++++%23+with+a+%23+are+ignored.%0A++++%23%0A++++%23+An+example+GraphQL+query+might+look+like%3A%0A++++%23%0A++++%23+++++%7B%0A++++%23+++++++field%28arg%3A+%22value%22%29+%7B%0A++++%23+++++++++subField%0A++++%23+++++++%7D%0A++++%23+++++%7D%0A++++%23%0A++++%23+Keyboard+shortcuts%3A%0A++++%23%0A++++%23++Prettify+Query%3A++Shift-Ctrl-P+%28or+press+the+prettify+button+above%29%0A++++%23%0A++++%23+++++Merge+Query%3A++Shift-Ctrl-M+%28or+press+the+merge+button+above%29%0A++++%23%0A++++%23+++++++Run+Query%3A++Ctrl-Enter+%28or+press+the+play+button+above%29%0A++++%23%0A++++%23+++Auto+Complete%3A++Ctrl-Space+%28or+just+start+typing%29%0A++++%23%0A++), where you can execute queries and fetch indexed data.
***
## Code Check ✅
```yaml theme={null}
specVersion: 0.0.4
description: Graph for Greeter contracts
repository: https://github.com/hashgraph/hedera-subgraph-example
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum/contract
name: Greeter
network: testnet
source:
address: "0xCc0d40EA9d2Dd16Ab5565ae91b121960d5e19e4e"
abi: IGreeter
startBlock: 1050018
mapping:
kind: ethereum/events
apiVersion: 0.0.6
language: wasm/assemblyscript
entities:
- Greeting
abis:
- name: IGreeter
file: ./abis/IGreeter.json
eventHandlers:
- event: GreetingSet(string)
handler: handleGreetingSet
file: ./src/mappings.ts
```
**docker-compose.yaml**
```yaml theme={null}
version: "3"
services:
graph-node:
image: graphprotocol/graph-node:v0.27.0
ports:
- "8000:8000"
- "8001:8001"
- "8020:8020"
- "8030:8030"
- "8040:8040"
depends_on:
- ipfs
- postgres
extra_hosts:
- host.docker.internal:host-gateway
environment:
postgres_host: postgres
postgres_user: "graph-node"
postgres_pass: "let-me-in"
postgres_db: "graph-node"
ipfs: "ipfs:5001"
ethereum:
GRAPH_LOG: info
GRAPH_ETHEREUM_GENESIS_BLOCK_NUMBER: 1
ipfs:
image: ipfs/go-ipfs:v0.10.0
ports:
- "5001:5001"
volumes:
- ./data/ipfs:/data/ipfs
postgres:
image: postgres
ports:
- "5432:5432"
command: ["postgres", "-cshared_preload_libraries=pg_stat_statements"]
environment:
POSTGRES_USER: "graph-node"
POSTGRES_PASSWORD: "let-me-in"
POSTGRES_DB: "graph-node"
PGDATA: "/data/postgres"
volumes:
- ./data/postgres:/var/lib/postgresql/data
```
#### *Congratulations! You've successfully deployed a subgraph to your local graph node!*;
Once the node finishes indexing, you can access the GraphQL API at: [http://localhost:8000/subgraphs/name/Greeter](http://localhost:8000/subgraphs/name/Greeter)
Follow the steps below to execute the query and fetch the indexed data from the subgraph's entities:
1. Enter the following GraphQL query into the left column of the playground *(see Step 1 in the screenshot below)*:
```graphql theme={null}
{
greetings {
id
currentGreeting
}
}
```
2. Execute the query by clicking on the play button at the top of the playground *(see Step 2 in the screenshot below).*
3. The query returns the indexed data from the subgraph's entities on the right column of the playground *(see Step 3 in the screenshot below)*:
```graphql theme={null}
{
"data": {
"greetings": [
{
"id": "0xe30c4a439ffbcf4a7e9f3083ec07cc056f456770d080f2f08cc546a399d71516",
"currentGreeting": "initial_msg"
}
]
}
}
```
#### **Congratulations! 🎉 You have successfully learned how to deploy a Subgraph using The Graph Protocol and JSON-RPC. Feel free to reach out on** [**Discord**](https://hedera.com/discord) **if you have any questions!**
***
## Additional Resources
**➡** [**Project Repository**](https://github.com/hashgraph/hedera-subgraph-example)
**➡** [**Subgraph Example**](https://github.com/hashgraph/hedera-json-rpc-relay/tree/main/tools/subgraph-example)
[GitHub](https://github.com/theekrystallee) |
[Hashnode](https://hashnode.com/@theekrystallee)
[GitHub](https://github.com/SimiHunjan) |
[LinkedIn](https://www.linkedin.com/in/shunjan/)
[GitHub](https://github.com/georgi-l95) |
[LinkedIn](https://www.linkedin.com/in/georgi-dimitorv-lazarov/)
# Deploy Smart Contracts on Hedera Using Truffle
Source: https://docs.hedera.com/evm/tools/other/truffle
The [Hedera JSON RPC Relay](/evm/development/json-rpc) enables developers to use their favorite EVM-compatible tools such as Truffle, Hardhat, Web3JS, EthersJS, to deploy and interact with smart contracts on the Hedera network. As highlighted in a [previous article](https://hedera.com/blog/anything-you-can-do-you-can-do-on-hedera-introducing-the-json-rpc-relay), the relay provides applications and tools seamless access to Hedera while masking implementation complexities and preventing reductions in performance, security, and scalability.
This tutorial shows you how to deploy smart contracts on Hedera using Truffle and the [JSON RPC Relay](/evm/development/json-rpc) with the following steps:
1. Create an account that has ECDSA keys using the Javascript SDK
2. Compile a contract using Truffle
3. Deploy the smart contract to Hedera network through the JSON RPC Relay
You can find more examples using Truffle, Web3JS, and Hardhat in [this GitHub repository](https://github.com/hashgraph/hedera-json-rpc-relay/tree/main/tools).
***
## Prerequisites
* Get a[ ](https://portal.hedera.com/register)[Hedera testnet account](https://portal.hedera.com/register)
* This[ Codesandbox](https://codesandbox.io/s/hedera-example-json-rpc-truffle-q6kibt?file=/create-account.js) is already setup for you to try this example
* Fork the sandbox
* Remember to provide your testnet account credentials for the ***operator*** in the ***.env*** file
* Open a new terminal and run:
* ***npm install -g truffle*** (this installation may take a few minutes)
* ***node create-account.js***
* Get[ ](https://github.com/ed-marquez/hedera-example-staking)[the example code from GitHub](https://github.com/ed-marquez/hedera-example-json-rpc-truffle)
***
## Table of Contents
1. [Create an ECDSA Account](#create-an-account-that-has-ecdsa-keys)
2. [Compile Smart Contract](#compile-a-smart-contract-using-truffle)
3. [Deploy Smart Contract](#deploy-the-smart-contract-to-hedera-using-truffle)
4. [Additional Resources](#additional-resources)
***
## Create an Account that Has ECDSA Keys
Hedera supports two popular types of signature algorithms, ED25519 and ECDSA. Both are used in many blockchain platforms, including Bitcoin and Ethereum. **Currently, the JSON RPC Relay only supports Hedera accounts with an alias set (i.e. public address) based on its ECDSA public key.** To deploy a smart contract using Truffle, we first have to create a new account that meets these criteria. The ***main()*** function in ***create-account.js*** helps us do just that.
In case you’re interested in more details about auto account creation and alias, check out the [documentation](/native/accounts/create#create-an-account-via-an-account-alias#create-an-account-via-an-account-alias) and [HIP-32](https://hips.hedera.com/hip/hip-32).
```javascript theme={null}
async function main() {
// Generate ECDSA key pair
console.log("- Generating a new key pair... \n");
const newPrivateKey = PrivateKey.generateECDSA();
const newPublicKey = newPrivateKey.publicKey;
const newAliasAccountId = newPublicKey.toAccountId(0, 0);
console.log(`- New account alias: ${newAliasAccountId} \n`);
console.log(`- New private key (Hedera): ${newPrivateKey} \n`);
console.log(`- New public key (Hedera): ${newPublicKey} \n`);
console.log(
`- New private key (RAW EVM): 0x${newPrivateKey.toStringRaw()} \n`
);
console.log(`- New public key (RAW): 0x${newPublicKey.toStringRaw()} \n`);
console.log(
`- New public key (EVM): 0x${newPublicKey.toEthereumAddress()} \n\n`
);
// Transfer HBAR to newAliasAccountId to auto-create the new account
// Get account information from a transaction record query
const [txReceipt, txRecQuery] = await autoCreateAccountFcn(
operatorId,
newAliasAccountId,
100
);
console.log(`- HBAR Transfer to new account: ${txReceipt.status} \n\n`);
console.log(`- Parent transaction ID: ${txRecQuery.transactionId} \n`);
console.log(
`- Child transaction ID: ${txRecQuery.children[0].transactionId.toString()} \n`
);
console.log(
`- New account ID (from RECORD query): ${txRecQuery.children[0].receipt.accountId.toString()} \n`
);
// Get account information from a mirror node query
const mirrorQueryResult = await mirrorQueryFcn(newPublicKey);
console.log(
`- New account ID (from MIRROR query): ${mirrorQueryResult.data?.accounts[0].account} \n`
);
}
```
Executing this code generates a new ECDSA key pair, displays the information about the keys in Hedera and EVM formats, and transfers HBAR to the account alias (***newAliasAccountId***) to auto-create a Hedera account that meets the criteria mentioned before. Information about the new account is obtained in two ways, a [transaction record query](/native/transactions/record) and a [mirror node query](https://hedera.com/blog/how-to-look-up-transaction-history-on-hedera-using-mirror-nodes-back-to-the-basics).
***Console Output:***
**IMPORTANT NOTE**: Private keys for Testnet are displayed here for educational purposes only. Never share your private key(s) with others, as that may result in lost funds, or loss of control over your account.
The next step is to deploy a smart contract using Truffle and the newly created Hedera account. Copy the value from “***New private key (RAW EVM)***” in the console output and paste it into the ***ETH\_PRIVATE\_KEY*** variable in the ***.env*** file (if you cloned the repository, you may need to rename the file from ***.env\_sample*** to ***.env***).
#### Helper Functions
The functions ***autoCreateAccountFcn()*** and ***mirrorQueryFcn()*** perform the auto account creation and mirror query, respectively.
```javascript theme={null}
async function autoCreateAccountFcn(
senderAccountId,
receiverAccountId,
hbarAmount
) {
//Transfer hbar to the account alias to auto-create account
const transferToAliasTx = new TransferTransaction()
.addHbarTransfer(senderAccountId, new Hbar(-hbarAmount))
.addHbarTransfer(receiverAccountId, new Hbar(hbarAmount))
.freezeWith(client);
const transferToAliasSign = await transferToAliasTx.sign(operatorKey);
const transferToAliasSubmit = await transferToAliasSign.execute(client);
const transferToAliasRx = await transferToAliasSubmit.getReceipt(client);
// Get a transaction record and query the record to get information about the account creation
const transferToAliasRec = await transferToAliasSubmit.getRecord(client);
const txRecordQuery = await new TransactionRecordQuery()
.setTransactionId(transferToAliasRec.transactionId)
.setIncludeChildren(true)
.execute(client);
return [transferToAliasRx, txRecordQuery];
}
```
```javascript theme={null}
async function mirrorQueryFcn(publicKey) {
// Query a mirror node for information about the account creation
await delay(10000); // Wait for 10 seconds before querying account id
const mirrorNodeUrl = "https://testnet.mirrornode.hedera.com/api/v1/";
const mQuery = await axios.get(
mirrorNodeUrl + "accounts?account.publickey=" + publicKey.toStringRaw()
);
return mQuery;
}
```
***
## Compile a Smart Contract Using Truffle
Now it’s time to compile ***SimpleStorage***, which is a basic smart contract that allows anyone to set and get data.
```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
contract SimpleStorage {
uint256 data;
function getData() external view returns (uint256) {
return data;
}
function setData(uint256 _data) external {
data = _data;
}
}
```
Use the following command to perform the compilation:
```javascript theme={null}
truffle compile
```
***Console Output:***
***
## Deploy the Smart Contract to Hedera Using Truffle
Finally, deploy the contract on Hedera through the JSON RPC Relay. Be sure to configure the following parameters in your ***.env*** file to be able to deploy to the Hedera testnet with Truffle.
```javascript theme={null}
NETWORK_ID = 296
JSON_RPC_RELAY_URL = https://testnet.hashio.io/api
ETH_PRIVATE_KEY = 0x7a9e... [Run create-account.js and paste value of “New private key (RAW EVM)”]
```
This example uses the [Hashio instance of the JSON RPC Relay](https://www.hashgraph.com/hashio/), hosted by [Hashgraph](https://www.hashgraph.com/). URLs are also available for the Hedera Mainnet and Previewnet.
Deploy the contract with the following command:
```javascript theme={null}
truffle migrate
```
***Console Output ✅***
You can obtain more information about the newly deployed contract using the [mirror node REST API](/reference/rest-api). Additional context for that API is provided in [this blog post](https://hedera.com/blog/how-to-look-up-transaction-history-on-hedera-using-mirror-nodes-back-to-the-basics). Based on the console output of the example above, here are two mirror node queries that provide more information about the new contract and account based on their respective Solidity addresses:
[https://testnet.mirrornode.hedera.com/api/v1/contracts/0x0000000000000000000000000000000002Da4d4b](https://testnet.mirrornode.hedera.com/api/v1/contracts/0x0000000000000000000000000000000002Da4d4b)
[https://testnet.mirrornode.hedera.com/api/v1/accounts/0x0000000000000000000000000000000002dA4D4a](https://testnet.mirrornode.hedera.com/api/v1/accounts/0x0000000000000000000000000000000002dA4D4a)
Now you know how to deploy smart contracts on Hedera using Truffle and the JSON RPC Relay. The first part of this example used the Hedera [JavaScript SDK](/native/fundamentals#hedera-services-code-sdks). However, you can try this with the other officially supported SDKs for Java and Go.
***
## Additional Resources
**➡** [**Project Repository**](https://github.com/ed-marquez/hedera-example-json-rpc-truffle)
**➡** [**CodeSandbox**](https://codesandbox.io/s/hedera-example-json-rpc-truffle-q6kibt?file=/create-account.js)
**➡** [**Truffle Documentation**](https://trufflesuite.com/docs/)
**➡ Feel free to reach out in** [**Discord**](https://hedera.com/discord)
[GitHub](https://github.com/ed-marquez) |
[LinkedIn](https://www.linkedin.com/in/ed-marquez/)
[GitHub](https://github.com/theekrystallee) |
[X](https://X.com/theekrystallee) |
[Hashnode](https://hashnode.com/@theekrystallee)
# Remix IDE
Source: https://docs.hedera.com/evm/tools/remix
Write, compile, and deploy Solidity contracts to Hedera testnet from your browser.
Remix is an open-source Solidity IDE that runs in the browser. It compiles, debugs, and deploys without anything installed locally. Because Hedera is EVM-compatible, the same Remix workflow you'd use against Ethereum works against Hedera once MetaMask is pointed at the JSON-RPC relay.
The rest of this page walks through that workflow end-to-end.
## Prerequisites
Network `Hedera Testnet`, RPC `https://testnet.hashio.io/api`, Chain ID `296`.
A small amount of testnet HBAR to pay for deployment.
## Step 1: Open Remix
Go to [remix.ethereum.org](https://remix.ethereum.org). Accept the default workspace if it asks. The left sidebar gives you a File Explorer, the Solidity Compiler, and the Deploy & Run Transactions panel.
## Step 2: Write a contract
In File Explorer, create `contracts/HelloHedera.sol` and paste:
```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
contract HelloHedera {
string public message = "Hello, Hedera!";
event MessageUpdated(address indexed by, string newMessage);
function updateMessage(string memory newMessage) public {
message = newMessage;
emit MessageUpdated(msg.sender, newMessage);
}
}
```
## Step 3: Compile
1. Open the **Solidity Compiler** tab.
2. Set the compiler version to `0.8.22` (or anything that satisfies the `pragma`).
3. Click **Compile HelloHedera.sol**. Green checkmark means success.
Turn on **Auto compile** in the compiler settings so Remix recompiles every save. Saves a click on every edit.
## Step 4: Connect MetaMask
1. Switch to the **Deploy & Run Transactions** tab.
2. In the **Environment** dropdown, pick **Injected Provider - MetaMask**.
3. MetaMask asks to connect. Approve.
4. The **Network** field should read `Custom (296) network`. That's Hedera testnet.
If it shows a different network, switch MetaMask to **Hedera Testnet** from its own network dropdown.
## Step 5: Deploy
1. Confirm the **CONTRACT** dropdown shows `HelloHedera`.
2. Click the orange **Deploy** button.
3. MetaMask prompts to confirm the transaction. Review the HBAR gas fee and click **Confirm**.
4. The contract appears under **Deployed Contracts** at the bottom of the panel after a few seconds.
## Step 6: Interact with the contract
Expand the deployed contract under **Deployed Contracts**. You get a blue read-only **message** button that returns the current value at no gas cost, and an orange **updateMessage** field that writes a new value (this one costs gas).
Click **message** to see the initial value. Type something into **updateMessage** and call it. Confirm the MetaMask popup. Click **message** again to see the new value.
## Step 7: View on HashScan
Copy the contract address from the **Deployed Contracts** panel and open:
```text theme={null}
https://hashscan.io/testnet/contract/
```
HashScan shows the deploy transaction, the bytecode, and any subsequent calls. If you want the source code readable on HashScan, [verify the contract](/evm/development/verifying) with the same Solidity file you used in Remix.
## When to reach for Remix
| Use case | Tool |
| ---------------------------------------- | -------------------------------------------------------------------------- |
| Quick prototyping, single contract | Remix |
| Reproducible deploys, tests, scripts | [Hardhat](/evm/tools/hardhat/index) or [Foundry](/evm/tools/foundry/index) |
| Guided UI for ERC-20 / ERC-721 templates | [Contract Builder](/evm/tools/contract-builder) |
| Cross-chain libraries (LayerZero, CCIP) | Hardhat. Remix's import resolution gets shaky |
Remix is the right tool when you're learning, poking at one contract, or doing throwaway experiments. Once you want version control, a test suite, or repeatable CI deploys, move to Hardhat or Foundry.
## See also
Browser-based deploys without writing any Solidity yourself.
Move your Remix contract into a Hardhat project for tests and CI.
# How to Mint & Burn an ERC-721 Token using Foundry(Part 1)
Source: https://docs.hedera.com/evm/tutorials/advanced/erc721-foundry/part1-mint-burn
In this tutorial, you’ll deploy, mint, and burn ERC‑721 tokens (NFTs) using Foundry and OpenZeppelin on the Hedera Testnet. You’ll set up a Foundry project, write an ERC‑721 contract, deploy it via a Foundry script, mint an NFT to your account, add burn functionality, and burn an NFT.
We’ll connect to Hedera via the JSON‑RPC relay (Hashio) and use Foundry tools:
* `forge`: build and deploy through scripts
* `cast`: quick RPC interactions
You can take a look at the **complete code** in the [**Hedera-Code-Snippets
repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/foundry-erc721-mint-burn).
***
## Prerequisites
* Foundry installed (forge, cast, anvil, chisel):
* `curl -L https://foundry.paradigm.xyz | bash`
* `foundryup`
* ECDSA account and 0x‑prefixed private key for Hedera Testnet (create/fund via the [Hedera Portal](https://portal.hedera.com/))
* Basic Solidity / CLI familiarity
***
## Table of Contents
1. [Step 1: Project Setup](#step-1%3A-project-setup)
2. [Step 2: Creating the ERC-721 Contract](#step-2%3A-creating-the-erc-721-contract)
3. [Step 3: Deploy your ERC-721 Smart Contract](#step-3%3A-deploy-your-erc-721-smart-contract)
4. [Step 4: Minting an NFT](#step-4%3A-minting-an-nft)
5. [Step 5: Adding the Burn Functionality](#step-5%3A-adding-the-burn-functionality)
6. [Step 6: Burning an NFT](#step-6%3A-burning-an-nft)
7. [Step 7: Run tests(Optional)](#step-7%3A-run-tests-optional)
8. [Interacting with the Contract using "cast"](#interacting-with-the-contract-using-cast-optional-advanced)
***
## Step 1: Project Setup
#### **Initialize Project**
Set up your project by initializing the hardhat project:
```bash theme={null}
forge init foundry-erc-721-mint-burn
cd foundry-erc-721-mint-burn
```
This creates a new directory with a standard Foundry project structure, including `src`, `test`, and `script` folders.
#### Install Dependencies
Foundry uses git submodules to manage dependencies. We'll install the OpenZeppelin Contracts library, which provides a standard and secure implementation of the ERC20 token.
```bash theme={null}
forge install OpenZeppelin/openzeppelin-contracts
```
This command will download the contracts and add them to your `lib` folder.
**Create `.env` File**
Create an `.env` for your RPC URL and private key.
```bash theme={null}
touch .env
```
Put the following into your environment file.
```bash .env theme={null}
HEDERA_RPC_URL=https://testnet.hashio.io/api
HEDERA_PRIVATE_KEY=0x-your-private-key
```
Now, let's also load these to the terminal:
```bash theme={null}
source .env
```
Replace the `0x-your-private-key` environment variable with the **HEX Encoded
Private Key** for your **ECDSA** **account** from the [Hedera
Portal](https://portal.hedera.com/)
***Please note**:* *that Hashio is intended for development and testing
purposes only. For production use cases, it's recommended to use
commercial-grade JSON-RPC Relay or host your own instance of the* [*Hiero
JSON-RPC Relay*](https://github.com/hiero-ledger/hiero-json-rpc-relay)*.*
#### Configure Foundry
Update your `foundry.toml` file in the root directory of your project. Open it and add profiles for the Hedera Testnet RPC endpoint.
```toml foundry.toml theme={null}
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
remappings = [
"@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/",
"forge-std/=lib/forge-std/src/"
]
# Add this section for Hedera testnet
[rpc_endpoints]
testnet = "${HEDERA_RPC_URL}"
```
Note the values in `remappings` field. We need this to import prefix to a filesystem path so both Foundry(forge) and our editor can resolve short, package-like imports instead of long relative paths.
We will be removing the default contracts that comes with foundry default project:
```bash theme={null}
rm -rf script/* src/* test/*
```
***
## Step 2: Creating the ERC-721 Contract
Create a new Solidity file (`MyToken.sol`) in our `contracts` directory:
```solidity src/MyToken.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC721, Ownable {
uint256 private _nextTokenId;
constructor(address initialOwner)
ERC721("MyToken", "MTK")
Ownable(initialOwner)
{}
function safeMint(address to) public onlyOwner returns (uint256) {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
return tokenId;
}
}
```
This contract was created using the [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/#erc721) and OpenZeppelin's ERC-721 standard implementation with an ownership model. The ERC-721 token's name has been set to "MyToken." The contract implements the `safeMint` function, which accepts the address of the owner of the new token and uses auto-increment IDs, starting from 0.
Let's compile this contract by running:
```bash theme={null}
forge build
```
This command will generate the smart contract artifacts, including the [ABI](/evm/development/compiling). We are now ready to deploy the smart contract.
***
## Step 3: Deploy Your ERC-721 Smart Contract
Create a deployment script (`DeployMyToken.s.sol`) in `script` directory:
```typescript script/DeployMyToken.s.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {Script, console} from "forge-std/Script.sol";
import {MyToken} from "../src/MyToken.sol";
contract MyTokenScript is Script {
function run() external returns (address) {
// Load the private key from the .env file
uint256 deployerPrivateKey = vm.envUint("HEDERA_PRIVATE_KEY");
// Start broadcasting transactions with the loaded private key
vm.startBroadcast(deployerPrivateKey);
// Get the deployer's address to use as the initial owner
address deployerAddress = vm.addr(deployerPrivateKey);
// Deploy the contract
MyToken myToken = new MyToken(deployerAddress);
// Stop broadcasting
vm.stopBroadcast();
console.log("MyToken deployed to:", address(myToken));
return address(myToken);
}
}
```
In this script, we first retrieve your account (the deployer) that's on our `.env` file. This account will own the deployed smart contract. Next, we use this account to deploy the contract by calling `MyToken.deploy(deployerAddress)`. This passes your account address as the initial owner and signer of the deployment transaction.
Deploy your contract by executing the script:
```bash theme={null}
forge script script/DeployMyToken.s.sol --rpc-url testnet --broadcast
```
After a few moments, you will see the address of your newly deployed contract:
```
Compiler run successful!
Script ran successfully.
== Return ==
0: address 0x1112a82254f48e0daEEE3fFD009B4E44a66A7f77
== Logs ==
MyToken deployed to: 0x1112a82254f48e0daEEE3fFD009B4E44a66A7f77
## Setting up 1 EVM.
==========================
Chain 296
Estimated gas price: 740.000000001 gwei
Estimated total gas used for script: 2524191
Estimated amount required: 1.867901340002524191 ETH
==========================
##### 296
✅ [Success] Hash: 0x0679fa510bda823be55600902aaef81b92814c010cee9af818b4c5712e625de2
Contract Address: 0x4397fa3bD44bb9b2986C9463d794bDD73763A3dE
Block: 25122524
Paid: 0.70677355 ETH (2019353 gas * 350 gwei)
✅ Sequence #1 on 296 | Total Paid: 0.70677355 ETH (2019353 gas * avg 350 gwei)
==========================
ONCHAIN EXECUTION COMPLETE & SUCCESSFUL.
```
Note that Foundry hardcodes “ETH” in its summary. However, even if it says
`ETH`, because we're connected to Hedera, the currency used is `HBAR`.
Next, set up variables for your contract address and public address to make the next commands easier to read. Please export these variables in your shell.
```bash highlight={2} theme={null}
# Replace with the contract address from the previous step
export CONTRACT_ADDRESS=
# Derive your public address from the private key
export MY_ADDRESS=$(cast wallet address $HEDERA_PRIVATE_KEY)
```
Let's also verify our contract because it is so easy to do so and it is good practice. Verification submits the source code to [Sourcify](https://sourcify.dev), which natively supports Hedera Testnet (chain ID `296`); the verified status will then appear on [HashScan](https://hashscan.io/).
```bash theme={null}
forge verify-contract $CONTRACT_ADDRESS src/MyToken.sol:MyToken \
--chain-id 296 \
--verifier sourcify \
--verifier-url "https://sourcify.dev/server" \
--constructor-args $(cast abi-encode "constructor(address)" $MY_ADDRESS)
```
***
## Step 4: Minting an NFT
We will now create a new file `MintMyToken.s.sol` script in our `script` directory to mint an NFT. Don't forget to replace the `` with the address you've just copied.
```typescript script/MintMyToken.s.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {Script, console} from "forge-std/Script.sol";
import {MyToken} from "../src/MyToken.sol";
contract MintMyTokenScript is Script {
function run() external {
// Load the private key from the .env file
uint256 deployerPrivateKey = vm.envUint("HEDERA_PRIVATE_KEY");
address contractAddr = ; // Replace with your deployed contract address
address recipient = vm.addr(deployerPrivateKey);
vm.startBroadcast(deployerPrivateKey);
MyToken token = MyToken(contractAddr);
uint256 beforeBal = token.balanceOf(recipient);
uint256 tokenId = token.safeMint(recipient);
uint256 afterBal = token.balanceOf(recipient);
vm.stopBroadcast();
console.log("Minted tokenId:", tokenId);
console.log("Recipient:", recipient);
console.log("Balance before:", beforeBal);
console.log("Balance after:", afterBal);
}
}
```
The code mints a new NFT to your account ( `deployer.address` ). Then we verify the balance to see if we own an ERC-721 token of type `MyToken`.
Mint an NFT:
```bash theme={null}
forge script script/MintMyToken.s.sol --rpc-url testnet --broadcast
```
Expected output:
```json theme={null}
Script ran successfully.
== Logs ==
Minted tokenId: 0
Recipient: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Balance before: 0
Balance after: 1
```
***
## Step 5: Adding the Burn Functionality
Update your contract to add NFT burning capability by importing the burnable extension and adding it to the interfaces list for your contract:
```solidity theme={null}
// [...]
import {ERC721Burnable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
contract MyToken is ERC721, ERC721Burnable, Ownable {
// [...]
```
Redeploy:
```bash theme={null}
forge script script/DeployMyToken.s.sol --rpc-url testnet --broadcast
```
Reverify:
```bash theme={null}
export CONTRACT_ADDRESS=
forge verify-contract $CONTRACT_ADDRESS src/MyToken.sol:MyToken \
--chain-id 296 \
--verifier sourcify \
--verifier-url "https://sourcify.dev/server" \
--constructor-args $(cast abi-encode "constructor(address)" $MY_ADDRESS)
```
Copy the new smart contract address and replace the address in the `script/MintMyToken.s.sol` script with your new address. Let's mint a new NFT for the redeployed contract:
```bash theme={null}
forge script script/MintMyToken.s.sol --rpc-url testnet --broadcast
```
***
## Step 6: Burning an NFT
Create a burn script (`BurnMyToken.s.sol` ) in your `script` directory. Don't forget to replace the `` with your own contract address and `` with the token Id you want to burn(eg. 0).
```typescript script/BurnMyToken.s.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {Script, console} from "forge-std/Script.sol";
import {MyToken} from "../src/MyToken.sol";
contract BurnMyTokenScript is Script {
function run() external {
// Load the private key from the .env file
uint256 deployerPrivateKey = vm.envUint("HEDERA_PRIVATE_KEY");
address contractAddr = 0x4397fa3bD44bb9b2986C9463d794bDD73763A3dE; // Replace with your deployed contract address
uint256 tokenId = ; // Replace with the tokenId you want to burn
address recipient = vm.addr(deployerPrivateKey);
vm.startBroadcast(deployerPrivateKey);
MyToken token = MyToken(contractAddr);
uint256 beforeBal = token.balanceOf(recipient);
token.burn(tokenId);
uint256 afterBal = token.balanceOf(recipient);
vm.stopBroadcast();
console.log("Burned tokenId:", tokenId);
console.log("Recipient:", recipient);
console.log("Balance before:", beforeBal);
console.log("Balance after:", afterBal);
}
}
```
The script will burn the ERC-721 token with the ID set to 1, which is the ERC-721 token you've just minted. To be sure the token has been deleted, let's print the balance for our account to the terminal. The balance should show a balance of `0`.
Burn the NFT:
```bash theme={null}
forge script script/BurnMyToken.s.sol --rpc-url testnet --broadcast
```
Expected output:
```json theme={null}
Script ran successfully.
== Logs ==
Burned tokenId: 0
Recipient: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Balance before: 1
Balance after: 0
```
**Congratulations! 🎉 You have successfully learned how to deploy an ERC-721 smart contract using Foundry and OpenZeppelin. Feel free to reach out in** [**Discord**](https://hedera.com/discord)**!**
## Step 7: Run tests(Optional)
You can find both types of tests in the [**Hedera-Code-Snippets repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/foundry-erc-721-mint-burn). You will find the following files:
* `test/MyToken.t.sol`
Copy this file and then run the tests:
```bash theme={null}
forge test
```
To deep dive into how to write these tests from scratch, go to the section under "[How to Write Tests in Solidity (Part 2)"](/evm/tutorials/advanced/erc721-foundry/part2-testing).
## Interacting with the Contract using "cast"(Optional - Advanced)
Apart from interacting with the contract using dedicated scripts like `MintMyToken.s.sol` or `BurnMyToken.s.sol`, we can also use `cast`, Foundry's command-line tool for doing the exact same thing by making RPC calls. We are going to deploy the contract using cast in this section but you should be able to able to perform any other operation such as mint or burn using `cast` as well(even though it might be a little bit more complicated to do so).
To use `cast` and other command-line tools, you need to load the variables from your `.env` file into your current terminal session.
### **Environment Setup**
Run the following command to load the `HEDERA_PRIVATE_KEY` and `HEDERA_RPC_URL` into your shell. In addition, we will derive our address and save it to `MY_ADDRESS`.
```bash theme={null}
source .env
# Derive your EVM address (the deployer/owner for MyToken)
export MY_ADDRESS=$(cast wallet address "$HEDERA_PRIVATE_KEY")
# Confirm you’re on Hedera Testnet (chain id 296)
cast chain-id --rpc-url "$HEDERA_RPC_URL"
```
Please make sure to have `jq` installed on your machine for the following
exercises. You can learn more about it on the [official jq
site](https://jqlang.org/download/).
### Compile and fetch creation bytecode
We’ll use forge to inspect the creation bytecode of MyToken and cast to encode constructor args.
```bash theme={null}
# Compile your project (generates artifacts)
forge build
# Get creation bytecode (0x…)
export BYTECODE=$(forge inspect src/MyToken.sol:MyToken bytecode)
# Encode constructor(address initialOwner) with your deployer address
export CTOR_ARGS=$(cast abi-encode "constructor(address)" "$MY_ADDRESS")
# Concatenate bytecode + constructor args (both hex)
export DEPLOY_DATA="0x${BYTECODE#0x}${CTOR_ARGS#0x}"
```
### Deploy the contract with cast
`cast` will submit a contract creation transaction. Then we’ll fetch the receipt and extract the deployed contract address.
```bash theme={null}
# Send the deployment tx (returns a tx hash)
export DEPLOY_TX=$(cast send --rpc-url "$HEDERA_RPC_URL" \
--private-key "$HEDERA_PRIVATE_KEY" \
--json --create "$DEPLOY_DATA" | jq -r .transactionHash)
echo "Deploy tx: $DEPLOY_TX"
# Capture the contract address
export CONTRACT_ADDRESS=$(cast receipt "$DEPLOY_TX" --rpc-url "$HEDERA_RPC_URL" \
--json | jq -r .contractAddress)
echo "MyToken deployed to: $CONTRACT_ADDRESS"
```
## Further Learning & Next Steps
Want to take your local development setup even further? Here are some excellent tutorials to help you dive deeper into smart contract development on Hedera using Foundry:
1. [How to Write Tests in Solidity (Part 2)](/evm/tutorials/advanced/erc721-foundry/part2-testing)\
Learn how to start writing tests in Foundry using Solidity
2. [How to Fork the Hedera Network for Local Testing](/evm/development/forking)\
Learn how to fork hedera network(testnet/mainnet) locally so you can start testing against the forked network
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
[GitHub](https://github.com/LukeForrest-Hashgraph) |
[X](https://x.com/_LukeForrest)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
# How to Write Tests in Solidity(Part 2)
Source: https://docs.hedera.com/evm/tutorials/advanced/erc721-foundry/part2-testing
In this tutorial, you’ll learn how to write Solidity unit tests with Foundry for an ERC‑721 (NFT) contract that supports minting and burning. We’ll cover:
* Using cheatcodes like prank, startPrank/stopPrank, expectRevert, label, and fuzzing
* Testing ownership-gated minting
* Testing ERC‑721 burn behavior (owner/approved)
* Common gotchas with OpenZeppelin v5 custom errors
This guide stands on its own, but continues from [Part 1](/evm/tutorials/advanced/erc721-foundry/part1-mint-burn) where we created the ERC‑721 contract and deploy/mint/burn scripts. Here, we focus exclusively on defining and writing tests.
Note: Foundry tests run locally on an in‑memory EVM; they do not use Hedera RPC. That makes them fast and deterministic. You’ll still deploy to Hedera when you run your scripts from Part 1.
You can take a look at the **complete code** in the [**Hedera-Code-Snippets
repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/foundry-erc-721-mint-burn).
***
## Prerequisites
* ⚠️ **Complete** [**tutorial part 1**](/evm/tutorials/advanced/erc721-foundry/part1-mint-burn) **as we continue from this example.**
* Foundry installed:
* `curl -L https://foundry.paradigm.xyz | bash`
* `foundryup`
* OpenZeppelin Contracts installed for the ERC‑721 implementation:
* `forge install OpenZeppelin/openzeppelin-contracts`
* Basic familiarity with Solidity and ERC‑721
***
## Table of Contents
1. [The Contract under Test](#the-contract-under-test)
2. [Writing tests in Solidity for Foundry](#writing-tests-in-solidity-for-foundry)
3. [Understanding each test and concept](#understanding-each-test-and-concept)
4. [Running tests](#running-tests)
5. [Common gotchas](#common-gotchas)
6. [Where tests fit with Hedera](#where-tests-fit-with-hedera)
***
## The Contract under Test
We’ll test the ERC‑721 contract that supports mint and burn. If you don’t already have it, create it:
```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC721Burnable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
contract MyToken is ERC721, ERC721Burnable, Ownable {
uint256 private _nextTokenId;
constructor(address initialOwner)
ERC721("MyToken", "MTK")
Ownable(initialOwner)
{}
function safeMint(address to) public onlyOwner returns (uint256) {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
return tokenId;
}
}
```
Key points:
* Name/symbol: “MyToken” / “MTK”
* Owner‑only `safeMint`
* Auto‑increment token IDs starting at 0
* Burnable via `ERC721Burnable` (owner or approved may burn)
***
## Writing tests in Solidity for Foundry
Create a test file at `test/MyToken.t.sol` with the suite below.
```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import "forge-std/Test.sol";
import {MyToken} from "../src/MyToken.sol";
import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol";
contract MyTokenTest is Test {
MyToken internal token;
address internal owner;
address internal alice;
address internal bob;
function setUp() public {
owner = makeAddr("owner");
alice = makeAddr("alice");
bob = makeAddr("bob");
// Deploy with explicit initial owner
token = new MyToken(owner);
// For nicer traces
vm.label(address(token), "MyToken");
vm.label(owner, "Owner");
vm.label(alice, "Alice");
vm.label(bob, "Bob");
}
/* =========================
Basics
========================= */
function test_NameAndSymbol() public view {
assertEq(token.name(), "MyToken");
assertEq(token.symbol(), "MTK");
}
function test_SupportsERC721Interface() public view {
// IERC721 interfaceId = 0x80ac58cd
assertTrue(token.supportsInterface(type(IERC721).interfaceId));
}
/* =========================
Ownership
========================= */
function test_OnlyOwnerCanMint() public {
// Non-owner tries to mint → revert with OwnableUnauthorizedAccount(address)
vm.prank(alice);
vm.expectRevert(
abi.encodeWithSignature(
"OwnableUnauthorizedAccount(address)",
alice
)
);
token.safeMint(alice);
}
function test_MintByOwner_IncrementsBalanceAndReturnsTokenId() public {
// First mint should return 0
vm.prank(owner);
uint256 id0 = token.safeMint(alice);
assertEq(id0, 0);
assertEq(token.balanceOf(alice), 1);
assertEq(token.ownerOf(0), alice);
// Second mint should return 1
vm.prank(owner);
uint256 id1 = token.safeMint(alice);
assertEq(id1, 1);
assertEq(token.balanceOf(alice), 2);
assertEq(token.ownerOf(1), alice);
}
/* =========================
Burn
========================= */
function test_BurnByOwner_RemovesTokenAndDecrementsBalance() public {
// Mint tokenId 0 to Alice
vm.startPrank(owner);
uint256 id0 = token.safeMint(alice);
vm.stopPrank();
assertEq(id0, 0);
assertEq(token.balanceOf(alice), 1);
assertEq(token.ownerOf(0), alice);
// Alice (owner) burns tokenId 0
vm.prank(alice);
token.burn(0);
// After burn: token no longer exists → ownerOf(0) should revert
vm.expectRevert(
abi.encodeWithSignature("ERC721NonexistentToken(uint256)", 0)
);
token.ownerOf(0);
// Balance drops
assertEq(token.balanceOf(alice), 0);
}
function test_BurnRequiresOwnerOrApproved() public {
// Mint tokenId 0 to Alice
vm.prank(owner);
token.safeMint(alice);
// Bob (not owner/approved) tries to burn → revert with ERC721InsufficientApproval(address,uint256)
vm.prank(bob);
vm.expectRevert(
abi.encodeWithSignature(
"ERC721InsufficientApproval(address,uint256)",
bob,
0
)
);
token.burn(0);
}
function test_BurnByApprovedOperator_Succeeds() public {
// Mint tokenId 0 to Alice
vm.prank(owner);
token.safeMint(alice);
// Alice approves Bob for tokenId 0
vm.prank(alice);
token.approve(bob, 0);
// Bob can now burn tokenId 0
vm.prank(bob);
token.burn(0);
// Token gone
vm.expectRevert(
abi.encodeWithSignature("ERC721NonexistentToken(uint256)", 0)
);
token.ownerOf(0);
assertEq(token.balanceOf(alice), 0);
}
function test_BurnByOperatorApprovedForAll_Succeeds() public {
// Mint tokenId 0 to Alice
vm.prank(owner);
token.safeMint(alice);
// Approve Bob for all of Alice's tokens
vm.prank(alice);
token.setApprovalForAll(bob, true);
// Bob can burn tokenId 0
vm.prank(bob);
token.burn(0);
vm.expectRevert(
abi.encodeWithSignature("ERC721NonexistentToken(uint256)", 0)
);
token.ownerOf(0);
assertEq(token.balanceOf(alice), 0);
}
/* =========================
Fuzzing
========================= */
function testFuzz_MintToAnyNonZeroAddress(address to) public {
vm.assume(to != address(0));
vm.assume(to.code.length == 0); // ensure EOA, not a contract
vm.prank(owner);
uint256 id = token.safeMint(to);
// id should be valid (not strictly needed to check exact id; existence is enough)
assertEq(token.ownerOf(id), to);
assertEq(token.balanceOf(to), 1);
}
}
```
***
## Understanding each test and concept
Foundry test basics:
* Test files go in `test/` and compile like any Solidity.
* A function is treated as a test if its name starts with `test` (default pattern). You can also use modifiers like `view` for read-only tests.
* `setUp()` runs before every test, letting you deploy fresh state.
* Assertions come from `forge-std/Test.sol`: `assertEq`, `assertTrue`, etc.
* Cheatcodes come from the `vm` interface in `Test`: powerful helpers to manipulate EVM context.
Key cheatcodes used:
* `vm.prank(addr)`: sets `msg.sender` for the next call only.
* `vm.startPrank(addr)` / `vm.stopPrank()`: sets `msg.sender` for multiple calls.
* `vm.expectRevert(bytes)`: expects the next call to revert with a specific error.
* `vm.label(addr, "name")`: names an address for prettier traces.
* `makeAddr("salt")`: generates a deterministic address for readability.
* `vm.assume(cond)`: discard fuzz inputs that don’t satisfy a condition.
Now, what each section does:
1. Basics
* `test_NameAndSymbol`: sanity checks for token metadata.
* `test_SupportsERC721Interface`: verifies ERC‑721 interface support via `supportsInterface()`. This asserts the contract follows the standard interface.
2. Ownership
* `test_OnlyOwnerCanMint`:
* We simulate a call from `alice` using `vm.prank(alice)`.
* We assert it reverts with OpenZeppelin v5’s custom error `OwnableUnauthorizedAccount(address)`.
* The order matters: `vm.expectRevert(...)` must be set before the call that’s expected to revert.
* `test_MintByOwner_IncrementsBalanceAndReturnsTokenId`:
* Simulates minting from the `owner`.
* Asserts token IDs start at 0 and increment.
* Checks `balanceOf` and `ownerOf` correctness.
3. Burn
* `test_BurnByOwner_RemovesTokenAndDecrementsBalance`:
* Owner mints to `alice`.
* `alice` burns her token.
* After burn, `ownerOf(0)` must revert with OZ v5’s `ERC721NonexistentToken(uint256)` custom error.
* `test_BurnRequiresOwnerOrApproved`:
* A non‑owner, non‑approved account (`bob`) attempts to burn → expect `ERC721InsufficientApproval(address,uint256)`.
* `test_BurnByApprovedOperator_Succeeds` and `test_BurnByOperatorApprovedForAll_Succeeds`:
* Show both approval paths: single token approval and operator approval for all.
* After burn, token no longer exists.
4. Fuzzing
* `testFuzz_MintToAnyNonZeroAddress(address to)`:
* Foundry will try many random addresses for `to`.
* `vm.assume(to != address(0))` filters out zero address (which would revert in ERC‑721).
* We assert ownership and balance for all valid cases.
***
## Running tests
* Run all tests:
* `forge test`
* With verbose logs/traces:
* `forge test -vv` (more verbose), `-vvv`, or `-vvvv` (full traces)
* Run a single test by name:
* `forge test --mt test_BurnByOwner_RemovesTokenAndDecrementsBalance`
* Run tests from a single contract:
* `forge test --mc MyTokenTest`
Optional tooling:
* Gas report: add `gas_reports = ["MyToken", "MyTokenTest"]` to `foundry.toml`, then `forge test --gas-report`
* Coverage:
* `forge coverage` (generates LCOV; integrate with your CI or IDE)
***
## Common gotchas
* Order of `expectRevert`:
* Always call `vm.expectRevert(...)` immediately before the call you expect to revert.
* `prank` vs `startPrank`:
* `vm.prank(addr)` affects only the next call; `vm.startPrank(addr)` persists until `vm.stopPrank()`. Use the latter for multi‑call sequences (e.g., deploy → call → call).
* `view` tests:
* Mark pure/read‑only tests as `view` for clarity. It’s optional, but communicates intent.
* Deterministic addresses:
* `makeAddr("label")` is a nice pattern for self‑documenting tests.
* Fuzzing:
* Use `vm.assume` constraints to avoid invalid inputs that would cause spurious reverts (e.g., zero address).
* Keep fuzz tests independent of each other (no hidden global state).
* Test isolation:
* `setUp()` runs before every test, so each test gets a fresh deployment and state.
* Naming:
* Descriptive names like `test_BurnByApprovedOperator_Succeeds` make failures easier to diagnose.
***
## Where tests fit with Hedera
* These tests run against Foundry’s local EVM, not Hedera. Use them to validate logic quickly.
* When satisfied, use your scripts from Part 1 to deploy/mint/burn on Hedera Testnet or Localnet.
* If something fails on-chain, add more unit tests here to replicate and fix.
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
# How to Mint & Burn an ERC-721 Token Using Hardhat and Ethers (Part 1)
Source: https://docs.hedera.com/evm/tutorials/advanced/erc721-hardhat/part1-mint-burn
In this tutorial, you'll learn how to deploy, mint, and burn [ERC-721](/support/glossary#erc-721) tokens (NFTs) using Hardhat, Ethers, and OpenZeppelin contracts on the Hedera Testnet. We'll cover setting up your project, writing and deploying an ERC-721 smart contract, minting an NFT to your account, and finally, burning an NFT.
By the end, you'll have hands-on experience with essential ERC-721 operations and interacting with smart contracts on Hedera.
You can take a look at the **complete code** in the [**Hedera-Code-Snippets
repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hardhat-erc-721-mint-burn).
***
## Prerequisites
* Complete Tutorial: [Configure Hardhat with Hedera localnet/testnet](/evm/tools/hardhat)
* Basic understanding of smart contracts.
* Basic understanding of [Node.js](https://nodejs.org/en/download) and JavaScript.
* Basic understanding of [Hardhat EVM Development Tool](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3) and [Ethers](https://docs.ethers.org/v6/).
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/).
***
## Table of Contents
1. [Project Setup](#step-1%3A-project-setup)
1. [Initialize Project](#initialize-project)
2. [Configure Hardhat](#configure-hardhat)
2. [Creating the ERC-721 Contract](#step-2%3A-creating-the-erc-721-contract)
3. [Deploy Your Smart Contract](#step-3%3A-deploy-your-erc-721-smart-contract)
4. [Minting an NFT](#step-4%3A-minting-an-nft)
5. [Adding the Burn Functionality](#step-5%3A-adding-the-burn-functionality)
6. [Burning an NFT](#step-6%3A-burning-an-nft)
7. [Run tests](#step-7%3A-run-tests-optional)
***
## Video Tutorial
You can watch the video tutorial (which uses **Hardhat version 2**) or follow the step-by-step tutorial below (which uses **Hardhat version 3**).
Key differences in Hardhat 3:
* **compile → build**\
`npx hardhat compile` is now `npx hardhat build`. This is the big one. The v3 migration guide explicitly shows using the `build` task.
* **project init switch**\
v2 commonly used `npx hardhat` or `npx hardhat init` to bootstrap. In v3 it’s `npx hardhat --init`.
- **keystore helper commands are new**\
v3’s recommended flow includes a keystore plugin with commands like `npx hardhat keystore set HEDERA_RPC_URL` and `npx hardhat keystore set HEDERA_PRIVATE_KEY`. These weren’t standard in v2.
- **Foundry-compatiable Solidity tests**\
In addition to offering Javascript/Typescript integration tests, Hardhat v3 also integrates Foundry-compatible Solidity tests that allows developers to write unit tests directly in Solidity
* **Enhanced Network Management**\
v3 allows tasks to create and manage multiple network connections simultaneously which is a significant improvement over the single, fixed connection available in version 2. This provides greater flexibility for scripts and tests that interact with multiple networks.
*📚 Learn more from the official* [*Hardhat documentation*](https://hardhat.org/docs/getting-started)*.*
***
## Step 1: Project Setup
#### **Initialize Project**
Set up your project by initializing the hardhat project:
```bash theme={null}
mkdir hardhat-erc-721-mint-burn
cd hardhat-erc-721-mint-burn
npx hardhat --init
```
Make sure to select "**Hardhat 3 -> Typescript Hardhat Project using Mocha and Ethers.js"** and accept the default values. Hardhat will configure your project correctly and install the required dependencies.
#### Install Dependencies
Next, install the required dependencies:
```bash theme={null}
npm install @openzeppelin/contracts
```
Before we make any changes to our Hardhat configuration file, let's set some configuration variables we will be referring to within the file later.
```bash theme={null}
# If you have set a different one before, use the --force flag to overwrite
npx hardhat keystore set HEDERA_RPC_URL
```
For `HEDERA_RPC_URL`, we'll have `https://testnet.hashio.io/api`
```bash theme={null}
# If you have set a different one before, use the --force flag
npx hardhat keystore set HEDERA_PRIVATE_KEY
```
For `HEDERA_PRIVATE_KEY`, enter the **HEX Encoded Private Key for your ECDSA account** from the [Hedera Portal.](https://portal.hedera.com/)
#### Note
[*Hashio*](https://www.hashgraph.com/hashio/) *is intended for development and testing purposes only. For production use cases, it's recommended to use commercial-grade JSON-RPC Relay or host your own instance of the* [*Hiero JSON-RPC Relay*](https://github.com/hiero-ledger/hiero-json-rpc-relay)*.*
#### Configure Hardhat
Update your `hardhat.config.ts`file in the root directory of your project. This file contains the network settings so Hardhat knows how to interact with the Hedera Testnet.
```typescript hardhat.config.ts theme={null}
import type { HardhatUserConfig } from "hardhat/config";
import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import { configVariable } from "hardhat/config";
const config: HardhatUserConfig = {
plugins: [hardhatToolboxMochaEthersPlugin],
solidity: {
profiles: {
default: {
version: "0.8.28",
},
production: {
version: "0.8.28",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
},
},
networks: {
testnet: {
type: "http",
url: configVariable("HEDERA_RPC_URL"),
accounts: [configVariable("HEDERA_PRIVATE_KEY")],
},
},
};
export default config;
```
You can verify the connection by running:
```bash theme={null}
npx hardhat console --network testnet
```
This command launches an interactive JavaScript console connected directly to the Hedera Testnet, providing access to the Ethers.js library for blockchain interactions. If you successfully enter this interactive environment, your Hardhat configuration is correct. To exit the interactive console, press `ctrl + c` twice.
We won't be using `ignition` and we will be removing the default contracts that comes with hardhat default project so we will remove all the unnecessary directories and files first:
```bash theme={null}
rm -rf contracts/* scripts/* test/*
rm -rf ignition
```
***
## Step 2: Creating the ERC-721 Contract
Create a new Solidity file (`MyToken.sol`) in our `contracts` directory:
```solidity contracts/MyToken.sol theme={null}
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.28;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC721, Ownable {
uint256 private _nextTokenId;
constructor(address initialOwner)
ERC721("MyToken", "MTK")
Ownable(initialOwner)
{}
function safeMint(address to) public onlyOwner returns (uint256) {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
return tokenId;
}
}
```
This contract was created using the [OpenZeppelin Contracts Wizard](https://wizard.openzeppelin.com/#erc721) and OpenZeppelin's ERC-721 standard implementation with an ownership model. The ERC-721 token's name has been set to "MyToken." The contract implements the `safeMint` function, which accepts the address of the owner of the new token and uses auto-increment IDs, starting from 0.
Let's compile this contract by running:
```bash theme={null}
npx hardhat build
```
This command will generate the smart contract artifacts, including the [ABI](/evm/development/compiling). We are now ready to deploy the smart contract.
***
## Step 3: Deploy Your ERC-721 Smart Contract
Create a deployment script (`deploy.ts`) in `scripts` directory:
```typescript scripts/deploy.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "testnet",
});
async function main() {
// Get the signer of the tx and address for minting the token
const [deployer] = await ethers.getSigners();
console.log("Deploying contract with the account:", deployer.address);
// The deployer will also be the owner of our NFT contract
const MyToken = await ethers.getContractFactory("MyToken", deployer);
const contract = await MyToken.deploy(deployer.address);
await contract.waitForDeployment();
const address = await contract.getAddress();
console.log("Contract deployed at:", address);
}
main().catch(console.error);
```
In this script, we first retrieve your account (the deployer) using Ethers.js. This account will own the deployed smart contract. Next, we use this account to deploy the contract by calling `MyToken.deploy(deployer.address)`. This passes your account address as the initial owner and signer of the deployment transaction.
Deploy your contract by executing the script:
```bash theme={null}
npx hardhat run scripts/deploy.ts --network testnet
```
Copy the deployed address—you'll need this in subsequent steps.
The output looks like this:
```bash theme={null}
~/projects/hardhat-erc-721-mint-burn >> npx hardhat run scripts/deploy.ts --network testnet
Compiling your Solidity contracts...
Compiled 1 Solidity file with solc 0.8.28 (evm target: cancun)
Deploying contract with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Contract deployed at: 0x6035bA3BCa9595637B463Aa514c3a1cE3f67f3de
```
***
## Step 4: Minting an NFT
Create a `mint.ts` script in your `scripts` directory to mint an NFT. Don't forget to replace the `` with the address you've just copied.
```typescript scripts/mint.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "testnet",
});
async function main() {
const [deployer] = await ethers.getSigners();
// Get the ContractFactory of your MyToken ERC-721 contract
const MyToken = await ethers.getContractFactory("MyToken", deployer);
// Connect to the deployed contract
// (REPLACE WITH YOUR CONTRACT ADDRESS)
const contractAddress = "";
const contract = MyToken.attach(contractAddress);
// Mint a token to ourselves
const mintTx = await contract.safeMint(deployer.address);
const receipt = await mintTx.wait();
console.log("receipt: ", JSON.stringify(receipt, null, 2));
const mintedTokenId = receipt?.logs[0].topics[3];
console.log("Minted token ID:", mintedTokenId);
// Check the balance of the token
const balance = await contract.balanceOf(deployer.address);
console.log("Balance:", balance.toString(), "NFTs");
}
main().catch(console.error);
```
The code mints a new NFT to your account ( `deployer.address` ). Then we verify the balance to see if we own an ERC-721 token of type `MyToken`.
Mint an NFT:
```bash theme={null}
npx hardhat run scripts/mint.ts --network testnet
```
Expected output:
```json theme={null}
~/projects/hardhat-erc-721-mint-burn >> npx hardhat run scripts/mint.ts --network testnet
Compiling your Solidity contracts...
Nothing to compile
receipt: {
"_type": "TransactionReceipt",
"blockHash": "0x110b2de909e2f4d515b76de4ffd7a8a9f4c3e68c79f8aa083f9baf2a7d082a5c",
"blockNumber": 23836191,
"contractAddress": "0x6035bA3BCa9595637B463Aa514c3a1cE3f67f3de",
"cumulativeGasUsed": "800000",
"from": "0xA98556A4deeB07f21f8a66093989078eF86faa30",
"gasPrice": "350000000000",
"blobGasUsed": null,
"blobGasPrice": null,
"gasUsed": "800000",
"hash": "0xb0a67ee89e224208599b29a71bc5de1abc5aba4cf64553893aaf0aeb051f7a91",
"index": 9,
"logs": [
{
"_type": "log",
"address": "0x6035bA3BCa9595637B463Aa514c3a1cE3f67f3de",
"blockHash": "0x110b2de909e2f4d515b76de4ffd7a8a9f4c3e68c79f8aa083f9baf2a7d082a5c",
"blockNumber": 23836191,
"data": "0x",
"index": 0,
"topics": [
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
"0x0000000000000000000000000000000000000000000000000000000000000000",
"0x000000000000000000000000a98556a4deeb07f21f8a66093989078ef86faa30",
"0x0000000000000000000000000000000000000000000000000000000000000000"
],
"transactionHash": "0xb0a67ee89e224208599b29a71bc5de1abc5aba4cf64553893aaf0aeb051f7a91",
"transactionIndex": 9
}
],
"logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008000000000000002001000000000000000000000000000000020000000000000000000800000000000000000000000010000000000000000000000400000000020000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000000",
"root": "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421",
"status": 1,
"to": "0x6035bA3BCa9595637B463Aa514c3a1cE3f67f3de"
}
Minted token ID: 0x0000000000000000000000000000000000000000000000000000000000000000
Balance: 1 NFTs
```
***
## Step 5: Adding the Burn Functionality
Update your contract to add NFT burning capability by importing the burnable extension and adding it to the interfaces list for your contract:
```solidity theme={null}
// [...]
import {ERC721Burnable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Burnable.sol";
contract MyToken is ERC721, ERC721Burnable, Ownable {
// [...]
```
Redeploy:
```bash theme={null}
npx hardhat run scripts/deploy.ts --network testnet
```
Copy the new smart contract address and replace the address in the `scripts/mint.ts` script with your new address. Let's mint a new NFT for the redeployed contract:
```bash theme={null}
npx hardhat run scripts/mint.ts --network testnet
```
***
## Step 6: Burning an NFT
Create a burn script (`burn.ts` ) in your `scripts` directory:
```typescript scripts/burn.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "testnet",
});
async function main() {
const [deployer] = await ethers.getSigners();
// Get the ContractFactory of your MyToken ERC-721 contract
const MyToken = await ethers.getContractFactory("MyToken", deployer);
// Connect to the deployed contract
// (REPLACE WITH YOUR CONTRACT ADDRESS)
const contractAddress = "";
const contract = MyToken.attach(contractAddress);
// Burn the token
const burnTx = await contract.burn(0);
const receipt = await burnTx.wait();
console.log("receipt: ", JSON.stringify(receipt, null, 2));
const burnedTokenId = receipt?.logs[0].topics[3];
console.log("Burned token with ID:", burnedTokenId);
// Check the balance of the token
const balance = await contract.balanceOf(deployer.address);
console.log("Balance:", balance.toString(), "NFTs");
}
main().catch(console.error);
```
Again, ensure you update `` to interact with your correct contract. The script will burn the ERC-721 token with the ID set to `0`, which is the ERC-721 token you've just minted. To be sure the token has been deleted, let's print the balance for our account to the terminal. The balance should show a balance of `0`.
Burn the NFT:
```bash theme={null}
npx hardhat run scripts/burn.ts --network testnet
```
**Congratulations! 🎉 You have successfully learned how to deploy an ERC-721 smart contract using Hardhat, OpenZeppelin, and Ethers. Feel free to reach out in** [**Discord**](https://hedera.com/discord)**!**
## Step 7: Run tests(Optional)
You can find both types of tests in the [**Hedera-Code-Snippets repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hardhat-erc-721-mint-burn). You will find the following files:
* `contracts/MyToken.t.sol`
* `test/MyToken.ts`
Copy these files and then run the tests:
```bash theme={null}
npx hardhat test
```
You can also run tests individually with either of these
```bash theme={null}
npx hardhat test solidity
npx hardhat test mocha
```
***
## Additional Resources
* [OpenZeppelin ERC-721 Documentation](https://docs.openzeppelin.com/contracts/5.x/erc721)
* [Full Code in Hedera-Code-Snippets Repository](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hardhat-erc-721-mint-burn)
[Github](https://github.com/michielmulders) | [Linkedin](https://www.linkedin.com/in/michielmulders/)
[Github](https://github.com/acuarica)
>
[GitHub](https://github.com/theekrystallee) | [X](https://x.com/theekrystallee)
[Github](https://github.com/kpachhai) | [Linkedin](https://www.linkedin.com/in/kiranpachhai/)
# How to Set Access Control, a Token URI, Pause, and Transfer an ERC-721 Token Using Hardhat (Part 2)
Source: https://docs.hedera.com/evm/tutorials/advanced/erc721-hardhat/part2-access-control
In this tutorial, you'll learn how to create and manage an advanced ERC-721 token smart contract using Hardhat and OpenZeppelin. We'll cover deploying the contract, minting NFTs, pausing and unpausing the contract, and transferring tokens. You'll gain experience with [Access Control](https://docs.openzeppelin.com/contracts/5.x/access-control#using-access-control) (admin, minting, pausing roles), URI storage, and Pausable functionalities.
You can take a look at the **complete code** in the [**Hedera-Code-Snippets
repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hardhat-erc-721-mint-burn).
***
## Prerequisites
* ⚠️ **Complete** [**tutorial part 1**](/evm/tutorials/advanced/erc721-hardhat/part1-mint-burn) **as we continue from this example.**
* Basic understanding of smart contracts.
* Basic understanding of [Node.js](https://nodejs.org/en/download) and JavaScript.
* Basic understanding of [Hardhat EVM Development Tool](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3) and [Ethers](https://docs.ethers.org/v6/).
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/).
***
## Table of Contents
1. [Create and Compile the Solidity Contract](#step-1%3A-create-and-compile-the-solidity-contract)
2. [Deploying the Smart Contract and Minting a Token](#step-2%3A-deploying-the-smart-contract-and-minting-a-token)
3. [Fixing Permissions, Redeploying, and Minting](#step-3%3A-fixing-permissions-redeploying-and-minting)
4. [Pausing the Contract](#step-4%3A-pausing-the-contract)[Pausing the Contract](#step-4%3A-pausing-the-contract)
5. [Transferring NFTs](#step-5%3A-transferring-nfts)
6. [Run tests](#step-6%3A-run-tests-optional)
***
## Video Tutorial
You can watch the video tutorial (which uses **Hardhat version 2**) or follow the step-by-step tutorial below (which uses **Hardhat version 3**).
Key differences in Hardhat 3:
* **compile → build**\
`npx hardhat compile` is now `npx hardhat build`. This is the big one. The v3 migration guide explicitly shows using the `build` task.
* **project init switch**\
v2 commonly used `npx hardhat` or `npx hardhat init` to bootstrap. In v3 it’s `npx hardhat --init`.
- **keystore helper commands are new**\
v3’s recommended flow includes a keystore plugin with commands like `npx hardhat keystore set HEDERA_RPC_URL` and `npx hardhat keystore set HEDERA_PRIVATE_KEY`. These weren’t standard in v2.
- **Foundry-compatiable Solidity tests**\
In addition to offering Javascript/Typescript integration tests, Hardhat v3 also integrates Foundry-compatible Solidity tests that allows developers to write unit tests directly in Solidity
* **Enhanced Network Management**\
v3 allows tasks to create and manage multiple network connections simultaneously which is a significant improvement over the single, fixed connection available in version 2. This provides greater flexibility for scripts and tests that interact with multiple networks.
*📚 Learn more from the official* [*Hardhat documentation*](https://hardhat.org/docs/getting-started)*.*
***
## Step 1: Create and Compile the Solidity Contract
Create a new Solidity file named `MyTokenAdvanced.sol` in your `contracts` directory, and paste this Solidity code:
```solidity contracts/MyTokenAdvanced.sol theme={null}
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.28;
import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import {ERC721Pausable} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721Pausable.sol";
import {ERC721URIStorage} from "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract MyTokenAdvanced is ERC721, ERC721URIStorage, ERC721Pausable, AccessControl {
bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
uint256 private _nextTokenId;
constructor(address defaultAdmin, address pauser, address minter)
ERC721("MyTokenAdvanced", "MTK")
{
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_grantRole(PAUSER_ROLE, pauser);
_grantRole(MINTER_ROLE, minter);
}
function pause() public onlyRole(PAUSER_ROLE) {
_pause();
}
function unpause() public onlyRole(PAUSER_ROLE) {
_unpause();
}
function safeMint(address to, string memory uri)
public
onlyRole(MINTER_ROLE)
returns (uint256)
{
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
_setTokenURI(tokenId, uri);
return tokenId;
}
// The following functions are overrides required by Solidity.
function _update(address to, uint256 tokenId, address auth)
internal
override(ERC721, ERC721Pausable)
returns (address)
{
return super._update(to, tokenId, auth);
}
function tokenURI(uint256 tokenId)
public
view
override(ERC721, ERC721URIStorage)
returns (string memory)
{
return super.tokenURI(tokenId);
}
function supportsInterface(bytes4 interfaceId)
public
view
override(ERC721, ERC721URIStorage, AccessControl)
returns (bool)
{
return super.supportsInterface(interfaceId);
}
}
```
The contract implements the `ERC721URIStorage`, `ERC721Pausable`, and `AccessControl` interfaces from OpenZeppelin. You can create the contract yourself using the OpenZeppelin Wizard and enable "Mintable," "Pausable," "URI Storage," and "Access Control → Roles."
Compile your new contract:
```bash theme={null}
npx hardhat build
```
***
## Step 2: Deploying the Smart Contract and Minting a Token
Create `deploy-advanced.ts` in your `scripts` folder:
```typescript scripts/deploy-advanced.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "testnet",
});
async function main() {
// Get the signer of the tx and address for minting the token
const [deployer] = await ethers.getSigners();
console.log("Deploying contract with the account:", deployer.address);
// The deployer will also be the owner of our NFT contract
const MyTokenAdvanced = await ethers.getContractFactory(
"MyTokenAdvanced",
deployer
);
const contract = await MyTokenAdvanced.deploy(
deployer.address,
deployer.address,
"0xc0ffee254729296a45a3885639AC7E10F9d54979"
);
await contract.waitForDeployment();
const address = await contract.getAddress();
console.log("Contract deployed at:", address);
}
main().catch(console.error);
```
Note that we are providing three arguments to the `MyTokenAdvanced.deploy()` function. When we look at the constructor of our smart contract, we can provide the admin, pauser, and minter roles.
```javascript theme={null}
constructor(address defaultAdmin, address pauser, address minter)
```
The `deploy-advanced.ts` script sets the minter role to an unknown (*random*) address. This should prevent the deployer account from minting new tokens in the next step. First, let's run the deployer script:
```bash theme={null}
npx hardhat run scripts/deploy-advanced.ts --network testnet
```
Here's the output of the command:
```bash theme={null}
Deploying contract with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Contract deployed at: 0x5f41411477b506FA32DFe3B73BEE52a3D80B755f
```
**Copy the contract address of your newly deployed contract.** Next, create `mint-advanced.ts` in your `scripts` folder:
```typescript scripts/mint-advanced.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "testnet",
});
async function main() {
const [deployer] = await ethers.getSigners();
// Get the ContractFactory of your MyTokenAdvanced ERC-721 contract
const MyTokenAdvanced = await ethers.getContractFactory(
"MyTokenAdvanced",
deployer
);
// Connect to the deployed contract (REPLACE WITH YOUR CONTRACT ADDRESS)
const contractAddress = "0x6F5F9Ed50140bb9C94246257241Ed5AA5d40A25d";
const contract = MyTokenAdvanced.attach(contractAddress);
// Mint a token to ourselves
const mintTx = await contract.safeMint(
deployer.address,
"https://myserver.com/8bitbeard/8bitbeard-tokens/tokens/1"
);
const receipt = await mintTx.wait();
console.log("receipt: ", JSON.stringify(receipt, null, 2));
const mintedTokenId = receipt?.logs[0].topics[3];
console.log("Minted token ID:", mintedTokenId);
// Check the balance of the token
const balance = await contract.balanceOf(deployer.address);
console.log("Balance:", balance.toString(), "NFTs");
}
main().catch(console.error);
```
This contract tries to mint a new token and sets the token URI to `https://myserver.com/8bitbeard/8bitbeard-tokens/tokens/1` . This transaction will fail because our deployer account doesn't have the `minter` permission. Run the script:
```bash theme={null}
npx hardhat run scripts/mint-advanced.ts --network testnet
```
‼️ Notice minting fails due to incorrect permissions. Let's fix this in the next step.
***
## Step 3: Fixing Permissions, Redeploying, and Minting
Update the minting role to your deployer account by modifying the following line of code in your `deploy-advanced.ts` script:
```typescript scripts/deploy-advanced.ts wrap theme={null}
const contract = await MyTokenAdvanced.deploy(
deployer.address,
deployer.address,
deployer.address
); // Deployer account gets all roles
```
Now that the deployer account has all the roles, redeploy the contract:
```bash theme={null}
npx hardhat run scripts/deploy-advanced.ts --network testnet
```
Don't forget to **copy the new contract address and update** the `contractAddress` variable in your `mint-advanced.ts` script with this new address.
Next, execute the minting logic:
```bash theme={null}
npx hardhat run scripts/mint-advanced.ts --network testnet
```
The new token will be minted with token ID `0` and the corresponding token URI is printed to your terminal.
***
## Step 4: Pausing the Contract
Create a new `pause-advanced.ts` script and make sure to replace the `contractAddress` variable with your address:
```typescript scripts/pause-advanced.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "testnet",
});
async function main() {
const [deployer] = await ethers.getSigners();
// Get the ContractFactory of your MyTokenAdvanced ERC-721 contract
const MyTokenAdvanced = await ethers.getContractFactory(
"MyTokenAdvanced",
deployer
);
// Connect to the deployed contract (REPLACE WITH YOUR CONTRACT ADDRESS)
const contractAddress = "0x2a35e6532e9e6477205Cc845362EB6e71FcC0F0E";
const contract = MyTokenAdvanced.attach(contractAddress);
// Pause the token
const pauseTx = await contract.pause();
const receipt = await pauseTx.wait();
console.log("receipt: ", JSON.stringify(receipt, null, 2));
console.log("Paused token");
// Read the paused state
const pausedState = await contract.paused();
console.log("Contract paused state is:", pausedState);
}
main().catch(console.error);
```
The script calls the pause function on your contract. As we have the correct role, the token will be paused, and its paused state will be printed to the terminal. Execute the script:
```bash theme={null}
npx hardhat run scripts/pause-advanced.ts --network testnet
```
The contract will return `true` when it is paused. Now, nobody can mint new tokens.
Pausing an ERC-721 contract temporarily disables critical functions, including
minting, transferring, and burning tokens. While the contract is paused, users
cannot perform these operations, making it particularly useful in emergency
scenarios or maintenance periods. However, read operations, such as checking
token balances or URIs, are still possible.
***
## Step 5: Transferring NFTs
Create a `transfer-advanced.ts` script to transfer an NFT to another address. Don't forget to replace the `contractAddress` with your smart contract address.
```typescript scripts/transfer-advanced.ts theme={null}
async function main() {
const [deployer] = await ethers.getSigners();
const MyTokenAdvanced = await ethers.getContractFactory(
"MyTokenAdvanced",
deployer
);
// Connect to the deployed contract (REPLACE WITH YOUR CONTRACT ADDRESS)
const contractAddress = "0x11828533C93F8A1e19623343308dFb4a811005dE";
const contract = await MyTokenAdvanced.attach(contractAddress);
// Unpause the token
const unpauseTx = await contract.unpause();
await unpauseTx.wait();
console.log("Unpaused token");
// Read the paused state
const pausedState = await contract.paused();
console.log("Contract paused state is:", pausedState);
// Transfer the token with ID 0
const transferTx = await contract.transferFrom(
deployer.address,
"0x5FbDB2315678afecb367f032d93F642f64180aa3",
0
);
await transferTx.wait();
const balance = await contract.balanceOf(
"0x5FbDB2315678afecb367f032d93F642f64180aa3"
);
console.log("Balance:", balance.toString(), "NFTs");
}
main().catch(console.error);
```
This script will first unpause your contract and then transfer the token to a random address `0x5FbDB2315678afecb367f032d93F642f64180aa3` using the [`transferFrom` function](https://docs.openzeppelin.com/contracts/2.x/api/token/erc721#ERC721-transferFrom-address-address-uint256-) on your contract. This function accepts the sender address, receiver address, and the token ID you want to transfer. Next, we check if the account has actually received the token by verifying its balance.
Execute the script to transfer the token:
```bash theme={null}
npx hardhat run scripts/transfer-advanced.ts --network testnet
```
**If the balance for the\*\***`0x5FbDB2315678afecb367f032d93F642f64180aa3`\***\*account shows `1` , then you've successfully transferred the NFT and completed this tutorial! 🎉**
***
## Step 6: Run tests(Optional)
You can find both types of tests in the [**Hedera-Code-Snippets repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hardhat-erc-721-mint-burn). You will find the following files:
* `contracts/MyTokenAdvanced.t.sol`
* `test/MyTokenAdvanced.ts`
Copy these files and then run the tests:
```bash theme={null}
npx hardhat test
```
You can also run tests individually with either of these
```bash theme={null}
npx hardhat test solidity
npx hardhat test mocha
```
***
## Additional Resources
* [OpenZeppelin ERC-721 Docs](https://docs.openzeppelin.com/contracts/5.x/erc721)
* [Access Control Docs](https://docs.openzeppelin.com/contracts/5.x/access-control#using-access-control)
[GitHub](https://github.com/michielmulders) |
[LinkedIn](https://www.linkedin.com/in/michielmulders/)
[GitHub](https://github.com/acuarica)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# How to Upgrade an ERC-721 Token with OpenZeppelin UUPS Proxies and Hardhat (Part 3)
Source: https://docs.hedera.com/evm/tutorials/advanced/erc721-hardhat/part3-upgradeable
In this tutorial, you'll learn how to upgrade your ERC-721 smart contract using the OpenZeppelin UUPS (Universal Upgradeable Proxy Standard) pattern and Hardhat. We'll first cover how the upgradeable proxy pattern works, then go through step-by-step implementation and upgrade verification, explaining each part clearly.
You can take a look at the **complete code** in the [**Hedera-Code-Snippets
repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hardhat-erc-721-mint-burn).
## Understanding the Upgradeable Proxy Pattern (Simplified)
In traditional smart contracts, once deployed, the code is immutable, meaning bugs can't be fixed and new features can't be added. The upgradeable proxy pattern solves this by separating the contract into two components:
1. **Proxy Contract**: Stores the contract’s state (data) and delegates all function calls to a logic contract using delegatecall.
2. **Logic Contract**: Contains the actual business logic and can be upgraded.
When you upgrade your smart contract, you deploy a new logic contract and point your proxy contract to this new logic. The proxy stays at the same address, retaining your data and allowing seamless upgrades.
**Important Note**: In upgradeable contracts, constructors aren't used because the proxy doesn't call the constructor of the logic contract. Instead, we use an initialize function marked with the initializer modifier. This function serves the role of the constructor—setting up initial values and configuring inherited modules like ERC721 or Ownable. The initializer modifier ensures this function can only be called once, helping protect against accidental or malicious re-initialization.
***
## Prerequisites
* ⚠️ **Complete** [**tutorial part 1**](/evm/tutorials/advanced/erc721-hardhat/part1-mint-burn) **as we continue from this example. Part 2 is optional.**
* Basic understanding of smart contracts.
* Basic understanding of [Node.js](https://nodejs.org/en/download) and JavaScript.
* Basic understanding of [Hardhat EVM Development Tool](https://hardhat.org/docs/getting-started#getting-started-with-hardhat-3) and [Ethers](https://docs.ethers.org/v6/).
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/).
***
## Table of Contents
1. [Set Up Your Project](#step-1%3A-set-up-your-project)
2. [Create Your Initial Upgradeable ERC-721 Contract](#step-2%3A-create-your-initial-upgradeable-erc-721-contract)
3. [Deploy Your Upgradeable Contract](#step-3%3A-deploy-your-upgradeable-contract)
4. [Upgrade Your ERC-721 Contract](#step-4%3A-upgrade-your-erc-721-contract)
5. [Deploy the Upgrade and Verify](#step-5%3A-deploy-the-upgrade-and-verify)
6. [Run tests](#step-6%3A-run-tests-optional)
7. [Why Use the UUPS Pattern?](#why-use-the-uups-pattern)
***
## Video Tutorial
You can watch the video tutorial (which uses **Hardhat version 2**) or follow the step-by-step tutorial below (which uses **Hardhat version 3**).
Key differences in Hardhat 3:
* **compile → build**\
`npx hardhat compile` is now `npx hardhat build`. This is the big one. The v3 migration guide explicitly shows using the `build` task.
* **project init switch**\
v2 commonly used `npx hardhat` or `npx hardhat init` to bootstrap. In v3 it’s `npx hardhat --init`.
- **keystore helper commands are new**\
v3’s recommended flow includes a keystore plugin with commands like `npx hardhat keystore set HEDERA_RPC_URL` and `npx hardhat keystore set HEDERA_PRIVATE_KEY`. These weren’t standard in v2.
- **Foundry-compatiable Solidity tests**\
In addition to offering Javascript/Typescript integration tests, Hardhat v3 also integrates Foundry-compatible Solidity tests that allows developers to write unit tests directly in Solidity
* **Enhanced Network Management**\
v3 allows tasks to create and manage multiple network connections simultaneously which is a significant improvement over the single, fixed connection available in version 2. This provides greater flexibility for scripts and tests that interact with multiple networks.
*📚 Learn more from the official* [*Hardhat documentation*](https://hardhat.org/docs/getting-started)*.*
***
## Step 1: Set Up Your Project
Install necessary dependencies if you haven't done so.
```bash theme={null}
npm install @openzeppelin/contracts-upgradeable
```
For part 3 of this tutorial series, we're adding one extra dependency:
* `@openzeppelin/contracts-upgradeable` : This is a version of the OpenZeppelin Contracts library designed for upgradeable contracts. It contains modular and reusable smart contract components that are compatible with proxy deployment patterns, such as UUPS.
**Files overview (what each file does):**
* **`contracts/MyTokenUpgradeable.sol`**
* Upgradeable ERC-721 logic (initializer-based), Ownable, UUPS-ready. Holds functions like initialize and safeMint.
* **`contracts/MyTokenUpgradeableV2.sol`**
* Upgrade version that inherits V1 and adds version() for verification. No new storage variables to preserve layout.
* **`contracts/OZTransparentUpgradeableProxy.sol`**
* Thin wrapper so Hardhat has an artifact to deploy the proxy. Constructor takes logic, admin EOA, and initializer calldata.
* **`scripts/deploy-upgradeable.ts`**
* Deploys V1 logic, encodes initialize, deploys the Transparent proxy with your EOA as admin, sanity-checks via proxy, prints PROXY\_ADDRESS.
* **`scripts/upgrade-upgradeable.ts`**
* Deploys V2 logic and upgrades the proxy in-place by calling upgradeToAndCall as the admin EOA, then verifies version().
***
## Step 2: Create Your Initial Upgradeable ERC-721 Contract
Create `MyTokenUpgradeable.sol` in the `contracts/` directory:
```solidity theme={null}
// SPDX-License-Identifier: MIT
// Compatible with OpenZeppelin Contracts ^5.0.0
pragma solidity ^0.8.28;
import {ERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
contract MyTokenUpgradeable is
Initializable,
ERC721Upgradeable,
OwnableUpgradeable,
UUPSUpgradeable
{
uint256 private _nextTokenId;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
function initialize(address initialOwner) public initializer {
__ERC721_init("MyTokenUpgradeable", "MTU");
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
}
function safeMint(address to) public onlyOwner returns (uint256) {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
return tokenId;
}
function _authorizeUpgrade(
address newImplementation
) internal override onlyOwner {}
}
```
* Uses initializer pattern; constructor disables initializers and initialize() sets up ERC721, Ownable, and UUPS.
* UUPS gate: `_authorizeUpgrade` is `onlyOwner`, ensuring only the owner can upgrade when using UUPS flows.
* `safeMint` increments `_nextTokenId` and mints; keep storage layout stable across future versions.
* No constructor state writes; all initialization happens via `initialize()`.
We also need to create `OZTransparentUpgradeableProxy.sol` in the `contracts/` directory:
```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
contract OZTransparentUpgradeableProxy is TransparentUpgradeableProxy {
constructor(
address _logic,
address admin_,
bytes memory _data
) TransparentUpgradeableProxy(_logic, admin_, _data) {}
}
```
* Thin wrapper so Hardhat produces an artifact to deploy the Transparent proxy.
* Constructor takes logic address, admin EOA, and initializer calldata to run once at deployment.
* We use the Transparent proxy path here for a straightforward upgrade on Hedera; user calls go to logic via delegatecall, admin calls manage upgrades.
Now, let's build the contracts:
```bash theme={null}
npx hardhat build
```
***
## Step 3: Deploy Your Upgradeable Contract
Create `deploy-upgradeable.ts` under the `scripts` directory:
```typescript theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "testnet",
});
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contract with the account:", deployer.address);
// 1) Deploy implementation (V1)
const Impl = await ethers.getContractFactory("MyTokenUpgradeable", deployer);
const implementation = await Impl.deploy();
await implementation.waitForDeployment();
const implementationAddress = await implementation.getAddress();
console.log("Implementation:", implementationAddress);
// 2) Encode initializer
const initData = Impl.interface.encodeFunctionData("initialize", [
deployer.address,
]);
// 3) Deploy Transparent proxy with EOA admin (your deployer)
// Requires wrapper contract OZTransparentUpgradeableProxy in your repo
const TransparentProxy = await ethers.getContractFactory(
"OZTransparentUpgradeableProxy",
deployer
);
const proxy = await TransparentProxy.deploy(
implementationAddress,
deployer.address, // admin = EOA
initData
);
await proxy.waitForDeployment();
const proxyAddress = await proxy.getAddress();
console.log("Proxy address:", proxyAddress);
// 4) Sanity check via proxy
const token = Impl.attach(proxyAddress);
console.log("Name/Symbol:", await token.name(), "/", await token.symbol());
const mintTx = await token.safeMint(deployer.address);
await mintTx.wait();
console.log("Minted token 0. Owner:", await token.ownerOf(0n));
// 6) Output env var for upgrade step
console.log("\nPROXY_ADDRESS:", proxyAddress);
}
main().catch(console.error);
```
* Deploys V1 logic, encodes initialize(initialOwner), and deploys the Transparent proxy with your EOA as admin.
* Validates the deployment by calling ERC‑721 functions through the proxy and minting a token.
* Prints the PROXY\_ADDRESS to use in the upgrade step.
Deploy your contract:
```bash theme={null}
npx hardhat run scripts/deploy-upgradeable.ts --network testnet
```
**Make sure to copy the smart contract address for your ERC-721 token.**
```bash theme={null}
Deploying contract with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Implementation: 0x04E2ec2e702C4B74146F5de89310B8CfA2A0a463
Proxy address: 0x5A69d6fFcd27A4D253B2197A95D8488879Dd8ab5
Name/Symbol: MyTokenUpgradeable / MTU
Minted token 0. Owner: 0xA98556A4deeB07f21f8a66093989078eF86faa30
PROXY_ADDRESS: 0x5A69d6fFcd27A4D253B2197A95D8488879Dd8ab5
```
***
## Step 4: Upgrade Your ERC-721 Contract
Let's upgrade your contract by adding a new `version` function. Create `MyTokenUpgradeableV2.sol` in your `contracts` folder:
```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
import {MyTokenUpgradeable} from "./MyTokenUpgradeable.sol";
contract MyTokenUpgradeableV2 is MyTokenUpgradeable {
// Example new function to verify the upgrade worked
function version() public pure returns (string memory) {
return "v2";
}
}
```
* Inherits from V1 and adds only behavior (`version()`); no new storage variables to keep layout compatible.
* Verifies that after upgrade, calls through the proxy hit the new implementation.
* Safe pattern for upgrades: extend behavior, avoid touching existing state ordering.
Build the upgraded version:
```bash theme={null}
npx hardhat build
```
***
## Step 5: Deploy the Upgrade and Verify
Create `upgrade-upgradeable.ts` script to upgrade and verify the new functionality. Make sure to update `Your_Proxy_Address` to your own from Step 3:
```javascript theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({
network: "testnet",
});
const PROXY_ADDRESS = "Your_Proxy_Address";
async function main() {
const [signer] = await ethers.getSigners();
console.log("Upgrader (must be proxy admin EOA):", signer.address);
console.log("Proxy:", PROXY_ADDRESS);
// 1) Deploy the new implementation (V2)
const V2 = await ethers.getContractFactory("MyTokenUpgradeableV2", signer);
const newImpl = await V2.deploy();
await newImpl.waitForDeployment();
const newImplAddress = await newImpl.getAddress();
console.log("New implementation:", newImplAddress);
// 2) Upgrade directly via proxy (EOA admin path)
// Transparent proxy exposes upgradeToAndCall(newImpl, data) to the admin EOA
const proxyIface = new ethers.Interface([
"function upgradeToAndCall(address newImplementation, bytes data)",
]);
const data = proxyIface.encodeFunctionData("upgradeToAndCall", [
newImplAddress,
"0x", // no initializer
]);
const tx = await signer.sendTransaction({
to: PROXY_ADDRESS,
data,
});
const receipt = await tx.wait();
console.log("Upgrade tx status:", receipt?.status);
const proxyAsV2 = V2.attach(PROXY_ADDRESS);
console.log("version():", await proxyAsV2.version());
}
main().catch(console.error);
```
* Deploys V2 and constructs `upgradeToAndCall(newImpl, "0x")` calldata via `ethers.Interface`.
* Sends the upgrade tx directly to the proxy from the admin EOA; this path is reliable on Hedera.
* Verifies by calling `version()` through the proxy; expect `"v2"` after a successful upgrade.
Run this upgrade script:
```bash theme={null}
npx hardhat run scripts/upgrade-upgradeable.ts --network testnet
```
Output confirms the upgrade:
```bash theme={null}
Upgrader (must be proxy admin EOA): 0xA98556A4deeB07f21f8a66093989078eF86faa30
Proxy: 0x5A69d6fFcd27A4D253B2197A95D8488879Dd8ab5
New implementation: 0x17ee29551847de4BE10d882472405F89F361ace7
Upgrade tx status: 1
version(): v2
```
Congratulations! 🎉 You've successfully implemented and upgraded an ERC-721 smart contract using OpenZeppelin’s UUPS proxy pattern with Hardhat.
## Step 6: Run tests(Optional)
You can find both types of tests in the [**Hedera-Code-Snippets repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hardhat-erc-721-mint-burn). You will find the following files:
* `contracts/MyTokenUpgradeable.t.sol`
* `contracts/MyTokenUpgradeableV2.t.sol`
* `test/MyTokenUpgradeable.ts`
* `test/MyTokenUpgradeableV2.ts`
Copy these files and then run the tests:
```bash theme={null}
npx hardhat test
```
You can also run tests individually with either of these
```bash theme={null}
npx hardhat test solidity
npx hardhat test mocha
```
## Why Use the UUPS Pattern?
* **Security**: Upgrade functions can be restricted, ensuring only authorized roles can perform upgrades.
* **Data Retention**: Maintains all token balances and stored data during upgrades.
* **Flexibility**: Enables easy updates for new features, improvements, or critical fixes without redeploying a completely new contract.
#### **Note**
This tutorial’s contracts follow the UUPS initializer and authorization best practices (`UUPSUpgradeable` + `\_authorizeUpgrade`), while the example scripts perform the upgrade using an admin function `TransparentUpgradeableProxy` for a straightforward, reliable flow on Hedera. If you later switch to a pure UUPS proxy (ERC1967Proxy), upgrades would be triggered via the logic contract’s UUPS mechanism instead of the Transparent proxy’s admin API.
***
## Additional Resources
* [Proxy Upgrade Pattern (OpenZeppelin)](https://docs.openzeppelin.com/upgrades-plugins/proxies)
[GitHub](https://github.com/michielmulders) |
[LinkedIn](https://www.linkedin.com/in/michielmulders/)
[GitHub](https://github.com/acuarica)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# How to Connect MetaMask to Hedera
Source: https://docs.hedera.com/evm/tutorials/beginner/connect-metamask
[**Download the MetaMask wallet**](https://metamask.io/download/), then configure the Hedera network/testnet settings in the MM network settings with one of the three methods below:
**➡** [**Connecting via HashScan**](#id-1.-connecting-via-hashscan)
**➡** [**Manual Method**](#id-2.-manual-method)
**➡** [**ChainList Method**](#id-3.-chainlist-method)
Each method offers its own advantages and caters to different user preferences. By the end of this guide, you'll be able to connect your MetaMask wallet to the Hedera network.
***
## 1. Connecting via HashScan
This new method allows you to connect to the Hedera network through HashScan:
1. Go to [https://hashscan.io](https://hashscan.io/).
2. Click on the **Connect Wallet** button in the top right corner.
3. Select 🦊 **MetaMask** from the list of wallet options.
4. If you haven't added the Hedera network to MetaMask yet, HashScan will prompt you to add it.
5. Click **Approve** to add the Hedera network to MetaMask.
6. Once added, you can switch to the Hedera network in MetaMask.
***
## 2. Manual Method
1. Open MetaMask and click on the network dropdown at the top.
2. Select **Add Network** and then **Add Network Manually**.
3. Enter the following details:
* **Network Name**: Hedera Mainnet
* **New RPC URL**: [https://mainnet.hashio.io/api](https://mainnet.hashio.io/api)
* **Chain ID**: 295
* **Currency Symbol**: HBAR
* **Block Explorer URL**: [https://hashscan.io/mainnet/](https://hashscan.io/mainnet/)
4. Click **Save** to add the Hedera Mainnet to MetaMask.
Choose the appropriate endpoint based on whether you want to connect to Mainnet, Testnet, or Previewnet.
**Mainnet**
* [https://mainnet.hashio.io/api](https://mainnet.hashio.io/api)
* [https://295.rpc.thirdweb.com](https://295.rpc.thirdweb.com)
**Testnet**
* [https://testnet.hashio.io/api](https://testnet.hashio.io/api)
* [https://296.rpc.thirdweb.com](https://296.rpc.thirdweb.com)
**Previewnet**
* [https://previewnet.hashio.io/api](https://previewnet.hashio.io/api)
* [https://297.rpc.thirdweb.com](https://297.rpc.thirdweb.com)
**Note**: [Hashio](https://swirldslabs.com/hashio/) is currently in beta & for
testing purposes only.
***
## 3. ChainList Method
Alternatively, connect MetaMask to Hedera using ChainList:
1. Go to [https://chainlist.org/?search=hedera](https://chainlist.org/?search=hedera).
2. Click **Connect Wallet**.
3. Choose your account to connect, click **Next**, and **Connect**.
4. Select a network (Hedera Mainnet, Testnet, Previewnet, Localnet), click **Add to MetaMask**, and then click **Approve**.
***
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee) |
[Hashnode](https://hashnode.com/@theekrystallee)
[GitHub](https://github.com/bguiz) | [Blog](https://blog.bguiz.com/)
# Your First Token
Source: https://docs.hedera.com/evm/tutorials/beginner/your-first-token
Deploy and mint an ERC-20 token on Hedera testnet using Hardhat and OpenZeppelin.
This walks through deploying a standard ERC-20 token to Hedera testnet using Hardhat. The contract uses OpenZeppelin's audited ERC-20 base, so most of the code is library calls. The workflow is the same one you'd use on any EVM chain; the only Hedera-specific bit is the network config.
**Run in your browser:** Skip local setup and try the same example in the [Contract Builder](https://portal.hedera.com/contract-builder/session/0.0.7785355/13): compile, deploy, and mint from your browser in seconds.
## Prerequisites
Network Name `Hedera Testnet`, RPC `https://testnet.hashio.io/api`, Chain ID `296`.
Around 5 HBAR is plenty to cover deployment.
You also need [Node.js 18 or later](https://nodejs.org) installed locally.
## Step 1: Scaffold the project
```bash theme={null}
mkdir my-first-token && cd my-first-token
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npm install @openzeppelin/contracts dotenv
npx hardhat init # choose: "Create an empty hardhat.config.js"
mkdir contracts scripts
```
## Step 2: Write the ERC-20 contract
Create `contracts/MyToken.sol`:
```solidity theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.22;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
constructor(address initialOwner)
ERC20("MyToken", "MTK")
Ownable(initialOwner)
{
// Mint 1,000,000 tokens (with 18 decimals) to the deployer.
_mint(initialOwner, 1_000_000 * 10 ** decimals());
}
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
```
The contract inherits OpenZeppelin's `ERC20` and `Ownable`, mints the initial 1,000,000 supply to the deployer in the constructor, and exposes a `mint()` function gated by `onlyOwner` for later minting.
## Step 3: Configure Hardhat for Hedera testnet
Create a `.env` file (and add it to `.gitignore`):
```dotenv theme={null}
TESTNET_RPC=https://testnet.hashio.io/api
TESTNET_PRIVATE_KEY=0xYOUR_METAMASK_PRIVATE_KEY
```
Use a dedicated dev wallet. Don't put a mainnet private key in a `.env` file you're experimenting with. To get the testnet key from MetaMask: Account → ⋮ → Account details → Show private key.
Replace `hardhat.config.js` with:
```javascript theme={null}
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: "0.8.22",
networks: {
hederaTestnet: {
url: process.env.TESTNET_RPC,
chainId: 296,
accounts: [process.env.TESTNET_PRIVATE_KEY],
},
},
};
```
## Step 4: Write the deploy script
Create `scripts/deploy.js`:
```javascript theme={null}
const hre = require("hardhat");
async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying with:", deployer.address);
const MyToken = await hre.ethers.getContractFactory("MyToken");
const token = await MyToken.deploy(deployer.address);
await token.waitForDeployment();
const address = await token.getAddress();
console.log("MyToken deployed to:", address);
console.log("Initial balance:", (await token.balanceOf(deployer.address)).toString());
}
main().catch((err) => {
console.error(err);
process.exitCode = 1;
});
```
## Step 5: Deploy
```bash theme={null}
npx hardhat compile
npx hardhat run scripts/deploy.js --network hederaTestnet
```
Expected output:
```text theme={null}
Deploying with: 0xAbC123...
MyToken deployed to: 0xDeF456...
Initial balance: 1000000000000000000000000
```
That balance figure is `1,000,000 × 10^18`, which is your million tokens with 18 decimals.
## Step 6: Verify on HashScan
Open `https://hashscan.io/testnet/contract/`. HashScan picks the contract up automatically and shows its bytecode, the deployer account, and the deploy transaction.
For source-code-level visibility, follow the [contract verification guide](/evm/development/verifying).
## Step 7: Add the token to MetaMask
In MetaMask: **Tokens** tab → **Import tokens**, paste your contract address. Symbol and decimals are auto-filled from the contract. You should now see your full supply in the wallet.
## What's next
The token works with any ERC-20-aware tool: DEX listings, wallet imports, indexers, block explorers.
The Hedera-native alternative: create an HTS token with built-in compliance keys (KYC, freeze, pause, wipe) enforced by the network.
# HSS x EVM - Schedule Smart Contract Calls (Part 1)
Source: https://docs.hedera.com/evm/tutorials/hedera/hss-evm/part1-schedule-calls
On most EVM chains like Ethereum, smart contracts cannot "wake up" on their own—every function call must be triggered by an externally owned account (EOA) or an off-chain bot. This means implementing time-based automation (like cron jobs) requires external infrastructure.
**Hedera changes this fundamentally.**
With the **[Hedera Schedule Service (HSS) via HIP-755](https://hips.hedera.com/hip/hip-755)** and **[HIP-1215](https://hips.hedera.com/hip/hip-1215)**, smart contracts on Hedera can schedule future calls to themselves or other contracts. The Hedera network itself stores and executes these scheduled transactions when the time comes—**no off-chain bots required**.
This unlocks powerful new patterns:
* **On-chain cron jobs** for DeFi rebalancing and automation
* **Time-based vesting** and token releases
* **Recurring payments** and subscriptions
* **DAO governance** with time-delayed execution
In this tutorial, you'll build a simple **AlarmClock** contract that demonstrates this unique capability by scheduling one-shot and recurring alarms entirely on-chain.
You can take a look at the **complete code** in the [**hss-schedule-sc-calls
demo
repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hss-schedule-sc-calls).
***
## Prerequisites
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/).
* Basic understanding of Solidity.
***
## Table of Contents
1. [Setup Project](#setup-project)
2. [Step 1: Configure Hardhat](#step-1%3A-configure-hardhat)
3. [Step 2: Create the AlarmClock Smart Contract](#step-2%3A-create-the-alarmclock-smart-contract)
4. [Step 3: Deploy the Contract](#step-3%3A-deploy-the-contract)
5. [Step 4: Set a One-Shot Alarm](#step-4%3A-set-a-one-shot-alarm)
6. [Step 5: Set a Recurring Alarm](#step-5%3A-set-a-recurring-alarm)
7. [Step 6: Monitor Alarm Triggers](#step-6%3A-monitor-alarm-triggers)
8. [Step 7: Run Tests (Optional)](#step-7%3A-run-tests-optional)
9. [Conclusion](#conclusion)
10. [Additional Resources](#additional-resources)
***
## Setup Project
Set up your project by initializing the hardhat project.
```bash theme={null}
mkdir hss-schedule-sc-calls
cd hss-schedule-sc-calls
npx hardhat --init
```
Make sure to select "**Hardhat 3 -> Typescript Hardhat Project using Mocha and Ethers.js**" and accept the default values. Hardhat will configure your project correctly and install the required dependencies.
Key differences in Hardhat 3:
* **compile → build**\
`npx hardhat compile` is now `npx hardhat build`. This is the big one. The v3 migration guide explicitly shows using the `build` task.
* **project init switch**\
v2 commonly used `npx hardhat` or `npx hardhat init` to bootstrap. In v3 it's `npx hardhat --init`.
- **keystore helper commands are new**\
v3's recommended flow includes a keystore plugin with commands like `npx hardhat keystore set HEDERA_RPC_URL` and `npx hardhat keystore set HEDERA_PRIVATE_KEY`. These weren't standard in v2.
- **Foundry-compatible Solidity tests**\
In addition to offering Javascript/Typescript integration tests, Hardhat v3 also integrates Foundry-compatible Solidity tests that allows developers to write unit tests directly in Solidity
* **Enhanced Network Management**\
v3 allows tasks to create and manage multiple network connections simultaneously which is a significant improvement over the single, fixed connection available in version 2. This provides greater flexibility for scripts and tests that interact with multiple networks.
*📚 Learn more from the official* [*Hardhat documentation*](https://hardhat.org/docs/getting-started)*.*
Before we make any changes to our Hardhat configuration file, let's set some configuration variables we will be referring to within the file later.
```bash theme={null}
# If you have already set this before, please use the --force flag
npx hardhat keystore set HEDERA_RPC_URL
```
For `HEDERA_RPC_URL`, we'll have `https://testnet.hashio.io/api`
```bash theme={null}
# If you have already set this before, please use the --force flag
npx hardhat keystore set HEDERA_PRIVATE_KEY
```
For `HEDERA_PRIVATE_KEY`, enter the **HEX Encoded Private Key for your ECDSA account** from the [Hedera Portal. ](https://portal.hedera.com/)
We won't need any additional dependencies for this tutorial since we'll be interacting directly with the Schedule Service system contract.
Now let's remove the default contracts and scripts that come with the Hardhat project:
```bash theme={null}
rm -rf contracts/* scripts/* test/*
rm -rf ignition
```
#### Install Dependencies
Next, install the required dependencies:
```bash theme={null}
npm install @hiero-ledger/hiero-contracts
```
Note that we are installing the latest code from the main branch when we install `@hiero-ledger/hiero-contracts`. This also gets installed at `@hashgraph/smart-contracts` so we can easily call these contracts from our own contract.
***
## Step 1: Configure Hardhat
Update your `hardhat.config.ts`file in the root directory of your project. This file contains the network settings so Hardhat knows how to interact with the Hedera Testnet.
```typescript hardhat.config.ts theme={null}
import type { HardhatUserConfig } from "hardhat/config";
import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import { configVariable } from "hardhat/config";
const config: HardhatUserConfig = {
plugins: [hardhatToolboxMochaEthersPlugin],
solidity: {
profiles: {
default: {
version: "0.8.31"
},
production: {
version: "0.8.31",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
}
}
},
networks: {
testnet: {
type: "http",
url: configVariable("HEDERA_RPC_URL"),
accounts: [configVariable("HEDERA_PRIVATE_KEY")]
}
}
};
export default config;
```
***
## Step 2: Create the AlarmClock Smart Contract
Create a new Solidity file (`AlarmClockSimple.sol`) in your `contracts` directory:
```solidity contracts/AlarmClockSimple. sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.31;
import {
HederaScheduleService
} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-schedule-service/HederaScheduleService.sol";
import {
HederaResponseCodes
} from "@hashgraph/smart-contracts/contracts/system-contracts/HederaResponseCodes.sol";
/// Very simple "alarm clock" demo:
/// - User calls setAlarm()
/// - Contract schedules a call to triggerAlarm(alarmId) at roughly now + intervalSeconds
/// - No off-chain bots required
contract AlarmClockSimple is HederaScheduleService {
// Gas limit used for scheduled calls (must cover triggerAlarm + re-scheduling logic)
uint256 internal constant SCHEDULED_CALL_GAS_LIMIT = 2_000_000;
struct Alarm {
address user;
uint256 time; // when we expect it to fire
uint256 numTimesTriggered; // how many times this alarm has fired
bool recurring;
uint256 interval; // seconds between firings (for recurring alarms)
}
uint256 public nextAlarmId;
mapping(uint256 => Alarm) public alarms;
event AlarmScheduled(
address scheduleAddress,
uint256 alarmId,
uint256 time
);
event AlarmTriggered(
uint256 alarmId,
address user,
uint256 time,
uint256 numTimesTriggered
);
/// Funds will be used to pay the cost of executing the triggerAlarm function
/// and to schedule another timer in case of recurrent alarm.
constructor() payable {}
receive() external payable {}
/// User calls this to set a one-shot or recurring alarm.
/// For simplicity we choose time = block.timestamp + intervalSeconds.
function setAlarm(bool recurring, uint256 intervalSeconds) external {
require(intervalSeconds > 0, "interval must be > 0");
uint256 alarmId = nextAlarmId++;
uint256 alarmTime = block.timestamp + intervalSeconds;
alarms[alarmId] = Alarm({
user: msg.sender,
time: alarmTime,
numTimesTriggered: 0,
recurring: recurring,
interval: intervalSeconds
});
_scheduleAlarm(alarmId, alarmTime);
}
function _scheduleAlarm(uint256 alarmId, uint256 time) internal {
// Encode the future call: triggerAlarm(alarmId)
bytes memory callData = abi.encodeWithSelector(
this.triggerAlarm.selector,
alarmId
);
// Ask HIP-1215 Schedule Service to schedule this call
(int64 rc, address scheduleAddress) = scheduleCall(
address(this),
time,
SCHEDULED_CALL_GAS_LIMIT,
0,
callData
);
require(rc == HederaResponseCodes.SUCCESS, "Schedule failed");
emit AlarmScheduled(scheduleAddress, alarmId, time);
}
/// This is called automatically by the network when the scheduled time arrives.
function triggerAlarm(uint256 alarmId) external {
Alarm storage alarm = alarms[alarmId];
// Only the alarm owner and this contract can trigger the alarm
require(
msg.sender == address(this) || msg.sender == alarm.user,
"Not authorized"
);
// One-shot alarm can only fire once
require(
alarm.recurring || alarm.numTimesTriggered == 0,
"Already triggered"
);
alarm.numTimesTriggered += 1;
emit AlarmTriggered(
alarmId,
alarm.user,
block.timestamp,
alarm.numTimesTriggered
);
// If recurring, reschedule for the next interval
if (alarm.recurring) {
alarm.time = alarm.time + alarm.interval;
_scheduleAlarm(alarmId, alarm.time);
}
}
}
```
**How It Works**
1. **Schedule Service System Contract (`0x16b`)**: The contract interacts with the Hedera Schedule Service at the fixed address `0x16b`, which provides the `scheduleCall` function.
2. **setAlarm function**: When a user calls `setAlarm(recurring, intervalSeconds)`, the contract:
* Creates an `Alarm` struct in storage with the user's address and target time
* Calls `_scheduleAlarm` to schedule the future execution
3. **\_scheduleAlarm (internal)**: This function:
* Encodes the call data for `triggerAlarm(alarmId)`
* Calls the Schedule Service at `0x16b` with `scheduleCall(... )`
* Specifies the target time (`expirySecond`), gas limit, and encoded call data
* Emits `AlarmScheduled` event
4. **Scheduled Execution**: When the scheduled time arrives, the Hedera network automatically executes the scheduled transaction, calling `triggerAlarm(alarmId)` on the contract.
5. **triggerAlarm function**: This is called by the network (or can be manually triggered):
* Increments the trigger counter
* Emits `AlarmTriggered` event
* For recurring alarms, schedules the next occurrence by calling `_scheduleAlarm` again
6. **HBAR Requirement**: The contract must hold HBAR to pay for gas when scheduled transactions execute. Each execution (and re-scheduling for recurring alarms) deducts from the contract's balance.
Let's build this contract by running:
```bash theme={null}
npx hardhat build
```
This command will generate the smart contract artifacts, including the ABI. We are now ready to deploy the smart contract.
***
## Step 3: Deploy the Contract
Create a deployment script (`deploy.ts`) in `scripts` directory:
```typescript scripts/deploy.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contract with the account:", deployer.address);
// Deploy the AlarmClockSimple contract with initial HBAR funding
const AlarmClockSimple = await ethers.getContractFactory(
"AlarmClockSimple",
deployer
);
// Send 5 HBAR to the contract during deployment
// This HBAR will be used to pay for scheduled alarm executions
const HBAR_TO_SEND = "5";
console.log(
`Deploying with ${HBAR_TO_SEND} HBAR to fund scheduled executions... `
);
const contract = await AlarmClockSimple.deploy({
value: ethers.parseEther(HBAR_TO_SEND)
});
await contract.waitForDeployment();
const contractAddress = await contract.getAddress();
console.log("AlarmClockSimple contract deployed at:", contractAddress);
// Get the contract balance to verify funding
const balance = await ethers.provider.getBalance(contractAddress);
console.log("Contract HBAR balance:", ethers.formatEther(balance), "HBAR");
console.log("📝 Save this address for the next steps!");
console.log(`export CONTRACT_ADDRESS=${contractAddress}`);
}
main().catch(console.error);
```
**Critical: Why Does the Contract Need HBAR?**
Every time a scheduled transaction executes (when the network automatically calls `triggerAlarm`), gas fees must be paid. Since the contract itself is the payer for these scheduled executions, it **must hold HBAR**.
**For recurring alarms:**
* Each `triggerAlarm` execution consumes gas
* Each `triggerAlarm` then schedules the next alarm (more gas)
* If the contract runs out of HBAR, scheduled executions will fail and recurring alarms will stop
**Best practice:** Fund the contract with enough HBAR based on:
* Your `SCHEDULED_CALL_GAS_LIMIT` (set to 2,000,000 in this example)
* Expected number of alarm executions
* For recurring alarms, estimate how long you want them to run
You can always add more HBAR by sending a transaction to the contract address.
Deploy your contract by executing the script:
```bash theme={null}
npx hardhat run scripts/deploy.ts --network testnet
```
Copy the deployed contract address and set it as an environment variable for
the next steps.
Expected output:
```bash theme={null}
Deploying contract with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Deploying with 5 HBAR to fund scheduled executions...
AlarmClockSimple contract deployed at: 0xBa2bD87abAF00F96212081CF621DFC5728E0697e
Contract HBAR balance: 5.0 HBAR
📝 Save this address for the next steps!
export CONTRACT_ADDRESS=0xBa2bD87abAF00F96212081CF621DFC5728E0697e
```
Set the contract address as an environment variable:
```bash theme={null}
export CONTRACT_ADDRESS=0xYOURDEPLOYEDADDRESS
```
In order to decode events emitted from the contract, the contract must be verified.
```bash theme={null}
./generate_hedera_sc_metadata.sh AlarmClockSimple
```
You can then upload the `verify-bundles/AlarmClockSimple/metadata.json` file to Hashscan to verify this contract.
***
## Step 4: Set a One-Shot Alarm
Create a script (`setOneShot.ts`) in your `scripts` directory to set a one-shot alarm.
```typescript scripts/setOneShot.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [signer] = await ethers.getSigners();
console.log("Using signer:", signer.address);
const contractAddress =
process.env.CONTRACT_ADDRESS || "";
const alarmClockContract = await ethers.getContractAt(
"AlarmClockSimple",
contractAddress,
signer
);
// Set a one-shot alarm that fires in 10 seconds
const recurring = false;
const intervalSeconds = 10;
console.log(
`Setting one-shot alarm to fire in ${intervalSeconds} seconds... `
);
const tx = await alarmClockContract.setAlarm(recurring, intervalSeconds);
await tx.wait();
console.log("setAlarm tx hash:", tx.hash);
// Get the alarm ID (it's the nextAlarmId - 1 after our call)
const nextAlarmId = await alarmClockContract.nextAlarmId();
const alarmId = nextAlarmId - BigInt(1);
console.log("Alarm ID:", alarmId.toString());
// Retrieve alarm details
const alarm = await alarmClockContract.alarms(alarmId);
console.log("\nAlarm Details:");
console.log(" User:", alarm.user);
console.log(
" Scheduled time:",
new Date(Number(alarm.time) * 1000).toISOString()
);
console.log(" Recurring:", alarm.recurring);
console.log(" Times triggered:", alarm.numTimesTriggered.toString());
console.log("\n✅ One-shot alarm scheduled!");
console.log(
`⏰ It will automatically trigger in ~${intervalSeconds} seconds`
);
console.log(
`📊 View events at: https://hashscan.io/testnet/contract/${contractAddress}/events`
);
}
main().catch(console.error);
```
**How It Works**
1. Connects to your deployed `AlarmClockSimple` contract
2. Calls `setAlarm(false, 10)` to schedule a one-shot alarm in 10 seconds
3. The contract immediately schedules a transaction with the Schedule Service
4. After \~10 seconds, the network will automatically execute `triggerAlarm(0)`
5. You can view the `AlarmScheduled` and `AlarmTriggered` events on HashScan
**Important:** The alarm will fire automatically—no further action needed from you!
Run the script:
```bash theme={null}
npx hardhat run scripts/setOneShot.ts --network testnet
```
Expected output:
```bash theme={null}
Using signer: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Setting one-shot alarm to fire in 10 seconds...
setAlarm tx hash: 0x50bd2d8f3938a92fcb6e4dc036fed1439c8200c19349846e2a98741fd9f0831d
Alarm ID: 0
Alarm Details:
User: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Scheduled time: 2025-12-24T16:26:43.000Z
Recurring: false
Times triggered: 0
✅ One-shot alarm scheduled!
⏰ It will automatically trigger in ~10 seconds
📊 View events at: https://hashscan.io/testnet/contract/0xBa2bD87abAF00F96212081CF621DFC5728E0697e/events
```
***
## Step 5: Set a Recurring Alarm
Create a script (`setRecurring.ts`) in your `scripts` directory to set a recurring alarm.
```typescript scripts/setRecurring.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [signer] = await ethers.getSigners();
console.log("Using signer:", signer.address);
const contractAddress =
process.env.CONTRACT_ADDRESS || "";
const alarmClockContract = await ethers.getContractAt(
"AlarmClockSimple",
contractAddress,
signer
);
// Set a recurring alarm that fires every 10 seconds
const recurring = true;
const intervalSeconds = 10;
console.log(
`Setting recurring alarm to fire every ${intervalSeconds} seconds...`
);
const tx = await alarmClockContract.setAlarm(recurring, intervalSeconds);
await tx.wait();
console.log("setAlarm tx hash:", tx.hash);
// Get the alarm ID
const nextAlarmId = await alarmClockContract.nextAlarmId();
const alarmId = nextAlarmId - BigInt(1);
console.log("Alarm ID:", alarmId.toString());
// Retrieve alarm details
const alarm = await alarmClockContract.alarms(alarmId);
console.log("\nAlarm Details:");
console.log(" User:", alarm.user);
console.log(
" Scheduled time:",
new Date(Number(alarm.time) * 1000).toISOString()
);
console.log(" Recurring:", alarm.recurring);
console.log(" Interval:", alarm.interval.toString(), "seconds");
console.log(" Times triggered:", alarm.numTimesTriggered.toString());
// Check contract balance
const balance = await ethers.provider.getBalance(contractAddress);
console.log("\nContract HBAR balance:", ethers.formatEther(balance), "HBAR");
console.log("\n✅ Recurring alarm scheduled!");
console.log(
`⏰ It will trigger every ${intervalSeconds} seconds automatically`
);
console.log(
`⚠️ Make sure the contract has enough HBAR to pay for scheduled executions`
);
console.log(
`📊 View events at: https://hashscan.io/testnet/contract/${contractAddress}/events`
);
}
main().catch(console.error);
```
**How Recurring Alarms Work**
1. You call `setAlarm(true, 10)` to set a recurring alarm with a 10-second interval
2. The contract schedules the first `triggerAlarm` call for 10 seconds from now
3. When the scheduled time arrives, the network executes `triggerAlarm`:
* Increments the trigger counter
* Emits `AlarmTriggered` event
* **Automatically schedules the next alarm** for 10 seconds later
4. This creates a self-sustaining on-chain timer!
**The alarm will keep firing indefinitely until:**
* The contract runs out of HBAR (scheduled execution fails)
* You modify the contract logic to stop it
* A scheduling error occurs
This is perfect for:
* DeFi rebalancing strategies
* Periodic token distributions
* Recurring payment systems
* Time-based governance actions
Run the script:
```bash theme={null}
npx hardhat run scripts/setRecurring.ts --network testnet
```
Expected output:
```bash theme={null}
Using signer: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Setting recurring alarm to fire every 10 seconds...
setAlarm tx hash: 0xf14efe33ec59918114a89fe4f9ff0f2579429ab612451a1ef4fa4332e28d5e14
Alarm ID: 1
Alarm Details:
User: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Scheduled time: 2025-12-24T16:29:16.000Z
Recurring: true
Interval: 10 seconds
Times triggered: 0
Contract HBAR balance: 3.768 HBAR
✅ Recurring alarm scheduled!
⏰ It will trigger every 10 seconds automatically
⚠️ Make sure the contract has enough HBAR to pay for scheduled executions
📊 View events at: https://hashscan.io/testnet/contract/0xBa2bD87abAF00F96212081CF621DFC5728E0697e/events
```
***
## Step 6: Monitor Alarm Triggers
After setting your alarms, you can monitor their execution on HashScan, Hedera's block explorer.
### Viewing Events
Navigate to your contract's events page:
```
https://hashscan.io/testnet/contract//events
```
You'll see two types of events:
**1. AlarmScheduled Event**
```
AlarmScheduled(
scheduleAddress: 0x...,
alarmId: 0,
time: 1705315800
)
```
This event is emitted when an alarm is scheduled. For recurring alarms, you'll see this event multiple times—once for each scheduled occurrence.
**2. AlarmTriggered Event**
```
AlarmTriggered(
alarmId: 0,
user: 0xA98556A4deeB07f21f8a66093989078eF86faa30,
time: 1705315800,
numTimesTriggered: 1
)
```
This event is emitted when the scheduled time arrives and the alarm fires.
### Viewing Scheduled Transactions
You can also view the actual scheduled transaction executions on HashScan:
**Example Flow:**
1. **setAlarm transaction**: [View on HashScan](https://hashscan.io/testnet/transaction/1766593595.774248162)
* This is when you call `setAlarm()`
* Emits `AlarmScheduled` event
2. **Scheduled triggerAlarm execution**: [View on HashScan](https://hashscan.io/testnet/transaction/1766593603.079000004)
* This is the **automatic execution** by the network
* Notice the transaction was triggered by the schedule service, not by an EOA
* Emits `AlarmTriggered` event
**Understanding the Transaction Flow**
When you look at the scheduled transaction on HashScan on the [trace tab](https://hashscan.io/testnet/transaction/1766593603.079000004/trace), you will notice:
* The "From" address will be the contract itself (or show as scheduled execution)
* The "To" address is the contract address
* The "Function" called is `triggerAlarm(alarmId)`
* You'll see the gas consumed by the execution
* **No external account** initiated this call—it was executed by the network's Schedule Service
This is the key difference from traditional EVM chains!
***
## Step 7: Run Tests (Optional)
You can find both types of tests in the [**hedera-code-snippets repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hss-schedule-sc-calls). You will find the following files:
* `contracts/AlarmClockSimple.t.sol`
- **Initial state**: Verifies contract deploys with `nextAlarmId` starting at 0.
- **Alarm creation**: Confirms `setAlarm` properly creates alarm structs with correct user, time, and interval values.
- **Authorization**: Ensures only the alarm owner or the contract itself can trigger alarms (non-owners are rejected).
- **One-shot enforcement**: Validates that one-shot alarms can only be triggered once and revert on subsequent attempts.
- **HBAR handling**: Verifies the contract can receive HBAR via `receive()` function for funding scheduled executions.
* `test/AlarmClockSimple.ts`
- **Deployment and setup**: Deploys the contract with 10 HBAR funding, creates
a separate funded user wallet, and validates initial state.
- **One-shot alarms**: Sets a one-shot alarm, verifies the `AlarmScheduled` event is emitted, and confirms alarm details are correctly stored.
- **Recurring alarms**: Sets a recurring alarm and validates it's configured with the recurring flag and proper interval.
- **Manual triggering**: Tests that users can manually trigger their own alarms and verifies the `AlarmTriggered` event is emitted with correct data.
- **Authorization checks**: Ensures non-owners cannot trigger someone else's alarm and that one-shot alarms cannot be triggered twice.
- **Network integration**: All tests run against Hedera testnet as the Schedule Service precompile (`0x16b`) is not available locally.
Copy these files and then run the tests:
```bash theme={null}
# This will run the Solidity unit tests via Foundry
npx hardhat test solidity
# This will run the TypeScript integration tests via Hedera testnet
# (precompiles are not available on hardhat locally and we must use the testnet)
npx hardhat test mocha
```
You can also run both the solidity and mocha tests altogether:
```bash theme={null}
npx hardhat test
```
Which should output something like:
```bash theme={null}
Running Solidity tests
contracts/AlarmClockSimple.t.sol:AlarmClockSimpleTest
✔ test_TriggerAlarmDirectly()
✔ test_RevertWhen_TriggeringOneShotAlarmTwice()
✔ test_RevertWhen_NonOwnerTriggersAlarm()
✔ test_RevertWhen_IntervalIsZero()
✔ test_RecurringAlarmCanTriggerMultipleTimes()
✔ test_ReceiveHBAR()
✔ test_InitialState()
✔ test_ContractCanTriggerAlarm()
✔ test_AlarmDataStructure()
Running Mocha tests
AlarmClockSimple
Deployer: 0xA98556A4deeB07f21f8a66093989078eF86faa30
User: 0x84b6F7e5fCD1AC75EbDFc3a24c47ea3f6915746e
Contract deployed at: 0x0B3A4Db300b471f09DbaB35bFAB8f5056F74B8bb
✔ should deploy with correct initial state (598ms)
✔ should set a one-shot alarm (6879ms)
✔ should set a recurring alarm (7809ms)
✔ should manually trigger an alarm (14435ms)
✔ should not allow non-owner to trigger someone else's alarm (8713ms)
✔ should not allow triggering a one-shot alarm twice (13419ms)
6 passing (1m)
9 passing (9 solidity)
```
***
## Conclusion
You've just built an on-chain alarm clock that demonstrates **Hedera Schedule Service's unique capability**—scheduling future smart contract calls without any off-chain infrastructure!
In this tutorial, you learned how to:
* **Interact with the Schedule Service** system contract
* **Schedule one-shot alarms** that fire once at a future time
* **Create recurring alarms** that automatically reschedule themselves
* **Monitor scheduled executions** on HashScan
* **Understand the HBAR requirements** for scheduled transactions
### Key Takeaways
* **Hedera's Schedule Service enables truly autonomous smart contracts**. No off-chain bots or keeper networks required
* **Contracts must hold HBAR** to pay for scheduled execution gas fees
* **Recurring patterns create self-sustaining on-chain automation**. Perfect for DeFi, vesting, payments, and governance
* **This capability doesn't exist on most EVM chains**. It's a fundamental difference in what's possible on Hedera
Continue exploring the tutorial about dynamic rebalancing through scheduled execution to see a real-world application of this technology—building a capacity-aware DeFi rebalancer that automatically adjusts its strategy based on network conditions.
***
## Additional Resources
Check out the resources below to learn more about Hedera's Schedule Service and HIP-1215!
* [HIP-755: Schedule Service System Contract](https://hips.hedera.com/hip/hip-755)
* [HIP-1215: Generalized Scheduled Contract Calls](https://hips.hedera.com/hip/hip-1215)
* [Full Contract and Demo Repository](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hss-schedule-sc-calls)
* [Hedera Schedule Service Documentation](/learn/core-concepts/transactions/scheduled)
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# HSS x EVM - Dynamic Rebalancing Through Scheduled Execution (Part 2)
Source: https://docs.hedera.com/evm/tutorials/hedera/hss-evm/part2-rebalancing
In [Part 1](/evm/tutorials/hedera/hss-evm/part1-schedule-calls), you learned how to schedule future smart contract calls using Hedera's Schedule Service. Now, let's build something more sophisticated: a **capacity-aware DeFi rebalancer** that automatically adjusts its scheduling strategy based on network conditions.
**What makes this advanced?**
Most blockchain automation requires off-chain infrastructure to periodically check and execute operations. Even with Hedera's Schedule Service, naively scheduling all operations at fixed intervals can create network congestion when many contracts compete for the same execution window.
This tutorial demonstrates how to build **intelligent on-chain automation** that:
* **Queries network capacity** before scheduling operations
* **Uses exponential backoff with jitter** to find optimal execution times
* **Self-sustains** by automatically rescheduling after each execution
* **Gracefully handles** network congestion and capacity constraints
* **Supports multiple scheduling methods** (`scheduleCall` and `scheduleCallWithPayer`)
* **Demonstrates one-shot immediate execution** using `executeCallOnPayerSignature`
You can take a look at the **complete code** in the
[**tutorial-hss-rebalancer-capacity-aware
repository**](https://github.com/hedera-dev/tutorial-hss-rebalancer-capacity-aware).
***
## What You'll Build
A `RebalancerCapacityAware` contract that:
1. **Starts a rebalancing loop** with configurable intervals
2. **Checks network capacity** using `hasScheduleCapacity()` before scheduling
3. **Applies intelligent retry logic** with exponential backoff and randomized jitter
4. **Supports two scheduling methods**: `scheduleCall` and `scheduleCallWithPayer`
5. **Executes rebalances automatically** via scheduled transactions
6. **Reschedules itself** after each execution, creating a self-sustaining loop
7. **Demonstrates one-shot execution** using `executeCallOnPayerSignature`
8. **Can be stopped** by canceling pending scheduled transactions
This pattern is perfect for:
* DeFi vault rebalancing
* Periodic token distributions
* Automated treasury management
* Time-based protocol operations
***
## Prerequisites
* Completion of [Part 1: Schedule Smart Contract Calls](/evm/tutorials/hedera/hss-evm/part1-schedule-calls)
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/)
* Understanding of DeFi rebalancing concepts
***
## Table of Contents
1. [Setup Project](#setup-project)
2. [Step 1: Understanding the Architecture](#step-1%3A-understanding-the-architecture)
3. [Step 2: Create the Rebalancer Contract](#step-2%3A-create-the-rebalancer-contract)
4. [Step 3: Deploy the Contract](#step-3%3A-deploy-the-contract)
5. [Step 4: Configure the Contract](#step-4%3A-configure-the-contract)
6. [Step 5: Start Rebalancing](#step-5%3A-start-rebalancing)
7. [Step 6: Monitor Rebalancing Operations](#step-6%3A-monitor-rebalancing-operations)
8. [Step 7: Stop Rebalancing](#step-7%3A-stop-rebalancing)
9. [Step 8: One-Shot Immediate Execution (Optional)](#step-8%3A-one-shot-immediate-execution-optional)
10. [Step 9: Run Tests (Optional)](#step-9%3A-run-tests-optional)
11. [Conclusion](#conclusion)
12. [Additional Resources](#additional-resources)
***
## Setup Project
If you completed Part 1, you can use the same project. Otherwise, set up a new project:
```bash theme={null}
mkdir tutorial-hss-rebalancer-capacity-aware
cd tutorial-hss-rebalancer-capacity-aware
npx hardhat --init
```
Make sure to select "**Hardhat 3 -> Typescript Hardhat Project using Mocha and Ethers.js**" and accept the default values. Hardhat will configure your project correctly and install the required dependencies.
Key differences in Hardhat 3:
* **compile → build**\
`npx hardhat compile` is now `npx hardhat build`. This is the big one. The v3 migration guide explicitly shows using the `build` task.
* **project init switch**\
v2 commonly used `npx hardhat` or `npx hardhat init` to bootstrap. In v3 it's `npx hardhat --init`.
- **keystore helper commands are new**\
v3's recommended flow includes a keystore plugin with commands like `npx hardhat keystore set HEDERA_RPC_URL` and `npx hardhat keystore set HEDERA_PRIVATE_KEY`. These weren't standard in v2.
- **Foundry-compatible Solidity tests**\
In addition to offering Javascript/Typescript integration tests, Hardhat v3 also integrates Foundry-compatible Solidity tests that allows developers to write unit tests directly in Solidity
* **Enhanced Network Management**\
v3 allows tasks to create and manage multiple network connections simultaneously which is a significant improvement over the single, fixed connection available in version 2. This provides greater flexibility for scripts and tests that interact with multiple networks.
*📚 Learn more from the official* [*Hardhat documentation*](https://hardhat.org/docs/getting-started)*.*
Before we make any changes to our Hardhat configuration file, let's set some configuration variables we will be referring to within the file later.
```bash theme={null}
# If you have already set this before, please use the --force flag
npx hardhat keystore set HEDERA_RPC_URL
```
For `HEDERA_RPC_URL`, we'll have `https://testnet.hashio.io/api`
```bash theme={null}
# If you have already set this before, please use the --force flag
npx hardhat keystore set HEDERA_PRIVATE_KEY
```
For `HEDERA_PRIVATE_KEY`, enter the **HEX Encoded Private Key for your ECDSA account** from the [Hedera Portal. ](https://portal.hedera.com/)
We also need a second private key for testing purposes:
```bash theme={null}
npx hardhat keystore set HEDERA_PRIVATE_KEY_2
```
For `HEDERA_PRIVATE_KEY_2`, enter another **HEX Encoded Private Key for a second ECDSA account**.
Now let's remove the default contracts and scripts that come with the Hardhat project:
```bash theme={null}
rm -rf contracts/* scripts/* test/*
rm -rf ignition
```
#### Install Dependencies
Next, install the required dependencies:
```bash theme={null}
npm install @hiero-ledger/hiero-contracts
```
Note that we are installing the latest code from the main branch when we install `@hiero-ledger/hiero-contracts`. This also gets installed at `@hashgraph/smart-contracts` so we can easily call these contracts from our own contract.
Configure `hardhat.config.ts`:
```typescript hardhat.config.ts theme={null}
import type { HardhatUserConfig } from "hardhat/config";
import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import { configVariable } from "hardhat/config";
const config: HardhatUserConfig = {
plugins: [hardhatToolboxMochaEthersPlugin],
solidity: {
profiles: {
default: {
version: "0.8.31"
},
production: {
version: "0.8.31",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
}
}
},
networks: {
testnet: {
type: "http",
url: configVariable("HEDERA_RPC_URL"),
accounts: [configVariable("HEDERA_PRIVATE_KEY")]
}
}
};
export default config;
```
***
## Step 1: Understanding the Architecture
Before diving into code, let's understand the key concepts that make this rebalancer capacity-aware.
### The Capacity Problem
When multiple contracts schedule transactions for the same future time:
* Network capacity for that second may be exhausted
* Subsequent scheduling attempts fail
* Operations get delayed or fail entirely
### The Solution: Capacity-Aware Scheduling
Our rebalancer uses three key Hedera features:
**1. hasScheduleCapacity(expirySecond, gasLimit)**
* Queries if a specific future second can accept a scheduled transaction
* Returns `true` if capacity is available, `false` otherwise
* Allows contracts to "probe" future availability
**2. Exponential Backoff with Jitter**
* If desired time lacks capacity, try progressively later times: +1s, +2s, +4s, +8s...
* Add random jitter to avoid "thundering herd" where all contracts retry at the same moment
* Spreads load across multiple seconds
**3. Hedera PRNG System Contract (0x169)**
* Provides pseudorandom seeds for jitter calculation
* Enables true on-chain randomness without external oracles
* Each contract gets different jitter, naturally distributing load
### Scheduling Methods
This tutorial demonstrates three different scheduling approaches:
| Method | Use Case | Payer | Loopable |
| ----------------------------- | -------------------------------- | -------- | -------- |
| `scheduleCall` | Automated recurring operations | Caller | ✅ Yes |
| `scheduleCallWithPayer` | Recurring with contract as payer | Contract | ✅ Yes |
| `executeCallOnPayerSignature` | One-shot immediate execution | Contract | ❌ No |
**Important**: `executeCallOnPayerSignature` is **not supported for
recursive/looped/cron operations** due to Hedera mainnet recursion protection
(`NO_SCHEDULING_ALLOWED_AFTER_SCHEDULED_RECURSION`). Use `scheduleCall` or
`scheduleCallWithPayer` for all automated recurring scheduling.
### How It Works Together
```
User calls: startRebalancing(60) // 60-second intervals
Contract:
1. Calculates desired time: now + 60 seconds
2. Checks: hasScheduleCapacity(desiredTime, gasLimit)?
- YES → Schedule at desiredTime
- NO → Try exponential backoff with jitter:
* Try desiredTime + 1 + random(0-1)
* Try desiredTime + 2 + random(0-2)
* Try desiredTime + 4 + random(0-4)
* Try desiredTime + 8 + random(0-8)
* ...until capacity found or max retries reached
3. Schedule rebalance() at chosen time (using selected method)
4. When rebalance() executes (automatically):
- Increment counter (or perform real DeFi operation)
- Calculate next desired time: now + 60 seconds
- Repeat capacity-aware scheduling process
Result: Self-sustaining loop that respects network capacity
```
**Why This Matters**
On traditional EVM chains, you'd need:
* Off-chain service to monitor network congestion
* Manual intervention to adjust timing
* External keeper network that understands capacity
On Hedera, the **contract itself** is capacity-aware and self-adjusting!
***
## Step 2: Create the Rebalancer Contract
Create `RebalancerCapacityAware.sol` in your `contracts` directory:
```solidity contracts/RebalancerCapacityAware.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.31;
import {
HederaScheduleService
} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-schedule-service/HederaScheduleService.sol";
import {
HederaResponseCodes
} from "@hashgraph/smart-contracts/contracts/system-contracts/HederaResponseCodes.sol";
import {
PrngSystemContract
} from "@hashgraph/smart-contracts/contracts/system-contracts/pseudo-random-number-generator/PrngSystemContract.sol";
contract RebalancerCapacityAware is HederaScheduleService {
uint256 internal constant REBALANCE_GAS_LIMIT = 2_000_000;
struct RebalanceConfig {
bool active;
uint256 intervalSeconds;
uint256 lastRebalanceTime;
uint256 rebalanceCount;
address lastScheduleAddress;
address payer;
bool usePayerScheduling;
}
RebalanceConfig public config;
event RebalancingStarted(
uint256 intervalSeconds,
uint256 firstScheduledAt,
address payer,
bool usePayerScheduling
);
event RebalanceScheduled(
uint256 chosenTime,
uint256 desiredTime,
address scheduleAddress,
string schedulingMethod
);
event RebalanceExecuted(uint256 timestamp, uint256 count);
event RebalancingStopped();
event PayerSet(address payer);
event SchedulingMethodChanged(bool usePayerScheduling);
event OneShotExecuted(address scheduleAddress, string method);
event DemoActionExecuted(address caller, uint256 value);
constructor() payable {}
receive() external payable {}
function setPayer(address _payer) external {
config.payer = _payer;
emit PayerSet(_payer);
}
function setSchedulingMethod(bool _usePayerScheduling) external {
require(!config.active, "stop rebalancing first");
config.usePayerScheduling = _usePayerScheduling;
emit SchedulingMethodChanged(_usePayerScheduling);
}
function startRebalancing(uint256 intervalSeconds) external {
require(intervalSeconds > 0, "interval must be > 0");
require(!config.active, "already active");
config.active = true;
config.intervalSeconds = intervalSeconds;
config.lastRebalanceTime = block.timestamp;
config.rebalanceCount = 0;
uint256 desiredTime = block.timestamp + intervalSeconds;
uint256 scheduledAt = _scheduleNextRebalance(desiredTime);
emit RebalancingStarted(
intervalSeconds,
scheduledAt,
config.payer,
config.usePayerScheduling
);
}
function rebalance() external {
require(config.active, "not active");
config.rebalanceCount += 1;
config.lastRebalanceTime = block.timestamp;
emit RebalanceExecuted(block.timestamp, config.rebalanceCount);
uint256 desiredTime = block.timestamp + config.intervalSeconds;
_scheduleNextRebalance(desiredTime);
}
function stopRebalancing() external {
if (config.lastScheduleAddress != address(0)) {
address scheduleAddress = config.lastScheduleAddress;
deleteSchedule(scheduleAddress);
config.lastScheduleAddress = address(0);
}
config.active = false;
emit RebalancingStopped();
}
function _scheduleNextRebalance(
uint256 desiredTime
) internal returns (uint256 chosenTime) {
chosenTime = _findAvailableSecond(desiredTime, REBALANCE_GAS_LIMIT, 8);
bytes memory callData = abi.encodeWithSelector(this.rebalance.selector);
int64 rc;
address scheduleAddress;
string memory method;
if (config.usePayerScheduling && config.payer != address(0)) {
(rc, scheduleAddress) = scheduleCallWithPayer(
address(this),
config.payer,
chosenTime,
REBALANCE_GAS_LIMIT,
0,
callData
);
method = "scheduleCallWithPayer";
} else {
(rc, scheduleAddress) = scheduleCall(
address(this),
chosenTime,
REBALANCE_GAS_LIMIT,
0,
callData
);
method = "scheduleCall";
}
require(rc == HederaResponseCodes.SUCCESS, "scheduleCall failed");
config.lastScheduleAddress = scheduleAddress;
emit RebalanceScheduled(
chosenTime,
desiredTime,
scheduleAddress,
method
);
}
function _findAvailableSecond(
uint256 expiry,
uint256 gasLimit,
uint256 maxProbes
) internal returns (uint256 second) {
if (hasScheduleCapacity(expiry, gasLimit)) {
return expiry;
}
bytes32 seed = PrngSystemContract(address(0x169)).getPseudorandomSeed();
for (uint256 i = 0; i < maxProbes; i++) {
uint256 baseDelay = 1 << i;
bytes32 hash = keccak256(abi.encodePacked(seed, i));
uint16 randomValue = uint16(uint256(hash));
uint256 jitter = uint256(randomValue) % (baseDelay + 1);
uint256 candidate = expiry + baseDelay + jitter;
if (hasScheduleCapacity(candidate, gasLimit)) {
return candidate;
}
}
revert("No capacity after maxProbes");
}
// ----------- One-shot immediate execution demo BEGIN -----------
function demoImmediateExecution(
uint256 timestamp,
bytes memory callData
) external returns (address, int64) {
require(config.payer != address(0), "set payer");
int64 rc;
address scheduleAddress;
(rc, scheduleAddress) = executeCallOnPayerSignature(
address(this),
config.payer,
timestamp,
REBALANCE_GAS_LIMIT,
0,
callData
);
emit OneShotExecuted(scheduleAddress, "executeCallOnPayerSignature");
return (scheduleAddress, rc);
}
function demoAction(uint256 value) public {
emit DemoActionExecuted(msg.sender, value);
}
// ----------- One-shot immediate execution demo END -----------
function getConfig()
external
view
returns (
bool active,
uint256 intervalSeconds,
uint256 lastRebalanceTime,
uint256 rebalanceCount,
address lastScheduleAddress,
address payer,
bool usePayerScheduling
)
{
return (
config.active,
config.intervalSeconds,
config.lastRebalanceTime,
config.rebalanceCount,
config.lastScheduleAddress,
config.payer,
config.usePayerScheduling
);
}
}
```
**How It Works**
1. **setPayer()**: Configures which address will pay for scheduled transactions (typically the contract itself)
2. **setSchedulingMethod()**: Switches between `scheduleCall` (false) and `scheduleCallWithPayer` (true)
3. **startRebalancing()**: Initializes the loop and schedules the first rebalance using capacity-aware logic
4. **\_findAvailableSecond()**: The core capacity-awareness algorithm:
* First checks if desired time has capacity
* If not, tries exponentially increasing delays: +1s, +2s, +4s, +8s...
* Adds random jitter (0 to baseDelay) to each attempt
* Uses Hedera's PRNG for true on-chain randomness
5. **rebalance()**: Executed automatically by scheduled transactions:
* Increments counter (in real DeFi, would perform actual rebalancing)
* Schedules next execution using capacity-aware logic
* Creates self-sustaining loop
6. **stopRebalancing()**: Cancels pending schedule and marks loop inactive
7. **demoImmediateExecution()**: Demonstrates one-shot execution using `executeCallOnPayerSignature`
8. **HBAR Requirement**: Contract must hold HBAR to pay for all scheduled executions
Build the contract:
```bash theme={null}
npx hardhat build
```
***
## Step 3: Deploy the Contract
Create `deploy. ts` in the `scripts` directory:
```typescript scripts/deploy.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying with account:", deployer.address);
const RebalancerCapacityAware = await ethers.getContractFactory(
"RebalancerCapacityAware",
deployer
);
const contract = await RebalancerCapacityAware.deploy({
value: ethers.parseEther("20")
});
await contract.waitForDeployment();
const contractAddress = await contract.getAddress();
console.log("RebalancerCapacityAware deployed at:", contractAddress);
const balance = await ethers.provider.getBalance(contractAddress);
console.log("Contract HBAR balance:", ethers.formatEther(balance), "HBAR");
console.log("📝 Save this address for the next steps!");
console.log(`export CONTRACT_ADDRESS=${contractAddress}`);
}
main().catch(console.error);
```
Deploy:
```bash theme={null}
npx hardhat run scripts/deploy.ts --network testnet
```
Copy the deployed contract address and set it as an environment variable for
the next steps.
Expected output:
```bash theme={null}
Deploying with account: 0xe3c0743e01bE37c42B2ee57BD1aA30c9c266c0Ae
RebalancerCapacityAware deployed at: 0xFAd66DAA323354799ADF0aF2a019Ce39211bA27F
Contract HBAR balance: 20.0 HBAR
📝 Save this address for the next steps!
export CONTRACT_ADDRESS=0xFAd66DAA323354799ADF0aF2a019Ce39211bA27F
```
Set the contract address as an environment variable:
```bash theme={null}
export CONTRACT_ADDRESS=0xYOURDEPLOYEDADDRESS
```
In order to decode events emitted from the contract, the contract must be verified.
```bash theme={null}
./generate_hedera_sc_metadata.sh RebalancerCapacityAware
```
You can then upload the `verify-bundles/RebalancerCapacityAware/metadata.json` file to Hashscan to verify this contract.
***
## Step 4: Configure the Contract
Before starting the rebalancing loop, you need to configure the payer and scheduling method.
### Set the Contract as Payer
Create `setPayer.ts` in the `scripts` directory:
```typescript scripts/setPayer. ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const contractAddress =
process.env.CONTRACT_ADDRESS || "";
if (!contractAddress) throw new Error("Set CONTRACT_ADDRESS env var!");
const [signer] = await ethers.getSigners();
const rebalancer = await ethers.getContractAt(
"RebalancerCapacityAware",
contractAddress,
signer
);
const tx = await rebalancer.setPayer(contractAddress);
await tx.wait();
console.log("Payer set to contract address:", contractAddress);
}
main().catch(console.error);
```
Run the script:
```bash theme={null}
npx hardhat run scripts/setPayer.ts --network testnet
```
### Choose a Scheduling Method
You have two options for scheduling. Choose one:
**Option 1: scheduleCall (default)**
Create `setSchedulingMethodScheduleCall.ts`:
```typescript scripts/setSchedulingMethodScheduleCall.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const contractAddress =
process.env.CONTRACT_ADDRESS || "";
if (!contractAddress) throw new Error("Set CONTRACT_ADDRESS env var!");
const [signer] = await ethers.getSigners();
const rebalancer = await ethers.getContractAt(
"RebalancerCapacityAware",
contractAddress,
signer
);
const tx = await rebalancer.setSchedulingMethod(false);
await tx.wait();
console.log("Scheduling method set to: scheduleCall");
}
main().catch(console.error);
```
```bash theme={null}
npx hardhat run scripts/setSchedulingMethodScheduleCall.ts --network testnet
```
**Option 2: scheduleCallWithPayer (contract as payer)**
Create `setSchedulingMethodScheduleCallWithPayer.ts`:
```typescript scripts/setSchedulingMethodScheduleCallWithPayer. ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const contractAddress =
process.env.CONTRACT_ADDRESS || "";
if (!contractAddress) throw new Error("Set CONTRACT_ADDRESS env var!");
const [signer] = await ethers.getSigners();
const rebalancer = await ethers.getContractAt(
"RebalancerCapacityAware",
contractAddress,
signer
);
const tx = await rebalancer.setSchedulingMethod(true);
await tx.wait();
console.log(
"Scheduling method set to: scheduleCallWithPayer (contract as payer)"
);
}
main().catch(console.error);
```
```bash theme={null}
npx hardhat run scripts/setSchedulingMethodScheduleCallWithPayer.ts --network testnet
```
***
## Step 5: Start Rebalancing
Create `startRebalancing.ts` in the `scripts` directory:
```typescript scripts/startRebalancing.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const contractAddress =
process.env.CONTRACT_ADDRESS || "";
if (!contractAddress) throw new Error("Set CONTRACT_ADDRESS env var!");
const [signer] = await ethers.getSigners();
const rebalancer = await ethers.getContractAt(
"RebalancerCapacityAware",
contractAddress,
signer
);
const intervalSeconds = 15; // set your demo interval here
const tx = await rebalancer.startRebalancing(intervalSeconds);
await tx.wait();
console.log(`Rebalancing started with interval: ${intervalSeconds} seconds`);
}
main().catch(console.error);
```
Run the script:
```bash theme={null}
npx hardhat run scripts/startRebalancing.ts --network testnet
```
Expected output:
```bash theme={null}
Rebalancing started with interval: 15 seconds
```
**What's Happening**
1. `startRebalancing(15)` calculates desired time: `now + 15 seconds`
2. Contract checks: `hasScheduleCapacity(desiredTime, 2_000_000)?`
3. If capacity available → schedules at desired time
4. If not → applies exponential backoff with jitter to find available slot
5. Emits `RebalancingStarted` with actual scheduled time and scheduling method
6. After \~15 seconds, network automatically executes `rebalance()`
7. `rebalance()` schedules next execution → creates self-sustaining loop
***
## Step 6: Monitor Rebalancing Operations
Create `monitorRebalancing. ts` to observe the rebalancing loop:
```typescript scripts/monitorRebalancing.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const contractAddress =
process.env.CONTRACT_ADDRESS || "";
if (!contractAddress) throw new Error("Set CONTRACT_ADDRESS env var!");
const rebalancer = await ethers.getContractAt(
"RebalancerCapacityAware",
contractAddress
);
console.log(
"Monitoring Rebalancer:",
contractAddress,
"\nPress Ctrl+C to stop\n"
);
async function display() {
const config = await rebalancer.getConfig();
const balance = await ethers.provider.getBalance(contractAddress);
console.log(`[${new Date().toISOString()}]`);
console.log(" Active:", config.active);
console.log(" Rebalance Count:", config.rebalanceCount.toString());
console.log(
" Last Rebalance:",
config.lastRebalanceTime > 0
? new Date(Number(config.lastRebalanceTime) * 1000).toISOString()
: "Never"
);
console.log(" Interval:", config.intervalSeconds.toString(), "seconds");
console.log(" Payer:", config.payer);
console.log(
" Scheduling Method:",
config.usePayerScheduling ? "scheduleCallWithPayer" : "scheduleCall"
);
console.log(" Contract Balance:", ethers.formatEther(balance), "HBAR");
console.log("---");
}
await display();
setInterval(display, 5000);
}
main().catch(console.error);
```
Run the monitoring script:
```bash theme={null}
npx hardhat run scripts/monitorRebalancing.ts --network testnet
```
You'll see output like:
```bash theme={null}
Monitoring Rebalancer: 0xFAd66DAA323354799ADF0aF2a019Ce39211bA27F
Press Ctrl+C to stop
[2025-12-22T21:09:36.100Z]
Active: true
Rebalance Count: 4
Last Rebalance: 2025-12-22T21:09:25.000Z
Interval: 15 seconds
Payer: 0xFAd66DAA323354799ADF0aF2a019Ce39211bA27F
Scheduling Method: scheduleCall
Contract Balance: 15.2 HBAR
---
[2025-12-22T21:09:41.391Z]
Active: true
Rebalance Count: 4
Last Rebalance: 2025-12-22T21:09:25.000Z
Interval: 15 seconds
Payer: 0xFAd66DAA323354799ADF0aF2a019Ce39211bA27F
Scheduling Method: scheduleCall
Contract Balance: 15.2 HBAR
---
```
Note that the `Rebalance Count` increments every \~15 seconds as scheduled transactions execute automatically. When the contract runs out of HBAR, scheduling will fail, and the count will stop increasing however the state remains `Active: true` until you explicitly stop rebalancing.
### Check Contract Config
You can also create a simple script to check the current configuration:
```typescript scripts/getConfig.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const contractAddress =
process.env.CONTRACT_ADDRESS || "";
if (!contractAddress) throw new Error("Set CONTRACT_ADDRESS env var!");
const rebalancer = await ethers.getContractAt(
"RebalancerCapacityAware",
contractAddress
);
const config = await rebalancer.getConfig();
const balance = await ethers.provider.getBalance(contractAddress);
console.log("Config for contract:", contractAddress);
console.log({
active: config.active,
intervalSeconds: config.intervalSeconds.toString(),
lastRebalanceTime: config.lastRebalanceTime.toString(),
rebalanceCount: config.rebalanceCount.toString(),
lastScheduleAddress: config.lastScheduleAddress,
payer: config.payer,
usePayerScheduling: config.usePayerScheduling,
contractBalance: ethers.formatEther(balance) + " HBAR"
});
}
main().catch(console.error);
```
```bash theme={null}
npx hardhat run scripts/getConfig.ts --network testnet
```
With output like:
```bash theme={null}
Config for contract: 0xFAd66DAA323354799ADF0aF2a019Ce39211bA27F
{
active: true,
intervalSeconds: '15',
lastRebalanceTime: '1766437807',
rebalanceCount: '7',
lastScheduleAddress: '0x00000000000000000000000000000000007294a6',
payer: '0xFAd66DAA323354799ADF0aF2a019Ce39211bA27F',
usePayerScheduling: false,
contractBalance: '11.6 HBAR'
}
```
### View Events on HashScan
Navigate to your contract's events page to see:
**RebalanceScheduled Events:**
```
RebalanceScheduled(
chosenTime: 1734087330,
desiredTime: 1734087330,
scheduleAddress: 0x000000000000000000000000000000000068d3ef,
schedulingMethod: "scheduleCall"
)
```
* Shows when capacity-aware scheduling found an available slot
* `chosenTime === desiredTime` means ideal time had capacity
* `chosenTime > desiredTime` means backoff was needed
* `schedulingMethod` shows which method was used
**RebalanceExecuted Events:**
```
RebalanceExecuted(
timestamp: 1734087330,
count: 1
)
```
* Confirms automatic execution by the network
* Tracks total rebalance operations performed
View live events at: `https://hashscan.io/testnet/contract/$CONTRACT_ADDRESS/events`
***
## Step 7: Stop Rebalancing
Create `stopRebalancing.ts` to halt the loop:
```typescript scripts/stopRebalancing.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const contractAddress =
process.env.CONTRACT_ADDRESS || "";
if (!contractAddress) throw new Error("Set CONTRACT_ADDRESS env var!");
const [signer] = await ethers.getSigners();
const rebalancer = await ethers.getContractAt(
"RebalancerCapacityAware",
contractAddress,
signer
);
const tx = await rebalancer.stopRebalancing();
await tx.wait();
console.log("Rebalancing stopped and schedule deleted.");
}
main().catch(console.error);
```
Run:
```bash theme={null}
npx hardhat run scripts/stopRebalancing.ts --network testnet
```
Expected output:
```bash theme={null}
Rebalancing stopped and schedule deleted.
```
**What Happened**
1. `stopRebalancing()` called `deleteSchedule(lastScheduleAddress)`
2. Pending scheduled transaction was canceled (best effort)
3. `config.active` set to `false`
4. Even if a scheduled `rebalance()` executes, the `require(config.active)` check prevents further scheduling
5. Loop is fully stopped
***
## Step 8: One-Shot Immediate Execution (Optional)
This demo shows how to use `executeCallOnPayerSignature` for a single, immediate function call. This method is **not loopable** due to Hedera's recursion protection.
Create `demoImmediateExecution.ts`:
```typescript scripts/demoImmediateExecution.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const contractAddress =
process.env.CONTRACT_ADDRESS || "";
if (!contractAddress) throw new Error("Set CONTRACT_ADDRESS env var!");
const [signer] = await ethers.getSigners();
const rebalancer = await ethers.getContractAt(
"RebalancerCapacityAware",
contractAddress,
signer
);
const timestamp = Math.floor(Date.now() / 1000) + 60;
const callData = rebalancer.interface.encodeFunctionData("demoAction", [
12345
]);
const tx = await rebalancer.demoImmediateExecution(timestamp, callData);
const receipt = await tx.wait();
console.log(
"One-shot executeCallOnPayerSignature scheduled for",
new Date(timestamp * 1000).toISOString()
);
if (receipt && Array.isArray(receipt.logs)) {
receipt.logs.forEach((log: any) => {
try {
const parsed = rebalancer.interface.parseLog(log);
if (parsed && parsed.name === "OneShotExecuted") {
console.log("Schedule Address:", parsed.args.scheduleAddress);
console.log("Method:", parsed.args.method);
}
if (parsed && parsed.name === "DemoActionExecuted") {
console.log(
"DemoActionExecuted: caller",
parsed.args.caller,
"value",
parsed.args.value.toString()
);
}
} catch (_e) {}
});
}
}
main().catch(console.error);
```
Make sure the payer is set first, then run:
```bash theme={null}
npx hardhat run scripts/setPayer.ts --network testnet
npx hardhat run scripts/demoImmediateExecution.ts --network testnet
```
With output like:
```bash theme={null}
One-shot executeCallOnPayerSignature scheduled for 2025-12-22T21:13:53.000Z
Schedule Address: 0x00000000000000000000000000000000007294b1
Method: executeCallOnPayerSignature
```
**You should see a `DemoActionExecuted` event emitted.**
***
## Step 9: Run Tests (Optional)
You can find both types of tests in the [tutorial-hss-rebalancer-capacity-aware](https://github.com/hedera-dev/tutorial-hss-rebalancer-capacity-aware) repository. You will find the following files:
The repository includes both Solidity unit tests and TypeScript integration tests.
### Solidity Unit Tests (`contracts/RebalancerCapacityAware.t.sol`)
These tests validate:
* **Initial state**: Verifies contract deploys with inactive configuration
* **Payer configuration**: Tests setting and changing the payer address
* **Scheduling method switching**: Verifies switching between `scheduleCall` and `scheduleCallWithPayer`
* **Start/stop logic**: Confirms only inactive rebalancers can be started and active ones can be stopped
* **Configuration validation**: Ensures interval must be greater than zero
* **HBAR handling**: Verifies contract can receive HBAR for funding scheduled operations
* **State management**: Tests that rebalance count and timestamps are properly maintained
### TypeScript Integration Tests (`test/RebalancerCapacityAware.ts`)
These tests run against Hedera testnet and validate:
* **Deployment and funding**: Deploys with substantial HBAR balance and validates initial state
* **scheduleCall method**: Tests automated recurring rebalancing with `scheduleCall`
* **scheduleCallWithPayer method**: Tests automated recurring rebalancing with `scheduleCallWithPayer` (contract as payer)
* **executeCallOnPayerSignature**: Demonstrates one-shot immediate execution
* **deleteSchedule**: Verifies schedule deletion via `stopRebalancing`
* **Capacity awareness**: Tests that the contract successfully finds available time slots using `hasScheduleCapacity`
* **Input validation**: Tests error handling for invalid inputs
* **Scheduling method switching**: Verifies switching between scheduling methods
Run the tests:
```bash theme={null}
# Solidity unit tests
npx hardhat test solidity
# TypeScript integration tests against testnet
npx hardhat test mocha
```
You can also run both the solidity and mocha tests altogether:
```bash theme={null}
npx hardhat test
```
Which should output something like:
```bash theme={null}
Running Solidity tests
contracts/RebalancerCapacityAware.t.sol:RebalancerCapacityAwareTest
✔ test_SwitchBetweenSchedulingMethods()
✔ test_StopRebalancing()
✔ test_SetSchedulingMethod()
✔ test_SetPayer()
✔ test_RevertWhen_SetSchedulingMethodWhileActive()
✔ test_RevertWhen_RebalanceNotActive()
✔ test_RevertWhen_IntervalIsZero()
✔ test_RevertWhen_AlreadyActive()
✔ test_ReceiveHBAR()
✔ test_PayerConfiguration()
✔ test_MultipleStartStopCycles()
✔ test_ManualRebalanceIncrementsCount()
✔ test_InitialState()
✔ test_ConfigUpdatesAfterStart()
✔ test_ConfigPersistsAcrossRebalances()
Running Mocha tests
RebalancerCapacityAware - Comprehensive HSS Demo
Deployer: 0xe3c0743e01bE37c42B2ee57BD1aA30c9c266c0Ae
User: 0xe3c0743e01bE37c42B2ee57BD1aA30c9c266c0Ae
Contract deployed at: 0x2FA345Ad7609bc18d935e48D50F70dB8a1021Fcd
✔ should have correct initial state (223ms)
✔ should automate recurring rebalancing with scheduleCall (28027ms)
✔ should automate recurring rebalancing with scheduleCallWithPayer (contract as payer) (28267ms)
✔ should demonstrate executeCallOnPayerSignature as a one-shot (15515ms)
✔ should demonstrate deleteSchedule via stopRebalancing (26809ms)
✔ should demonstrate hasScheduleCapacity via capacity-aware scheduling (30746ms)
✔ should validate input and state transitions (28158ms)
✔ should support switching between scheduling methods (22519ms)
✔ should support payer configuration (6743ms)
9 passing (3m)
15 passing (15 solidity)
```
***
## Conclusion
You've built a sophisticated **capacity-aware DeFi rebalancer** that demonstrates advanced patterns with Hedera's Schedule Service!
In this tutorial, you learned how to:
* **Query network capacity** using `hasScheduleCapacity()`
* **Implement exponential backoff** with randomized jitter
* **Use Hedera's PRNG** for true on-chain randomness
* **Build self-sustaining loops** that automatically reschedule
* **Choose between scheduling methods**: `scheduleCall` vs `scheduleCallWithPayer`
* **Handle one-shot execution** using `executeCallOnPayerSignature`
* **Handle network congestion** gracefully
* **Cancel scheduled operations** when needed
### Key Takeaways
* **Capacity-aware scheduling prevents network congestion**. Contracts cooperate with the network's throttling model
* **Exponential backoff + jitter distributes load**. Avoids "thundering herd" where all contracts compete for the same slot
* **True on-chain randomness via PRNG**. No external oracles needed for jitter calculation
* **Multiple scheduling methods for different use cases**. Use `scheduleCall` or `scheduleCallWithPayer` for recurring operations, `executeCallOnPayerSignature` for one-shots
* **This level of network awareness doesn't exist on most EVM chains**. Hedera enables truly intelligent on-chain automation
### Real-World Applications
This pattern can be extended to:
* **DeFi Vaults**: Automatic portfolio rebalancing based on price oracles
* **Liquidity Management**: Periodic adjustment of AMM positions
* **Treasury Operations**: Scheduled fund distributions or buybacks
* **Yield Optimization**: Regular harvesting and compounding of rewards
* **DAO Governance**: Time-delayed execution of approved proposals
All without relying on off-chain infrastructure or keeper networks!
***
## Additional Resources
* [HIP-755: Schedule Service System Contract](https://hips.hedera.com/hip/hip-755)
* [HIP-1215: Generalized Scheduled Contract Calls](https://hips.hedera.com/hip/hip-1215)
* [HIP-351: PRNG System Contract](https://hips.hedera.com/hip/hip-351)
* [Full Contract and Demo Repository](https://github.com/hedera-dev/tutorial-hss-rebalancer-capacity-aware)
* [Part 1: Schedule Smart Contract Calls](/evm/tutorials/hedera/hss-evm/part1-schedule-calls)
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# HTS x EVM - How to Mint NFTs (Part 1)
Source: https://docs.hedera.com/evm/tutorials/hedera/hts-evm/part1-mint-nfts
On Hedera, we can create, mint, burn and transfer non-fungible tokens(NFTs) without deploying or dealing with any smart contracts. We can do this using only the Hedera Token Service(HTS) and official SDKs available in varrious languages such as Javascript, Rust, Go, Python, Java, etc. If you want to learn how to perform these operations using the SDK, refer to [this documentation](/native/tutorials/tokens/hts-part1-mint).
However, it is also possible to create a token on Hedera using a **smart contract** and still benefit from the native Hedera Token Service. However, the contract needs to interact with the [HTS System Contract](/evm/hedera-services/system-contracts), which provides Hedera-specific token operations. By combining **HTS** and **Solidity**, you:
* Get all the performance, cost-efficiency, and security of native HTS tokens.
* Can embed custom, decentralized logic in your contract for advanced use cases.
In this tutorial, you’ll:
* **Create** an NFT collection with a royalty fee schedule.
* **Mint** new NFTs with metadata pointing to IPFS.
* **Burn** an existing NFT.
You can take a look at the **complete code** in the [**Hedera-Code-Snippets
repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hts-evm-mint-nfts).
***
## Prerequisites
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/).
* Basic understanding of Solidity.
***
## Table of Contents
1. [Setup Project](#setup-project)
2. [Step 1: Configure Hardhat](#step-1%3A-configure-hardhat)
3. [Step 2: Creating an NFT collection via HTS(similar to ERC721)](#step-2%3A-creating-an-nft-collection-via-hts-similar-to-erc721)
4. [Step 3: Deploy HTS NFT Smart Contract](#step-3%3A-deploy-your-hts-nft-smart-contract)
5. [Step 4: Minting an HTS NFT](#step-4%3A-minting-an-hts-nft)
6. [Step 5: Burning an HTS NFT](#step-5%3A-burning-an-hts-nft)
7. [Step 6: Run tests](#step-6%3A-run-tests-optional)
8. [Conclusion](#conclusion)
9. [Additional Resources](#additional-resources)
***
## Setup Project
Set up your project by initializing the hardhat project.
```bash theme={null}
mkdir hts-evm-mint-nfts
cd hts-evm-mint-nfts
npx hardhat --init
```
Make sure to select "**Hardhat 3 -> Typescript Hardhat Project using Mocha and Ethers.js"** and accept the default values. Hardhat will configure your project correctly and install the required dependencies.
Key differences in Hardhat 3:
* **compile → build**\
`npx hardhat compile` is now `npx hardhat build`. This is the big one. The v3 migration guide explicitly shows using the `build` task.
* **project init switch**\
v2 commonly used `npx hardhat` or `npx hardhat init` to bootstrap. In v3 it’s `npx hardhat --init`.
- **keystore helper commands are new**\
v3’s recommended flow includes a keystore plugin with commands like `npx hardhat keystore set HEDERA_RPC_URL` and `npx hardhat keystore set HEDERA_PRIVATE_KEY`. These weren’t standard in v2.
- **Foundry-compatiable Solidity tests**\
In addition to offering Javascript/Typescript integration tests, Hardhat v3 also integrates Foundry-compatible Solidity tests that allows developers to write unit tests directly in Solidity
* **Enhanced Network Management**\
v3 allows tasks to create and manage multiple network connections simultaneously which is a significant improvement over the single, fixed connection available in version 2. This provides greater flexibility for scripts and tests that interact with multiple networks.
*📚 Learn more from the official* [*Hardhat documentation*](https://hardhat.org/docs/getting-started)*.*
Before we make any changes to our Hardhat configuration file, let's set some configuration variables we will be referring to within the file later.
```bash theme={null}
# If you have already set this before, please use the --force flag
npx hardhat keystore set HEDERA_RPC_URL
```
For `HEDERA_RPC_URL`, we'll have `https://testnet.hashio.io/api`
```bash theme={null}
# If you have already set this before, please use the --force flag
npx hardhat keystore set HEDERA_PRIVATE_KEY
```
For `HEDERA_PRIVATE_KEY`, enter the **HEX Encoded Private Key for your ECDSA account** from the [Hedera Portal.](https://portal.hedera.com/)
#### Install Dependencies
Next, install the required dependencies:
```bash theme={null}
npm install @openzeppelin/contracts
npm install @hiero-ledger/hiero-contracts
```
Note that we are installing the latest code from the main branch when we install `@hiero-ledger/hiero-contracts` . This also gets installed at `@hashgraph/smart-contracts` so we can easily call these contracts from our own contract.
***
## Step 1: Configure Hardhat
Update your `hardhat.config.ts`file in the root directory of your project. This file contains the network settings so Hardhat knows how to interact with the Hedera Testnet.
```typescript hardhat.config.ts theme={null}
import type { HardhatUserConfig } from "hardhat/config";
import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import { configVariable } from "hardhat/config";
const config: HardhatUserConfig = {
plugins: [hardhatToolboxMochaEthersPlugin],
solidity: {
profiles: {
default: {
version: "0.8.28",
},
production: {
version: "0.8.28",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
},
},
networks: {
testnet: {
type: "http",
url: configVariable("HEDERA_RPC_URL"),
accounts: [configVariable("HEDERA_PRIVATE_KEY")],
},
},
};
export default config;
```
We won't be using `ignition` and we will be removing the default contracts that comes with hardhat default project so we will remove all the unnecessary directories and files first:
```bash theme={null}
rm -rf contracts/* scripts/* test/*
rm -rf ignition
```
***
## Step 2: Creating an NFT collection via HTS(similar to ERC721)
Create a new Solidity file (`MyHTSToken.sol`) in our `contracts` directory:
```solidity contracts/MyHTSToken.sol theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
// Admin/ownership like the OZ example
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
// Read/transfer via ERC721 facade exposed at the HTS token EVM address
import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
// Hedera HTS system contracts (as in your setup)
// Hedera HTS system contracts (v1, NOT v2)
import {HederaTokenService} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/HederaTokenService.sol";
import {IHederaTokenService} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/IHederaTokenService.sol";
import {HederaResponseCodes} from "@hashgraph/smart-contracts/contracts/system-contracts/HederaResponseCodes.sol";
import {KeyHelper} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/KeyHelper.sol";
/**
* HTS-backed ERC721-like collection:
* - Creates the HTS NFT collection in the constructor (like deploying an ERC721).
* - SUPPLY key = this contract (mint/burn only via contract).
* - ADMIN key = this contract (admin updates only via contract).
* - Holders use the token’s ERC721 facade directly (SDK or EVM).
* - Royalty: 10% with 1 HBAR fallback to initialOwner.
*/
contract MyHTSToken is HederaTokenService, KeyHelper, Ownable {
// Underlying HTS NFT token EVM address (set during initialize. This is the "ERC721-like" token)
address public tokenAddress;
// Cosmetic copies for convenience (optional)
string public name;
string public symbol;
// Small non-empty default metadata for simple mints (<=100 bytes as per HTS limit)
bytes private constant DEFAULT_METADATA = hex"01";
uint256 private constant INT64_MAX = 0x7fffffffffffffff;
event NFTCollectionCreated(address indexed token);
event NFTMinted(
address indexed to,
uint256 indexed tokenId,
int64 newTotalSupply
);
event NFTBurned(uint256 indexed tokenId, int64 newTotalSupply);
event HBARReceived(address indexed from, uint256 amount);
event HBARFallback(address sender, uint256 amount, bytes data);
event HBARWithdrawn(address indexed to, uint256 amount);
/**
* Constructor sets ownership.
* Actual HTS token creation happens in createNFTCollection().
*/
constructor() Ownable(msg.sender) {}
/**
* Creates the HTS NFT collection with custom fees.
* Can be called exactly once by the owner after deployment.
*
* @param _name Token/collection name
* @param _symbol Token/collection symbol
*/
function createNFTCollection(
string memory _name,
string memory _symbol
) external payable onlyOwner {
require(tokenAddress == address(0), "Already initialized");
name = _name;
symbol = _symbol;
// Build token definition
IHederaTokenService.HederaToken memory token;
token.name = name;
token.symbol = symbol;
token.treasury = address(this);
token.memo = "";
// Keys: SUPPLY + ADMIN -> contractId
IHederaTokenService.TokenKey[]
memory keys = new IHederaTokenService.TokenKey[](2);
keys[0] = getSingleKey(
KeyType.SUPPLY,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[1] = getSingleKey(
KeyType.ADMIN,
KeyValueType.CONTRACT_ID,
address(this)
);
token.tokenKeys = keys;
// Royalty: 10% with 1 HBAR fallback to the owner
IHederaTokenService.RoyaltyFee[]
memory royaltyFees = new IHederaTokenService.RoyaltyFee[](1);
royaltyFees[0] = IHederaTokenService.RoyaltyFee({
numerator: 1,
denominator: 10,
amount: 100_000_000, // 1 HBAR in tinybars
tokenId: address(0),
useHbarsForPayment: true,
feeCollector: owner()
});
IHederaTokenService.FixedFee[]
memory fixedFees = new IHederaTokenService.FixedFee[](0);
(int rc, address created) = createNonFungibleTokenWithCustomFees(
token,
fixedFees,
royaltyFees
);
require(rc == HederaResponseCodes.SUCCESS, "HTS: create NFT failed");
tokenAddress = created;
emit NFTCollectionCreated(created);
}
// ---------------------------------------------------------------------------
// ERC721-like minting (admin via Ownable + SUPPLY key on contract)
// ---------------------------------------------------------------------------
// Minimal API parity: mintNFT(to) onlyOwner -> returns new tokenId (serial)
function mintNFT(address to) public onlyOwner returns (uint256) {
return _mintAndSend(to, DEFAULT_METADATA);
}
// Optional overload with custom metadata (<= 100 bytes)
function mintNFT(
address to,
bytes memory metadata
) public onlyOwner returns (uint256) {
require(metadata.length <= 100, "HTS: metadata >100 bytes");
return _mintAndSend(to, metadata);
}
function _mintAndSend(
address to,
bytes memory metadata
) internal returns (uint256 tokenId) {
require(tokenAddress != address(0), "HTS: not created");
// 1) Mint to treasury (this contract)
bytes[] memory arr = new bytes[](1);
arr[0] = metadata;
(int rc, int64 newTotalSupply, int64[] memory serials) = mintToken(
tokenAddress,
0,
arr
);
require(
rc == HederaResponseCodes.SUCCESS && serials.length == 1,
"HTS: mint failed"
);
// 2) Transfer from treasury -> recipient via ERC721 facade
uint256 serial = uint256(uint64(serials[0]));
// Recipient must be associated (or have auto-association available)
IERC721(tokenAddress).transferFrom(address(this), to, serial);
emit NFTMinted(to, serial, newTotalSupply);
return serial;
}
// ---------------------------------------------------------------------------
// ERC721Burnable-like flow for holders
// ---------------------------------------------------------------------------
// Holder-initiated burn:
// - User approves this contract for tokenId (approve or setApprovalForAll)
// - Calls burn(tokenId); contract pulls to treasury and burns via HTS
function burnNFT(uint256 tokenId) external {
require(tokenAddress != address(0), "HTS: not created");
address owner_ = IERC721(tokenAddress).ownerOf(tokenId);
// Match ERC721Burnable semantics: only the token owner or an approved operator may trigger burn
require(
msg.sender == owner_ ||
IERC721(tokenAddress).getApproved(tokenId) == msg.sender ||
IERC721(tokenAddress).isApprovedForAll(owner_, msg.sender),
"caller not owner nor approved"
);
// If not already in treasury, ensure this contract is approved to pull the token and then pull it
if (owner_ != address(this)) {
bool contractApproved = IERC721(tokenAddress).getApproved(
tokenId
) ==
address(this) ||
IERC721(tokenAddress).isApprovedForAll(owner_, address(this));
require(contractApproved, "contract not approved to transfer");
IERC721(tokenAddress).transferFrom(owner_, address(this), tokenId);
}
// Burn via HTS (requires token to be in treasury)
int64[] memory serials = new int64[](1);
serials[0] = _toI64(tokenId);
(int rc, int64 newTotalSupply) = burnToken(tokenAddress, 0, serials);
require(rc == HederaResponseCodes.SUCCESS, "HTS: burn failed");
emit NFTBurned(tokenId, newTotalSupply);
}
// ---------------------------------------------------------------------------
// HBAR handling
// ---------------------------------------------------------------------------
// Accept HBAR
receive() external payable {
emit HBARReceived(msg.sender, msg.value);
}
fallback() external payable {
emit HBARFallback(msg.sender, msg.value, msg.data);
}
function withdrawHBAR() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No HBAR to withdraw");
(bool success, ) = owner().call{value: balance}("");
require(success, "Failed to withdraw HBAR");
emit HBARWithdrawn(owner(), balance);
}
// --------------------- internal helpers ---------------------
function _toI64(uint256 x) internal pure returns (int64) {
require(x <= INT64_MAX, "cast: > int64.max");
return int64(uint64(x));
}
}
```
**How It Works**
1. **Royalty fee**: Sets a network-enforced 10% royalty with a 1 HBAR fallback (to initialOwner) via createNonFungibleTokenWithCustomFees.
2. **Token creation**: Builds the HederaToken (name, symbol, treasury=this contract) and creates the NFT collection at deployment; tokenAddress is stored immutably.
3. **Keys and security**: SUPPLY and ADMIN keys are set to the contractId, so mint/burn and any admin updates can only occur through this contract (not directly via SDK by EOAs).
4. **ERC721 facade**: Dapps/wallets interact with the token like a standard ERC721 using IERC721(tokenAddress) for transfers/approvals; ownerOf and balanceOf are true view functions.
5. **Minting (safeMint)**: Only owner can mint; each mint creates a serial to the treasury, then transfers it to the recipient via ERC721 transferFrom. Recipient must be associated (or have auto-association) or the transfer will revert.
6. **Burning (ERC721Burnable-like)**: Owner or approved operator calls burn(tokenId). If not already in treasury, the contract (when approved) pulls the token from the owner, then burns it via HTS.
7. **HBAR handling**: Contract can receive HBAR (receive/fallback) and the owner can withdraw any balance with withdrawHBAR().
8. **HTS nuances**: NFT metadata is limited to 100 bytes per serial; token IDs are HTS serials (start at 1); association is required on Hedera for receiving NFTs.
Hedera Native Tokens(created via HTS) are highly interoperable with their
corresponding ERC Contracts. Hedera Accounts are able to transfer native NFTs
as though they are ERC-721 Smart Contracts!
Let's build this contract by running:
```bash theme={null}
npx hardhat build
```
This command will generate the smart contract artifacts, including the [ABI](/evm/development/compiling). We are now ready to deploy the smart contract.
***
## Step 3: Deploy Your HTS NFT Smart Contract
Create a deployment script (`deploy.ts`) in `scripts` directory:
```typescript scripts/deploy.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contract with the account:", deployer.address);
// 1) Deploy the wrapper contract
// The deployer will also be the owner of our NFT contract
const MyHTSToken = await ethers.getContractFactory("MyHTSToken", deployer);
const contract = await MyHTSToken.deploy();
await contract.waitForDeployment();
// 2) Create the HTS NFT collection by calling createNFTCollection()
// NOTE: createNFTCollection() must be payable to accept this value.
const NAME = "MyHTSTokenNFTCollection";
const SYMBOL = "MHT";
const HBAR_TO_SEND = "15"; // HBAR to send with createNFTCollection()
console.log(
`Calling createNFTCollection() with ${HBAR_TO_SEND} HBAR to create the HTS collection...`
);
const tx = await contract.createNFTCollection(NAME, SYMBOL, {
gasLimit: 250_000,
value: ethers.parseEther(HBAR_TO_SEND),
});
await tx.wait();
console.log("createNFTCollection() tx hash:", tx.hash);
// 3) Read the created HTS token address
const contractAddress = await contract.getAddress();
console.log("MyHTSToken contract deployed at:", contractAddress);
const tokenAddress = await contract.tokenAddress();
console.log(
"Underlying HTS NFT Collection (ERC721 facade) address:",
tokenAddress
);
}
main().catch(console.error);
```
In this script, we first retrieve your account (the deployer) using Ethers.js. This account will own the deployed smart contract. Next, we use this account to deploy the contract by calling `MyHTSToken.deploy()`.
**Note**
For most HTS [System Smart Contract](/evm/hedera-services/system-contracts) calls, an HBAR value **is not** required to be sent in the contract call; the gas fee will cover it. However, for expensive transactions, like [Create HTS NFT Collection](#step-3%3A-deploy-your-hts-nft-smart-contract), the gas fee is reduced, and the transaction cost is covered by the payable amount. This is to reduce the gas consumed by the contract call.
Deploy your contract by executing the script:
```bash theme={null}
npx hardhat run scripts/deploy.ts --network testnet
```
Copy the deployed address—you'll need this in subsequent steps.
The output looks like this:
```bash theme={null}
Deploying contract with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Calling createNFTCollection() with 15 HBAR to create the HTS collection...
createNFTCollection() tx hash: 0x5c5f584cae867a3b5dce130756f48921b3071671717a7d646f68654c1396cf67
MyHTSToken contract deployed at: 0xC244Cf8d1c123B1A2C8c12c780ce41d813eb70be
Underlying HTS NFT Collection (ERC721 facade) address: 0x000000000000000000000000000000000068D3eF
```
***
## Step 4: Minting an HTS NFT
Create a `mintNFT.ts` script in your `scripts` directory to mint an NFT. Don't forget to replace the `` with the address you've just copied.
```typescript scripts/mintNFT.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [signer] = await ethers.getSigners();
console.log("Using signer:", signer.address);
const contractAddress = "";
const recipient = signer.address;
const myHTSTokenContract = await ethers.getContractAt(
"MyHTSToken",
contractAddress,
signer
);
// Display the underlying HTS token address
const tokenAddress = await myHTSTokenContract.tokenAddress();
console.log("HTS ERC721 facade address:", tokenAddress);
// 1) Associate the signer via token.associate() (EOA -> token contract)
const tokenAssociateAbi = ["function associate()"];
const token = new ethers.Contract(tokenAddress, tokenAssociateAbi, signer);
console.log("Associating signer to token via token.associate() ...");
const assocTx = await token.associate({ gasLimit: 800_000 });
await assocTx.wait();
console.log("Associate tx hash:", assocTx.hash);
// 2) Prepare metadata (<= 100 bytes)
const metadata = ethers.hexlify(
ethers.toUtf8Bytes(
"ipfs://bafkreibr7cyxmy4iyckmlyzige4ywccyygomwrcn4ldcldacw3nxe3ikgq"
)
);
const byteLen = ethers.getBytes(metadata).length;
if (byteLen > 100) {
throw new Error(
`Metadata is ${byteLen} bytes; must be <= 100 bytes for HTS`
);
}
// 3) Mint the NFT via the wrapper (wrapper holds supply key)
console.log(`Minting NFT to ${recipient} with metadata: ${metadata} ...`);
// Note: Our mintNFT function is overloaded; we must use this syntax to disambiguate
// or we get a typescript error.
const tx = await myHTSTokenContract["mintNFT(address,bytes)"](
recipient,
metadata,
{
gasLimit: 350_000,
}
);
await tx.wait();
console.log("Mint tx hash:", tx.hash);
// Check recipient's NFT balance on the ERC721 facade (not on MyHTSToken)
const erc721 = new ethers.Contract(
tokenAddress,
["function balanceOf(address owner) view returns (uint256)"],
signer
);
const balance = (await erc721.balanceOf(recipient)) as bigint;
console.log("Balance:", balance.toString(), "NFTs");
}
main().catch(console.error);
```
**How It Works**
1. Connects to Hedera testnet, gets the first signer, and attaches to your deployed MyHTSToken contract.
2. Reads the underlying HTS ERC721 facade address (tokenAddress) from the contract.
3. Constructs \<=100-byte UTF-8 metadata and calls mintNFT(recipient, metadata), then waits for the transaction receipt.
4. Queries balanceOf(recipient) on the ERC721 facade and logs the current NFT count.
The code mints a new NFT to your account ( `signer.address` ). Then we verify the balance to see if we own an HTS NFT.
Mint an NFT:
```bash theme={null}
npx hardhat run scripts/mintNFT.ts --network testnet
```
Expected output:
```bash theme={null}
Using signer: 0xA98556A4deeB07f21f8a66093989078eF86faa30
HTS ERC721 facade address: 0x000000000000000000000000000000000068D3eF
Associating signer to token via token.associate() ...
Associate tx hash: 0x08dd0150d9a356cac3e949b26841a6688c7aab4b017224cfea7330d1b3cb432e
Minting NFT to 0xA98556A4deeB07f21f8a66093989078eF86faa30 with metadata: 0x697066733a2f2f6261666b7265696272376379786d79346979636b6d6c797a69676534797763637979676f6d7772636e346c64636c64616377336e786533696b6771 ...
Mint tx hash: 0x55fe1d9cb4126913eb07fc2e2d596c0bcb41eab66e0dbcd4c93be9e73c69beed
Balance: 1 NFTs
```
***
## Step 5: Burning an HTS NFT
Create a burn script (`burnNFT.ts` ) in your `scripts` directory. Make sure to replace `` to the MyHTSToken contract address you got from deploying and replace `` with the tokenId you want to burn(eg. "1") :
```typescript scripts/burnNFT.ts theme={null}
import { network } from "hardhat";
import type { ContractTransactionResponse } from "ethers";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [signer] = await ethers.getSigners();
console.log("Using signer:", signer.address);
const contractAddress = "";
const tokenId = BigInt("");
const myHTSTokenContract = await ethers.getContractAt(
"MyHTSToken",
contractAddress,
signer
);
const tokenAddress: string = await myHTSTokenContract.tokenAddress();
console.log("HTS ERC721 facade address:", tokenAddress);
// Minimal ERC721 ABI for approvals and balance
const erc721 = new ethers.Contract(
tokenAddress,
[
"function approve(address to, uint256 tokenId) external",
"function getApproved(uint256 tokenId) external view returns (address)",
"function ownerOf(uint256 tokenId) external view returns (address)",
"function balanceOf(address owner) external view returns (uint256)",
],
signer
);
const ownerOfToken: string = await erc721.ownerOf(tokenId);
console.log("Current owner of token:", ownerOfToken);
// Check if already approved for this tokenId; if not, approve MyHTSToken contract
const currentApproved: string = await erc721.getApproved(tokenId);
if (currentApproved.toLowerCase() !== contractAddress.toLowerCase()) {
console.log(
`Approving MyHTSToken contract ${contractAddress} for tokenId ${tokenId.toString()}...`
);
const approveTx = (await erc721.approve(
contractAddress,
tokenId
)) as unknown as ContractTransactionResponse;
await approveTx.wait();
console.log("Approval tx hash:", approveTx.hash);
} else {
console.log("MyHTSToken contract is already approved for this tokenId.");
}
// Burn via MyHTSToken
console.log(`Burning tokenId ${tokenId.toString()}...`);
const burnTx = (await myHTSTokenContract.burnNFT(tokenId, {
gasLimit: 200_000,
})) as unknown as ContractTransactionResponse;
await burnTx.wait();
console.log("Burn tx hash:", burnTx.hash);
// Show caller's balance after burn
const balanceAfter = (await erc721.balanceOf(signer.address)) as bigint;
console.log("Balance after burn:", balanceAfter.toString(), "NFTs");
}
main().catch(console.error);
```
**How It Works**
1. Connects to Hedera testnet, gets the signer, attaches to MyHTSToken, and reads the ERC721 facade tokenAddress.
2. Checks token ownership and existing approval; if needed, approves the MyHTSToken contract for the specific tokenId.
3. Calls burnNFT(tokenId) on MyHTSToken and waits for the transaction receipt.
4. Reads and logs the signer’s NFT balance from the ERC721 facade after the burn.
The script will burn the HTS NFT with the ID set to `1`, which is the HTS NFT you've just minted. To be sure the token has been deleted, let's print the balance for our account to the terminal. The balance should show a balance of `0`.
Burn the NFT:
```bash theme={null}
npx hardhat run scripts/burnNFT.ts --network testnet
```
You should get an output similar to:
```bash theme={null}
Using signer: 0xA98556A4deeB07f21f8a66093989078eF86faa30
HTS ERC721 facade address: 0x000000000000000000000000000000000068D3eF
Current owner of token: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Approving MyHTSToken contract 0xC244Cf8d1c123B1A2C8c12c780ce41d813eb70be for tokenId 1...
Approval tx hash: 0xf41d3696908fab800bfe36c32be149e05c8738c32c85e71eff534cf49a5e1e7f
Burning tokenId 1...
Burn tx hash: 0x0483a4616af64e150ba52fe092c3d9fabf81439a90067e6dd3efaad299a601cd
Balance after burn: 0 NFTs
```
**Congratulations! 🎉 You have successfully learned how to deploy an HTS NFT collection smart contract using Hardhat, OpenZeppelin, and Ethers. Feel free to reach out in** [**Discord**](https://hedera.com/discord)**!**
***
## Step 6: Run tests(Optional)
You can find both types of tests in the [**Hedera-Code-Snippets repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hts-evm-mint-nfts). You will find the following files:
* `contracts/MyHTSToken.t.sol`
- **Ownership and access control**: Ensures constructor sets the correct owner
and that onlyOwner is enforced for createNFTCollection (non-owners revert with
OwnableUnauthorizedAccount). \* **Pre-creation guards:** Confirms HTS-dependent
functions (mint, burn) revert with "HTS: not created" before the collection is
created. \* **HBAR handling:** Verifies the contract can receive HBAR
(HBARReceived event), blocks non-owner withdrawals, and lets the owner
withdraw all HBAR (HBARWithdrawn event) leaving the contract balance at zero.
* `test/MyHTSToken.ts`
- **Deployment and setup**: Deploys the wrapper, creates the HTS NFT collection (funded with 15 HBAR), and retrieves/validates the ERC721 facade address.
- **Mint with metadata**: Mints an NFT to the deployer with metadata (\<= 100 bytes), asserts the NFTMinted event, and extracts the tokenId from wrapper logs.
- **ERC721 interactions**: Uses a minimal ERC721 ABI to query owner/balance and manage approvals without relying on full artifacts.
- **Burn flow**: Ensures the wrapper is approved for the specific tokenId (approves if needed), then burns via the wrapper and asserts the NFTBurned event.
- **Post-burn check**: Reads the deployer’s ERC721 balance after burn to confirm calls succeed (balance may vary if multiple NFTs exist).
Copy these files and then run the tests:
```bash theme={null}
# This will run the tests via hardhat
npx hardhat test solidity
# This will run the tests via hedera testnet as the precompiles
# are not available on hardhat locally and we must use the testnet
npx hardhat test mocha
```
You can also run both the solidity and mocha tests altogether:
```bash theme={null}
npx hardhat test
```
***
## Conclusion
Using Solidity on Hedera, you can **create**, **mint and** **burn** native NFTs with minimal code thanks to the **HTS System Contract**. In this tutorial, you saw how to:
* **Create** a new NFT class with royalty fees (`createNFTCollection`).
* **Mint** new tokens (`mintNFT`).
* **Burn** tokens when they are no longer needed (`burnNFT`).
Continue exploring our [Part 2: KYC & Update](/evm/tutorials/hedera/hts-evm/part2-kyc-update) to see how advanced compliance flags (e.g., KYC) or updating tokens can be handled natively.
***
## Additional Resources
Check out our GitHub repo to find the full contract and Hardhat test scripts, along with the configuration files you need to deploy and test on Hedera!
* [Full Contract and Tests Repository](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hts-evm-mint-nfts)
[GitHub](https://github.com/jaycoolh) | [X](https://x.com/jaycoolh)
[GitHub](https://github.com/michielmulders) |
[LinkedIn](https://www.linkedin.com/in/michielmulders/)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# HTS x EVM - KYC & Update (Part 2)
Source: https://docs.hedera.com/evm/tutorials/hedera/hts-evm/part2-kyc-update
In [Part 1](/evm/tutorials/hedera/hts-evm/part1-mint-nfts) of the series, you saw how to mint, transfer, and burn an NFT using Hedera'a EVM and [Hedera Token Service (HTS) System Smart Contracts](/evm/hedera-services/system-contracts). In this guide, you’ll learn the basics of how to configure / permission native Hedera Tokens via a Smart Contract. Specifically, you will learn how to:
* **Create** and **configure** an NFT.
* **Grant** and **revoke** a Know Your Customer (KYC) flag.
* **Update** the KYC key with an Admin (to rotate compliance keys, for example)
You can take a look at the **complete code** in the [**Hedera-Code-Snippets
repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hts-evm-mint-nfts)
***
## Prerequisites
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/).
* Basic understanding of Solidity.
***
## Table of Contents
1. [Step 1: Add KYC key when creating HTS NFT Collection](#step-1.-add-kyc-key-when-creating-hts-nft-collection)
2. [Step 2: Minting and Burning an NFT](#step-2%3A.-minting-and-burning-an-nft)
3. [Step 3: Granting KYC](#step-4%3A.-granting-kyc)
4. [Step 4: Revoking KYC](#step-5.-revoking-kyc)
5. [Step 5: Updating the KYC Key](#step-7%3A.-updating-the-kyc-key)
6. [Step 6: Deploy your HTS KYC enabled NFT Smart Contract](#step-6%3A-deploy-your-hts-kyc-enabled-nft-smart-contract)
7. [Step 7: Minting an HTS NFT with KYC](#step-7%3A-minting-an-hts-nft-with-kyc)
8. [Step 8: Burning an HTS NFT with KYC](#step-8%3A-burning-an-hts-nft)
9. [Step 9: Run tests](#step-9-run-tests-optional)
10. [Token Association in the Tests](#token-association-in-the-tests)
11. [Conclusion](#conclusion)
12. [Additional Resources](#additional-resources)
***
## Step 1. Add KYC key when creating HTS NFT Collection
The [previous tutorial](/evm/tutorials/hedera/hts-evm/part1-mint-nfts#id-2.-minting-an-nft) covered creating NFT collection. Everything remains largely the same except for the following changes:
* We just need to add one additional line for managing the KYC key that is able to grant/remove KYC.
* We will be using `createNonFungibleToken` instead of `createNonFungibleTokenWithCustomFees` for this exercise.
#### **Key Code Snippet:**
```solidity contracts/MyHTSTokenKYC.sol wrap theme={null}
contract MyHTSTokenKYC is HederaTokenService, KeyHelper, Ownable {
...
function createNFTCollection(
string memory _name,
string memory _symbol
) external payable onlyOwner {
require(tokenAddress == address(0), "Already initialized");
name = _name;
symbol = _symbol;
// Build token definition
IHederaTokenService.HederaToken memory token;
token.name = name;
token.symbol = symbol;
token.treasury = address(this);
token.memo = "";
// Keys: SUPPLY + ADMIN + KYC -> contractId
IHederaTokenService.TokenKey[]
memory keys = new IHederaTokenService.TokenKey[](3);
keys[0] = getSingleKey(
KeyType.SUPPLY,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[1] = getSingleKey(
KeyType.ADMIN,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[2] = getSingleKey(
KeyType.KYC,
KeyValueType.CONTRACT_ID,
address(this)
);
token.tokenKeys = keys;
(int rc, address created) = createNonFungibleToken(token);
require(rc == HederaResponseCodes.SUCCESS, "HTS: create NFT failed");
tokenAddress = created;
// KYC the treasury so it may receive and operate on NFTs when KYC is enforced
int rcTreasuryKyc = grantTokenKyc(tokenAddress, address(this));
require(
rcTreasuryKyc == HederaResponseCodes.SUCCESS,
"HTS: self KYC failed"
);
emit NFTCollectionCreated(created);
}
...
}
```
**How It Works**
1. **Define Token Details** – Provide `name` and `symbol`.
2. **Set Keys** – We generate three token keys:
* **AdminKey**: Grants permission to update token-level properties later.
* **SupplyKey**: Permits minting and burning of tokens.
* **KYCKey**: Allows the contract (acting as the KYC authority) to grant or revoke KYC on specific accounts.
3. **Create the NFT** – Call the HTS System Contract's `createNonFungibleToken` function from within the contract. If successful, store the resulting HTS token address in `tokenAddress`.
We call `createNFTCollection(...)` and expect it to emit an `NFTCollectionCreated` event with a valid token address.
***
## Step 2. Minting and Burning an NFT
The [previous tutorial](/evm/tutorials/hedera/hts-evm/part1-mint-nfts#id-2.-minting-an-nft) covered minting and burning NFTs. Nothing's changed in the code as it's the same as before.
***
## Step 3. Granting KYC
Let's update our contract by:
* Adding a new function `grantKYC` to enable KYC for a specific account. If a token is configured to enforce KYC, that account must be “granted” KYC before it can receive or send the token.
* We will also define a new event `KYCGranted` to go along with it.
#### **Key Code Snippet:**
```solidity contracts/MyHTSTokenKYC.sol wrap theme={null}
contract MyHTSTokenKYC is HederaTokenService, KeyHelper, Ownable {
...
event KYCGranted(address account);
...
function grantKYC(address account) external {
require(tokenAddress != address(0), "HTS: not created");
int response = grantTokenKyc(tokenAddress, account);
require(response == HederaResponseCodes.SUCCESS, "HTS: grant KYC failed");
emit KYCGranted(account);
}
...
}
```
Without this step, the account won’t be able to receive or transact the NFT.
***
## Step 4. Revoking KYC
Let's update our contract by:
* Adding a new function `revokeKYC` to disable KYC for a specific account. After revocation, that account can no longer receive or transfer the token.
* We will also define a new event `KYCRevoked` to go along with it.
```solidity contracts/MyHTSTokenKYC.sol wrap theme={null}
contract MyHTSTokenKYC is HederaTokenService, KeyHelper, Ownable {
...
event KYCRevoked(address account);
...
function revokeKYC(address account) external {
require(tokenAddress != address(0), "HTS: not created");
int response = revokeTokenKyc(tokenAddress, account);
require(
response == HederaResponseCodes.SUCCESS ||
response ==
HederaResponseCodes.ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN,
"HTS: revoke KYC failed"
);
emit KYCRevoked(account);
}
...
}
```
***
## Step 5. Updating the KYC Key
Let's update our contract by:
* Adding a new function `updateKYCKey` to change the KYC key on the token. This could be a “key rotation” to maintain compliance or to assign another entity control over KYC status.
* We will also define a new event `KYCKeyUpdated` to go along with it.
```solidity contracts/MyHTSTokenKYC.sol wrap theme={null}
contract MyHTSTokenKYC is HederaTokenService, KeyHelper, Ownable {
...
event KYCKeyUpdated(bytes newKey);
...
function updateKYCKey(bytes memory newKYCKey) external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
// Create a new TokenKey array with just the KYC key
IHederaTokenService.TokenKey[]
memory keys = new IHederaTokenService.TokenKey[](1);
keys[0] = getSingleKey(KeyType.KYC, KeyValueType.SECP256K1, newKYCKey);
int responseCode = updateTokenKeys(tokenAddress, keys);
require(
responseCode == HederaResponseCodes.SUCCESS,
"HTS: update KYC key failed"
);
emit KYCKeyUpdated(newKYCKey);
}
...
}
```
After this key rotation, the contract's key is no longer able to perform KYC operations. In the snippet above, we immediately demonstrate that KYC attempts signed by the contract itself will revert.
Account 1 will now be able to grant/revoke KYC [using the SDK](/native/tokens/enable-kyc).
Here's the complete contract code for `MyHTSTokenKYC.sol`:
```solidity contracts/MyHTSTokenKYC.sol wrap theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
// Admin/ownership like the OZ example
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
// Read/transfer via ERC721 facade exposed at the HTS token EVM address
import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
// Hedera HTS system contracts (as in your setup)
// Hedera HTS system contracts (v1, NOT v2)
import {HederaTokenService} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/HederaTokenService.sol";
import {IHederaTokenService} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/IHederaTokenService.sol";
import {HederaResponseCodes} from "@hashgraph/smart-contracts/contracts/system-contracts/HederaResponseCodes.sol";
import {KeyHelper} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/KeyHelper.sol";
/**
* HTS-backed ERC721-like collection:
* - Creates the HTS NFT collection in the constructor (like deploying an ERC721).
* - SUPPLY key = this contract (mint/burn only via contract).
* - ADMIN key = this contract (admin updates only via contract).
* - KYC key = this contract (KYC management via contract).
* - Holders use the token’s ERC721 facade directly (SDK or EVM).
*/
contract MyHTSTokenKYC is HederaTokenService, KeyHelper, Ownable {
// Underlying HTS NFT token EVM address (set during initialize. This is the "ERC721-like" token)
address public tokenAddress;
// Cosmetic copies for convenience (optional)
string public name;
string public symbol;
// Small non-empty default metadata for simple mints (<=100 bytes as per HTS limit)
bytes private constant DEFAULT_METADATA = hex"01";
uint256 private constant INT64_MAX = 0x7fffffffffffffff;
event NFTCollectionCreated(address indexed token);
event NFTMinted(
address indexed to,
uint256 indexed tokenId,
int64 newTotalSupply
);
event NFTBurned(uint256 indexed tokenId, int64 newTotalSupply);
event KYCGranted(address account);
event KYCRevoked(address account);
event KYCKeyUpdated(bytes newKey);
event HBARReceived(address indexed from, uint256 amount);
event HBARFallback(address sender, uint256 amount, bytes data);
event HBARWithdrawn(address indexed to, uint256 amount);
/**
* Constructor sets ownership.
* Actual HTS token creation happens in createNFTCollection().
*/
constructor() Ownable(msg.sender) {}
/**
* Creates the HTS NFT collection with custom fees.
* Can be called exactly once by the owner after deployment.
*
* @param _name Token/collection name
* @param _symbol Token/collection symbol
*/
function createNFTCollection(
string memory _name,
string memory _symbol
) external payable onlyOwner {
require(tokenAddress == address(0), "Already initialized");
name = _name;
symbol = _symbol;
// Build token definition
IHederaTokenService.HederaToken memory token;
token.name = name;
token.symbol = symbol;
token.treasury = address(this);
token.memo = "";
// Keys: SUPPLY + ADMIN + KYC -> contractId
IHederaTokenService.TokenKey[]
memory keys = new IHederaTokenService.TokenKey[](3);
keys[0] = getSingleKey(
KeyType.SUPPLY,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[1] = getSingleKey(
KeyType.ADMIN,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[2] = getSingleKey(
KeyType.KYC,
KeyValueType.CONTRACT_ID,
address(this)
);
token.tokenKeys = keys;
(int rc, address created) = createNonFungibleToken(token);
require(rc == HederaResponseCodes.SUCCESS, "HTS: create NFT failed");
tokenAddress = created;
// KYC the treasury so it may receive and operate on NFTs when KYC is enforced
int rcTreasuryKyc = grantTokenKyc(tokenAddress, address(this));
require(
rcTreasuryKyc == HederaResponseCodes.SUCCESS,
"HTS: self KYC failed"
);
emit NFTCollectionCreated(created);
}
// ---------------------------------------------------------------------------
// ERC721-like minting (admin via Ownable + SUPPLY key on contract)
// ---------------------------------------------------------------------------
// Minimal API parity: mintNFT(to) onlyOwner -> returns new tokenId (serial)
function mintNFT(address to) public onlyOwner returns (uint256) {
return _mintAndSend(to, DEFAULT_METADATA);
}
// Optional overload with custom metadata (<= 100 bytes)
function mintNFT(
address to,
bytes memory metadata
) public onlyOwner returns (uint256) {
require(metadata.length <= 100, "HTS: metadata >100 bytes");
return _mintAndSend(to, metadata);
}
function _mintAndSend(
address to,
bytes memory metadata
) internal returns (uint256 tokenId) {
require(tokenAddress != address(0), "HTS: not created");
// 1) Mint to treasury (this contract)
bytes[] memory arr = new bytes[](1);
arr[0] = metadata;
(int rc, int64 newTotalSupply, int64[] memory serials) = mintToken(
tokenAddress,
0,
arr
);
require(
rc == HederaResponseCodes.SUCCESS && serials.length == 1,
"HTS: mint failed"
);
// 2) Transfer from treasury -> recipient via ERC721 facade
uint256 serial = uint256(uint64(serials[0]));
// Recipient must be associated (or have auto-association available)
IERC721(tokenAddress).transferFrom(address(this), to, serial);
emit NFTMinted(to, serial, newTotalSupply);
return serial;
}
// ---------------------------------------------------------------------------
// ERC721Burnable-like flow for holders
// ---------------------------------------------------------------------------
// Holder-initiated burn:
// - User approves this contract for tokenId (approve or setApprovalForAll)
// - Calls burn(tokenId); contract pulls to treasury and burns via HTS
// Allows onlyOwner to burn when the NFT is already in treasury,
// avoiding the need for ERC721 approvals in that case.
function burnNFT(uint256 tokenId) external {
require(tokenAddress != address(0), "HTS: not created");
address owner_ = IERC721(tokenAddress).ownerOf(tokenId);
// Match ERC721Burnable semantics: only the token owner or an approved operator may trigger burn
require(
msg.sender == owner_ ||
IERC721(tokenAddress).getApproved(tokenId) == msg.sender ||
IERC721(tokenAddress).isApprovedForAll(owner_, msg.sender),
"caller not owner nor approved"
);
// If not already in treasury, ensure this contract is approved to pull the token and then pull it
if (owner_ != address(this)) {
bool contractApproved = IERC721(tokenAddress).getApproved(
tokenId
) ==
address(this) ||
IERC721(tokenAddress).isApprovedForAll(owner_, address(this));
require(contractApproved, "contract not approved to transfer");
IERC721(tokenAddress).transferFrom(owner_, address(this), tokenId);
}
// Burn via HTS (requires token to be in treasury)
int64[] memory serials = new int64[](1);
serials[0] = _toI64(tokenId);
(int rc, int64 newTotalSupply) = burnToken(tokenAddress, 0, serials);
require(rc == HederaResponseCodes.SUCCESS, "HTS: burn failed");
emit NFTBurned(tokenId, newTotalSupply);
}
function grantKYC(address account) external {
require(tokenAddress != address(0), "HTS: not created");
int response = grantTokenKyc(tokenAddress, account);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: grant KYC failed"
);
emit KYCGranted(account);
}
function revokeKYC(address account) external {
require(tokenAddress != address(0), "HTS: not created");
int response = revokeTokenKyc(tokenAddress, account);
require(
response == HederaResponseCodes.SUCCESS ||
response ==
HederaResponseCodes.ACCOUNT_KYC_NOT_GRANTED_FOR_TOKEN,
"HTS: revoke KYC failed"
);
emit KYCRevoked(account);
}
function updateKYCKey(bytes memory newKYCKey) external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
// Create a new TokenKey array with just the KYC key
IHederaTokenService.TokenKey[]
memory keys = new IHederaTokenService.TokenKey[](1);
keys[0] = getSingleKey(KeyType.KYC, KeyValueType.SECP256K1, newKYCKey);
int responseCode = updateTokenKeys(tokenAddress, keys);
require(
responseCode == HederaResponseCodes.SUCCESS,
"HTS: update KYC key failed"
);
emit KYCKeyUpdated(newKYCKey);
}
// ---------------------------------------------------------------------------
// HBAR handling
// ---------------------------------------------------------------------------
// Accept HBAR
receive() external payable {
emit HBARReceived(msg.sender, msg.value);
}
fallback() external payable {
emit HBARFallback(msg.sender, msg.value, msg.data);
}
function withdrawHBAR() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No HBAR to withdraw");
(bool success, ) = owner().call{value: balance}("");
require(success, "Failed to withdraw HBAR");
emit HBARWithdrawn(owner(), balance);
}
// --------------------- internal helpers ---------------------
function _toI64(uint256 x) internal pure returns (int64) {
require(x <= INT64_MAX, "cast: > int64.max");
return int64(uint64(x));
}
}
```
***
## Step 6: Deploy Your HTS KYC Enabled NFT Smart Contract
Create a deployment script (`deployKYC.ts`) in `scripts` directory:
```typescript scripts/deployKYC.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contract with the account:", deployer.address);
// 1) Deploy the wrapper contract
// The deployer will also be the owner of our NFT contract
const MyHTSTokenKYC = await ethers.getContractFactory(
"MyHTSTokenKYC",
deployer
);
const contract = await MyHTSTokenKYC.deploy();
await contract.waitForDeployment();
// 2) Create the HTS NFT collection by calling createNFTCollection()
// NOTE: createNFTCollection() must be payable to accept this value.
const NAME = "MyHTSTokenKYCNFTCollection";
const SYMBOL = "MHT";
const HBAR_TO_SEND = "15"; // HBAR to send with createNFTCollection()
console.log(
`Calling createNFTCollection() with ${HBAR_TO_SEND} HBAR to create the HTS collection...`
);
const tx = await contract.createNFTCollection(NAME, SYMBOL, {
gasLimit: 350_000,
value: ethers.parseEther(HBAR_TO_SEND),
});
await tx.wait();
console.log("createNFTCollection() tx hash:", tx.hash);
// 3) Read the created HTS token address
const contractAddress = await contract.getAddress();
console.log("MyHTSTokenKYC contract deployed at:", contractAddress);
const tokenAddress = await contract.tokenAddress();
console.log(
"Underlying HTS KYC NFT Collection (ERC721 facade) address:",
tokenAddress
);
}
main().catch(console.error);
```
In this script, we first retrieve your account (the deployer) using Ethers.js. This account will own the deployed smart contract. Next, we use this account to deploy the contract by calling `MyHTSTokenKYC.deploy()`.
**Note**
For most HTS [System Smart Contract](/evm/hedera-services/system-contracts) calls, an HBAR value **is not** required to be sent in the contract call; the gas fee will cover it. However, for expensive transactions, like [Create HTS NFT Collection](#step-3%3A-deploy-your-hts-nft-smart-contract), the gas fee is reduced, and the transaction cost is covered by the payable amount. This is to reduce the gas consumed by the contract call.
Deploy your contract by executing the script:
```bash theme={null}
npx hardhat run scripts/deployKYC.ts --network testnet
```
Copy the deployed address—you'll need this in subsequent steps.
The output looks like this:
```bash theme={null}
Deploying contract with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Calling createNFTCollection() with 15 HBAR to create the HTS collection...
createNFTCollection() tx hash: 0x0e279272b7c9de310ea7fd235755177214dfd2489d9cce83a723eb14e97dc58a
MyHTSTokenKYC contract deployed at: 0xe162146963C77CaC223a5D0f6DeFb7035fF7075D
Underlying HTS KYC NFT Collection (ERC721 facade) address: 0x000000000000000000000000000000000068D4f2
```
## Step 7: Minting an HTS NFT with KYC
Create a `mintNFTKYC.ts` script in your `scripts` directory to mint an NFT. Don't forget to replace the `` with the address you've just copied.
```typescript scripts/mintNFTKYC.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [signer] = await ethers.getSigners();
console.log("Using signer:", signer.address);
const contractAddress = "";
const recipient = signer.address;
const myHTSTokenKYCContract = await ethers.getContractAt(
"MyHTSTokenKYC",
contractAddress,
signer
);
// Display the underlying HTS token address
const tokenAddress = await myHTSTokenKYCContract.tokenAddress();
console.log("HTS ERC721 facade address:", tokenAddress);
// 1) Associate the signer via token.associate() (EOA -> token contract)
const tokenAssociateAbi = ["function associate()"];
const token = new ethers.Contract(tokenAddress, tokenAssociateAbi, signer);
console.log("Associating signer to token via token.associate() ...");
const assocTx = await token.associate({ gasLimit: 800_000 });
await assocTx.wait();
console.log("Associate tx hash:", assocTx.hash);
// 2) Grant KYC to the recipient via wrapper (wrapper holds KYC key)
try {
console.log(`Granting KYC to ${recipient} ...`);
const grantTx = await myHTSTokenKYCContract.grantKYC(recipient, {
gasLimit: 75_000,
});
await grantTx.wait();
console.log("Grant KYC tx hash:", grantTx.hash);
} catch (e: any) {
console.warn(
"Grant KYC failed (ensure wrapper still holds KYC key):",
e?.message || e
);
throw e;
}
// 3) Prepare metadata (<= 100 bytes)
const metadata = ethers.hexlify(
ethers.toUtf8Bytes(
"ipfs://bafkreibr7cyxmy4iyckmlyzige4ywccyygomwrcn4ldcldacw3nxe3ikgq"
)
);
const byteLen = ethers.getBytes(metadata).length;
if (byteLen > 100) {
throw new Error(
`Metadata is ${byteLen} bytes; must be <= 100 bytes for HTS`
);
}
// 4) Mint to recipient
console.log(`Minting NFT to ${recipient} with metadata: ${metadata} ...`);
// Note: Our mintNFT function is overloaded; we must use this syntax to disambiguate
// or we get a typescript error.
const tx = await myHTSTokenKYCContract["mintNFT(address,bytes)"](
recipient,
metadata,
{
gasLimit: 400_000,
}
);
await tx.wait();
console.log("Mint tx hash:", tx.hash);
// Check recipient's NFT balance on the ERC721 facade (not on MyHTSTokenKYC)
const erc721 = new ethers.Contract(
tokenAddress,
["function balanceOf(address owner) view returns (uint256)"],
signer
);
const balance = (await erc721.balanceOf(recipient)) as bigint;
console.log("Balance:", balance.toString(), "NFTs");
}
main().catch(console.error);
```
**How It Works**
1. Connects to Hedera testnet, gets the first signer, and attaches to your deployed MyHTSTokenKYC contract.
2. Reads the underlying HTS ERC721 facade address (tokenAddress) from the contract.
3. Associates the signer via `token.associate()`(EOA -> token contract)
4. Grant KYC to the recipient
5. Constructs \<=100-byte UTF-8 metadata and calls mintNFT(recipient, metadata), then waits for the transaction receipt.
6. Mints NFT to recipient
7. Queries balanceOf(recipient) on the ERC721 facade and logs the current NFT count.
The code mints a new NFT to your account ( `signer.address` ). Then we verify the balance to see if we own an HTS NFT.
Mint an NFT:
```bash theme={null}
npx hardhat run scripts/mintNFTKYC.ts --network testnet
```
Expected output:
```bash theme={null}
Using signer: 0xA98556A4deeB07f21f8a66093989078eF86faa30
HTS ERC721 facade address: 0x000000000000000000000000000000000068D4f2
Associating signer to token via token.associate() ...
Associate tx hash: 0xce72afe465d89bf5788697c3185e1f289957cd51e6e1f28994ce1b9bc629d47d
Granting KYC to 0xA98556A4deeB07f21f8a66093989078eF86faa30 ...
Grant KYC tx hash: 0x54b604035edfc1aed19336a33a08156d39862ddd6e6d68f5062c038e34e9a574
Minting NFT to 0xA98556A4deeB07f21f8a66093989078eF86faa30 with metadata: 0x697066733a2f2f6261666b7265696272376379786d79346979636b6d6c797a69676534797763637979676f6d7772636e346c64636c64616377336e786533696b6771 ...
Mint tx hash: 0x1c7f02fb63b6b6add6ebab11bc5137d8289e7ec7576b2b1e394b864b49777a7e
Balance: 1 NFTs
```
***
## Step 8: Burning an HTS NFT
Create a burn script (`burnNFTKYC.ts` ) in your `scripts` directory. Make sure to replace `` to the MyHTSToken contract address you got from deploying and replace `` with the tokenId you want to burn(eg. "1") :
```typescript scripts/burnNFTKYC.ts theme={null}
import { network } from "hardhat";
import type { ContractTransactionResponse } from "ethers";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [signer] = await ethers.getSigners();
console.log("Using signer:", signer.address);
const contractAddress = "";
const tokenId = BigInt("");
const myHTSTokenKYCContract = await ethers.getContractAt(
"MyHTSTokenKYC",
contractAddress,
signer
);
const tokenAddress: string = await myHTSTokenKYCContract.tokenAddress();
console.log("HTS ERC721 facade address:", tokenAddress);
// Minimal ERC721 ABI for approvals and balance
const erc721 = new ethers.Contract(
tokenAddress,
[
"function approve(address to, uint256 tokenId) external",
"function getApproved(uint256 tokenId) external view returns (address)",
"function ownerOf(uint256 tokenId) external view returns (address)",
"function balanceOf(address owner) external view returns (uint256)",
],
signer
);
const ownerOfToken: string = await erc721.ownerOf(tokenId);
console.log("Current owner of token:", ownerOfToken);
// Check if already approved for this tokenId; if not, approve MyHTSTokenKYC contract
const currentApproved: string = await erc721.getApproved(tokenId);
if (currentApproved.toLowerCase() !== contractAddress.toLowerCase()) {
console.log(
`Approving MyHTSTokenKYC contract ${contractAddress} for tokenId ${tokenId.toString()}...`
);
const approveTx = (await erc721.approve(
contractAddress,
tokenId
)) as unknown as ContractTransactionResponse;
await approveTx.wait();
console.log("Approval tx hash:", approveTx.hash);
} else {
console.log("MyHTSTokenKYC contract is already approved for this tokenId.");
}
// Burn via MyHTSTokenKYC
console.log(`Burning tokenId ${tokenId.toString()}...`);
const burnTx = (await myHTSTokenKYCContract.burnNFT(tokenId, {
gasLimit: 200_000,
})) as unknown as ContractTransactionResponse;
await burnTx.wait();
console.log("Burn tx hash:", burnTx.hash);
// Show caller's balance after burn
const balanceAfter = (await erc721.balanceOf(signer.address)) as bigint;
console.log("Balance after burn:", balanceAfter.toString(), "NFTs");
}
main().catch(console.error);
```
**How It Works**
1. Connects to Hedera testnet, gets the signer, attaches to MyHTSTokenKYC, and reads the ERC721 facade tokenAddress.
2. Checks token ownership and existing approval; if needed, approves the MyHTSTokenKYC contract for the specific tokenId.
3. Calls burnNFT(tokenId) on MyHTSTokenKYC and waits for the transaction receipt.
4. Reads and logs the signer’s NFT balance from the ERC721 facade after the burn.
The script will burn the HTS NFT with the ID set to `1`, which is the HTS NFT you've just minted. To be sure the token has been deleted, let's print the balance for our account to the terminal. The balance should show a balance of `0`.
Burn the NFT:
```bash theme={null}
npx hardhat run scripts/burnNFTKYC.ts --network testnet
```
You should get an output similar to:
```bash theme={null}
Using signer: 0xA98556A4deeB07f21f8a66093989078eF86faa30
HTS ERC721 facade address: 0x000000000000000000000000000000000068D4f2
Current owner of token: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Approving MyHTSTokenKYC contract 0xe162146963C77CaC223a5D0f6DeFb7035fF7075D for tokenId 1...
Approval tx hash: 0x93b20306e6699e07c642721a7aa935c580f4f43ff1f39d87ca80b4c42de282af
Burning tokenId 1...
Burn tx hash: 0x8b6eb2e8cbb485636569859d8a839dc2a345a4dca0660a4ec9e52edabdc4777f
Balance after burn: 0 NFTs
```
**Congratulations! 🎉 You have successfully learned how to deploy an HTS NFT collection smart contract using Hardhat, OpenZeppelin, and Ethers. Feel free to reach out in** [**Discord**](https://hedera.com/discord)**!**
## Step 9: Run tests(Optional)
You can find both types of tests in the [**Hedera-Code-Snippets repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hts-evm-mint-nfts). You will find the following files:You can find both types of tests in the [**Hedera-Code-Snippets repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hts-evm-mint-nfts). You will find the following files:
* `contracts/MyHTSTokenKYC.t.sol`
- **Ownership and access control**: Ensures the constructor sets the correct
owner and enforces onlyOwner for createNFTCollection and updateKYCKey
(non-owners revert with OwnableUnauthorizedAccount). \* **Pre-creation
guards:** Confirms HTS-dependent functions (mint, burn, grantKYC, revokeKYC,
updateKYCKey) revert with "HTS: not created" before the collection is created.
- **HBAR handling**: Verifies the contract can receive HBAR (HBARReceived
event), blocks non-owner withdrawals, and allows the owner to withdraw all
HBAR (HBARWithdrawn event) leaving the contract balance at zero.
* `test/MyHTSTokenKYC.ts`
- **Deployment and setup**: Deploys the KYC wrapper, creates the HTS NFT
collection (with KYC key), and retrieves the ERC721 facade address. \* **KYC
enforcement before mint:** Validates that minting reverts when KYC has not
been granted to the recipient. \* **Association + KYC + mint:** Associates the
signer via token.associate(), grants KYC via the wrapper, then mints and
extracts the tokenId from the wrapper’s NFTMinted event. \* **Burn flow:**
Approves the wrapper for the minted token if needed and burns it via the
wrapper; confirms the operation by checking the signer’s ERC721 balance. \*
**KYC key rotation and effect:** Derives the signer’s compressed public key
on-chain and updates the KYC key; verifies subsequent grantKYC calls fail
since the wrapper no longer holds the KYC key.
Copy these files and then run the tests:
```bash theme={null}
# This will run the tests via hardhat
npx hardhat test solidity
# This will run the tests via hedera testnet as the precompiles
# are not available on hardhat locally and we must use the testnet
npx hardhat test mocha
```
You can also run both the solidity and mocha tests altogether:
```bash theme={null}
npx hardhat test
```
***
## Token Association in the Tests
Because we’re using a hybrid approach of EVM and the Native Hedera Token Service, you’ll see special logic to:
* **Associate** the newly created token with the signer’s account.
* **Grant** KYC to the account
* **Mint NFT** to the account
This is due to a nuance: In order to grant KYC to an account, it must have the token associated with it. This is the case even if the account has unlimited auto associations.
***
## Conclusion
Using a Solidity Smart Contract on Hedera, you can replicate many of the native HTS functionalities—granting and revoking KYC, updating token keys, minting and transferring NFTs—while retaining the benefit of contract-driven logic and on-chain state. This approach may be preferable if:
* **You want advanced business logic** in a self-contained contract.
* **You prefer standard Solidity patterns** and tooling for your Web3 workflows.
* **You plan** to modularize or integrate your token behavior with other smart contracts.
Check out [Part 3: How to Pause, Freeze, Wipe, and Delete NFTs](/evm/tutorials/hedera/hts-evm/part3-pause-freeze-wipe) to learn more about configuring Native Tokens with Smart Contracts.
***
## Additional Resources
Check out our GitHub repo to find the full contract and Hardhat test scripts, along with the configuration files you need to deploy and test on Hedera!
* [Full Contract and Tests Repository](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hts-evm-mint-nfts)
[GitHub](https://github.com/jaycoolh) | [X](https://x.com/jaycoolh)
[GitHub](https://github.com/michielmulders) |
[LinkedIn](https://www.linkedin.com/in/michielmulders/)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# HTS x EVM - How to Pause, Freeze, Wipe, and Delete NFTs (Part 3)
Source: https://docs.hedera.com/evm/tutorials/hedera/hts-evm/part3-pause-freeze-wipe
In [HTS x EVM - Part 2](/evm/tutorials/hedera/hts-evm/part2-kyc-update), you learned how to grant / revoke KYC and manage a token using the [Hedera Token Service (HTS) System Smart Contract](/evm/hedera-services/system-contracts#hedera-token-service). But those aren't all the token operations you can do!
In this guide, you will learn how to:
* **Pause** a token (stop all operations)
* **Freeze** a token for a specific account
* **Wipe** NFTs from a specific account
* **Delete** a token
***
## Prerequisites
* ECDSA account from the [Hedera Portal](https://portal.hedera.com/).
* Basic understanding of Solidity.
***
## Table of Contents
1. [Step 1: Creating, minting, and transferring an NFT](#step-2%3A.-creating-minting-and-transferring-an-nft)
2. [Step 2: Pause a Token](#step-3%3A.-pause-a-token)
3. [Step 3: Unpause a Token](#step-4%3A.-unpause-a-token)
4. [Step 4: Freeze a Token for a Specific Account](#step-5.-freeze-a-token-for-a-specific-account)
5. [Step 5: Unfreeze a Token for a Specific Account](#step-6%3A.-unfreeze-a-token-for-a-specific-account)
6. [Step 6: Wipe a Token](#step-7%3A.-wipe-a-token)
7. [Step 7: Delete a Token](#step-8%3A.-delete-a-token)
8. [Step 8: Deploy Your HTS NFT Smart Contract](#step-8%3A-deploy-your-hts-nft-smart-contract)
9. [Step 9: Minting an HTS NFT ](#step-9-minting-an-hts-nft-with-kyc)
10. [Step 10: Burning an HTS NFT](#step-10-burning-an-hts-nft)
11. [Step 11: Run tests](#step-11-run-tests-optional)
12. [Conclusion](#conclusion)
13. [Additional Resources](#additional-resources)
***
## Step 1. Creating, minting, and burning an NFT
These steps of the flow have been covered in the [Part 1](/evm/tutorials/hedera/hts-evm/part1-mint-nfts#id-2.-minting-an-nft) and [Part 2](/evm/tutorials/hedera/hts-evm/part2-kyc-update). The only difference here is that we set a few different keys to handle pausing, freezing, and wiping.
The previous tutorials covered creating NFT collection. Everything remains largely the same except for the following changes:
* Add one additional line for managing the PAUSE key that can be used to prevent the token from being involved in any kind of operation.
* Add one additional line for managing the FREEZE key that can be used to freeze transfers of the specified token for the account.
* Add one additional line for managing the WIPE key that can be used to wipe the provided amount of fungible or non-fungible tokens from the specified Hedera account. This transaction does not delete tokens from the treasury account. Wiping an account's tokens burns the tokens and decreases the total supply.
* Add one additional line for managing the DELETE key that can be used to mark a token as deleted, though it will remain in the ledger. Once deleted update, mint, burn, wipe, freeze, unfreeze, grant KYC, revoke KYC and token transfer transactions will resolve to TOKEN\_WAS\_DELETED. You cannot delete a specific NFT. You can delete the class of the NFT specified by the token ID after you have burned all associated NFTs associated with the token class
#### **Key Code Snippet:**
```solidity contracts/MyHTSTokenPFWD.sol wrap theme={null}
contract MyHTSTokenPFWD is HederaTokenService, KeyHelper, Ownable {
...
function createNFTCollection(
string memory _name,
string memory _symbol
) external payable onlyOwner {
require(tokenAddress == address(0), "Already initialized");
name = _name;
symbol = _symbol;
// Build token definition
IHederaTokenService.HederaToken memory token;
token.name = name;
token.symbol = symbol;
token.treasury = address(this);
token.memo = "";
// Keys: SUPPLY + ADMIN + PAUSE + FREEZE + WIPE + DELETE -> contractId
IHederaTokenService.TokenKey[]
memory keys = new IHederaTokenService.TokenKey[](6);
keys[0] = getSingleKey(
KeyType.SUPPLY,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[1] = getSingleKey(
KeyType.ADMIN,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[2] = getSingleKey(
KeyType.PAUSE,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[3] = getSingleKey(
KeyType.FREEZE,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[4] = getSingleKey(
KeyType.WIPE,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[5] = getSingleKey(
KeyType.DELETE,
KeyValueType.CONTRACT_ID,
address(this)
);
token.tokenKeys = keys;
(int rc, address created) = createNonFungibleToken(token);
require(rc == HederaResponseCodes.SUCCESS, "HTS: create NFT failed");
tokenAddress = created;
emit NFTCollectionCreated(created);
}
...
}
```
***
## Step 2. Pause a Token
Let's update our contract by:
* Adding a new function `pauseToken` to pause the token so it prevents the token from being involved in any kind of operation.
* We will also define a new event `TokenPaused`.
#### **Key Code Snippet:**
```solidity contracts/MyHTSTokenPFWD.sol wrap theme={null}
contract MyHTSTokenPFWD is HederaTokenService, KeyHelper, Ownable {
...
event TokenPaused();
...
function pauseToken() external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = pauseToken(tokenAddress);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: pause token failed"
);
emit TokenPaused();
}
...
}
```
***
## Step 3. Unpause a Token
Let's update our contract by:
* Adding a new function `unpauseToken` to unpause the token so the token operations can be executed again.
* We will also define a new event `TokenPaused`.
#### **Key Code Snippet:**
```solidity contracts/MyHTSTokenPFWD.sol wrap theme={null}
contract MyHTSTokenPFWD is HederaTokenService, KeyHelper, Ownable {
...
event TokenUnpaused();
...
function unpauseToken() external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = unpauseToken(tokenAddress);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: unpause token failed"
);
emit TokenUnpaused();
}
...
}
```
***
## Step 4. Freeze a Token for a Specific Account
Let's update our contract by:
* Adding a new function `freezeAccount` to freezes a specific account, meaning it can neither send nor receive the token. Freezing is more granular than pausing; it only affects a specific account. This is useful for making soul-bound tokens.
* We will also define a new event `AccountFrozen`.
#### **Key Code Snippet:**
```solidity contracts/MyHTSTokenPFWD.sol wrap theme={null}
contract MyHTSTokenPFWD is HederaTokenService, KeyHelper, Ownable {
...
event AccountFrozen(address indexed account);
...
function freezeAccount(address account) external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = freezeToken(tokenAddress, account);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: freeze account failed"
);
emit AccountFrozen(account);
}
...
}
```
***
## Step 5. Unfreeze a Token for a Specific Account
Let's update our contract by:
* Adding a new function `unfreezeAccount` to unfreeze the token for a specific account.
* We will also define a new event `AccountUnFrozen`.
#### **Key Code Snippet:**
```solidity contracts/MyHTSTokenPFWD.sol wrap theme={null}
contract MyHTSTokenPFWD is HederaTokenService, KeyHelper, Ownable {
...
event AccountUnFrozen(address indexed account);
...
function unfreezeAccount(address account) external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = unfreezeToken(tokenAddress, account);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: unfreeze account failed"
);
emit AccountUnfrozen(account);
}
...
}
```
After unfreezing, the `owner` account can transact freely again.
***
## Step 6. Wipe a Token
Let's update our contract by:
* Adding a new function `wipeTokenFromAccount` to wipe the NFT from an account. This effectively burns that token (i.e., reduces the total supply) from a non-treasury account.
For [**fungible tokens**](/support/glossary#fungible-token), specify an amount to wipe; for [**non-fungible tokens (NFTs)**](/support/glossary#non-fungible-token-nft), we specify the serial numbers to be wiped.
* We will also define a new event `TokenWiped`.
#### **Key Code Snippet:**
```solidity contracts/MyHTSTokenPFWD.sol theme={null}
contract MyHTSTokenPFWD is HederaTokenService, KeyHelper, Ownable {
...
event TokenWiped(address indexed account, int64[] serialNumbers);
...
function wipeTokenFromAccount(
address account,
int64[] memory serialNumbers
) external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = wipeTokenAccountNFT(
tokenAddress,
account,
serialNumbers
);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: wipe token failed"
);
emit TokenWiped(account, serialNumbers);
}
...
}
```
***
## Step 7. Delete a Token
Let's update our contract by:
* Adding a new function `deleteToken` to delete the token. This renders a token completely unusable for future operations. The token still exists on the ledger (you can query it), but all transactions (e.g., minting, transfers, etc.) will fail. The **ADMIN** key is required to delete.
* We will also define a new event `TokenDeleted`.
#### **Key Code Snippet**:
```solidity contracts/MyHTSTokenPFWD.sol theme={null}
contract MyHTSTokenPFWD is HederaTokenService, KeyHelper, Ownable {
...
event TokenDeleted();
...
function deleteToken() external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = deleteToken(tokenAddress);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: delete token failed"
);
emit TokenDeleted();
}
...
}
```
Once deleted, attempting further operations like minting will fail.
Here's the complete contract code for `MyHTSTokenPFWD.sol`:
```solidity contracts/MyHTSTokenPFWD.sol wrap theme={null}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;
// Admin/ownership like the OZ example
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
// Read/transfer via ERC721 facade exposed at the HTS token EVM address
import {IERC721} from "@openzeppelin/contracts/interfaces/IERC721.sol";
// Hedera HTS system contracts (as in your setup)
// Hedera HTS system contracts (v1, NOT v2)
import {HederaTokenService} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/HederaTokenService.sol";
import {IHederaTokenService} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/IHederaTokenService.sol";
import {HederaResponseCodes} from "@hashgraph/smart-contracts/contracts/system-contracts/HederaResponseCodes.sol";
import {KeyHelper} from "@hashgraph/smart-contracts/contracts/system-contracts/hedera-token-service/KeyHelper.sol";
/**
* HTS-backed ERC721-like collection:
* - Creates the HTS NFT collection in the constructor (like deploying an ERC721).
* - SUPPLY key = this contract (mint/burn only via contract).
* - ADMIN key = this contract (admin updates only via contract).
* - PAUSE key = this contract (pause/unpause via contract).
* - FREEZE key = this contract (freeze/unfreeze via contract).
* - WIPE key = this contract (wipe via contract).
* - Holders use the token’s ERC721 facade directly (SDK or EVM).
*/
contract MyHTSTokenPFWD is HederaTokenService, KeyHelper, Ownable {
// Underlying HTS NFT token EVM address (set during initialize. This is the "ERC721-like" token)
address public tokenAddress;
// Cosmetic copies for convenience (optional)
string public name;
string public symbol;
// Small non-empty default metadata for simple mints (<=100 bytes as per HTS limit)
bytes private constant DEFAULT_METADATA = hex"01";
uint256 private constant INT64_MAX = 0x7fffffffffffffff;
event NFTCollectionCreated(address indexed token);
event NFTMinted(
address indexed to,
uint256 indexed tokenId,
int64 newTotalSupply
);
event NFTBurned(uint256 indexed tokenId, int64 newTotalSupply);
event TokenPaused();
event TokenUnpaused();
event AccountFrozen(address indexed account);
event AccountUnfrozen(address indexed account);
event TokenWiped(address indexed account, int64[] serialNumbers);
event TokenDeleted();
event HBARReceived(address indexed from, uint256 amount);
event HBARFallback(address sender, uint256 amount, bytes data);
event HBARWithdrawn(address indexed to, uint256 amount);
/**
* Constructor sets ownership.
* Actual HTS token creation happens in createNFTCollection().
*/
constructor() Ownable(msg.sender) {}
/**
* Creates the HTS NFT collection with custom fees.
* Can be called exactly once by the owner after deployment.
*
* @param _name Token/collection name
* @param _symbol Token/collection symbol
*/
function createNFTCollection(
string memory _name,
string memory _symbol
) external payable onlyOwner {
require(tokenAddress == address(0), "Already initialized");
name = _name;
symbol = _symbol;
// Build token definition
IHederaTokenService.HederaToken memory token;
token.name = name;
token.symbol = symbol;
token.treasury = address(this);
token.memo = "";
// Keys: SUPPLY + ADMIN/DELETE + PAUSE + FREEZE + WIPE -> contractId
IHederaTokenService.TokenKey[]
memory keys = new IHederaTokenService.TokenKey[](5);
keys[0] = getSingleKey(
KeyType.SUPPLY,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[1] = getSingleKey(
KeyType.ADMIN,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[2] = getSingleKey(
KeyType.PAUSE,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[3] = getSingleKey(
KeyType.FREEZE,
KeyValueType.CONTRACT_ID,
address(this)
);
keys[4] = getSingleKey(
KeyType.WIPE,
KeyValueType.CONTRACT_ID,
address(this)
);
token.tokenKeys = keys;
(int rc, address created) = createNonFungibleToken(token);
require(rc == HederaResponseCodes.SUCCESS, "HTS: create NFT failed");
tokenAddress = created;
emit NFTCollectionCreated(created);
}
// ---------------------------------------------------------------------------
// ERC721-like minting (admin via Ownable + SUPPLY key on contract)
// ---------------------------------------------------------------------------
// Minimal API parity: mintNFT(to) onlyOwner -> returns new tokenId (serial)
function mintNFT(address to) public onlyOwner returns (uint256) {
return _mintAndSend(to, DEFAULT_METADATA);
}
// Optional overload with custom metadata (<= 100 bytes)
function mintNFT(
address to,
bytes memory metadata
) public onlyOwner returns (uint256) {
require(metadata.length <= 100, "HTS: metadata >100 bytes");
return _mintAndSend(to, metadata);
}
function _mintAndSend(
address to,
bytes memory metadata
) internal returns (uint256 tokenId) {
require(tokenAddress != address(0), "HTS: not created");
// 1) Mint to treasury (this contract)
bytes[] memory arr = new bytes[](1);
arr[0] = metadata;
(int rc, int64 newTotalSupply, int64[] memory serials) = mintToken(
tokenAddress,
0,
arr
);
require(
rc == HederaResponseCodes.SUCCESS && serials.length == 1,
"HTS: mint failed"
);
// 2) Transfer from treasury -> recipient via ERC721 facade
uint256 serial = uint256(uint64(serials[0]));
// Recipient must be associated (or have auto-association available)
IERC721(tokenAddress).transferFrom(address(this), to, serial);
emit NFTMinted(to, serial, newTotalSupply);
return serial;
}
// ---------------------------------------------------------------------------
// ERC721Burnable-like flow for holders
// ---------------------------------------------------------------------------
// Holder-initiated burn:
// - User approves this contract for tokenId (approve or setApprovalForAll)
// - Calls burn(tokenId); contract pulls to treasury and burns via HTS
// Allows onlyOwner to burn when the NFT is already in treasury,
// avoiding the need for ERC721 approvals in that case.
function burnNFT(uint256 tokenId) external {
require(tokenAddress != address(0), "HTS: not created");
address owner_ = IERC721(tokenAddress).ownerOf(tokenId);
// Match ERC721Burnable semantics: only the token owner or an approved operator may trigger burn
require(
msg.sender == owner_ ||
IERC721(tokenAddress).getApproved(tokenId) == msg.sender ||
IERC721(tokenAddress).isApprovedForAll(owner_, msg.sender),
"caller not owner nor approved"
);
// If not already in treasury, ensure this contract is approved to pull the token and then pull it
if (owner_ != address(this)) {
bool contractApproved = IERC721(tokenAddress).getApproved(
tokenId
) ==
address(this) ||
IERC721(tokenAddress).isApprovedForAll(owner_, address(this));
require(contractApproved, "contract not approved to transfer");
IERC721(tokenAddress).transferFrom(owner_, address(this), tokenId);
}
// Burn via HTS (requires token to be in treasury)
int64[] memory serials = new int64[](1);
serials[0] = _toI64(tokenId);
(int rc, int64 newTotalSupply) = burnToken(tokenAddress, 0, serials);
require(rc == HederaResponseCodes.SUCCESS, "HTS: burn failed");
emit NFTBurned(tokenId, newTotalSupply);
}
function pauseToken() external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = pauseToken(tokenAddress);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: pause token failed"
);
emit TokenPaused();
}
function unpauseToken() external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = unpauseToken(tokenAddress);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: unpause token failed"
);
emit TokenUnpaused();
}
function freezeAccount(address account) external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = freezeToken(tokenAddress, account);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: freeze account failed"
);
emit AccountFrozen(account);
}
function unfreezeAccount(address account) external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = unfreezeToken(tokenAddress, account);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: unfreeze account failed"
);
emit AccountUnfrozen(account);
}
function wipeTokenFromAccount(
address account,
int64[] memory serialNumbers
) external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = wipeTokenAccountNFT(
tokenAddress,
account,
serialNumbers
);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: wipe token failed"
);
emit TokenWiped(account, serialNumbers);
}
function deleteToken() external onlyOwner {
require(tokenAddress != address(0), "HTS: not created");
int response = deleteToken(tokenAddress);
require(
response == HederaResponseCodes.SUCCESS,
"HTS: delete token failed"
);
emit TokenDeleted();
}
// ---------------------------------------------------------------------------
// HBAR handling
// ---------------------------------------------------------------------------
// Accept HBAR
receive() external payable {
emit HBARReceived(msg.sender, msg.value);
}
fallback() external payable {
emit HBARFallback(msg.sender, msg.value, msg.data);
}
function withdrawHBAR() external onlyOwner {
uint256 balance = address(this).balance;
require(balance > 0, "No HBAR to withdraw");
(bool success, ) = owner().call{value: balance}("");
require(success, "Failed to withdraw HBAR");
emit HBARWithdrawn(owner(), balance);
}
// --------------------- internal helpers ---------------------
function _toI64(uint256 x) internal pure returns (int64) {
require(x <= INT64_MAX, "cast: > int64.max");
return int64(uint64(x));
}
}
```
***
## Step 8: Deploy Your HTS NFT Smart Contract
Create a deployment script (`deployPFWD.ts`) in `scripts` directory:
```typescript scripts/deployPWD.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [deployer] = await ethers.getSigners();
console.log("Deploying contract with the account:", deployer.address);
// 1) Deploy the PFWD wrapper contract
const MyHTSTokenPFWD = await ethers.getContractFactory(
"MyHTSTokenPFWD",
deployer
);
const contract = await MyHTSTokenPFWD.deploy();
await contract.waitForDeployment();
// 2) Create the HTS NFT collection by calling createNFTCollection()
const NAME = "MyHTSTokenPFWDCollection";
const SYMBOL = "MHTPFWD";
const HBAR_TO_SEND = "15"; // HBAR to send with createNFTCollection()
console.log(
`Calling createNFTCollection() with ${HBAR_TO_SEND} HBAR to create the HTS collection...`
);
const tx = await contract.createNFTCollection(NAME, SYMBOL, {
gasLimit: 350_000,
value: ethers.parseEther(HBAR_TO_SEND),
});
await tx.wait();
console.log("createNFTCollection() tx hash:", tx.hash);
// 3) Read the created HTS token address
const contractAddress = await contract.getAddress();
console.log("MyHTSTokenPFWD contract deployed at:", contractAddress);
const tokenAddress = await contract.tokenAddress();
console.log(
"Underlying HTS NFT Collection (ERC721 facade) address:",
tokenAddress
);
}
main().catch(console.error);
```
In this script, we first retrieve your account (the deployer) using Ethers.js. This account will own the deployed smart contract. Next, we use this account to deploy the contract by calling `MyHTSTokenPFWD.deploy()`.
**Note**
For most HTS [System Smart Contract](/evm/hedera-services/system-contracts) calls, an HBAR value **is not** required to be sent in the contract call; the gas fee will cover it. However, for expensive transactions, like [Create HTS NFT Collection](#step-3%3A-deploy-your-hts-nft-smart-contract), the gas fee is reduced, and the transaction cost is covered by the payable amount. This is to reduce the gas consumed by the contract call.
Deploy your contract by executing the script:
```bash theme={null}
npx hardhat run scripts/deployPFWD.ts --network testnet
```
Copy the deployed address—you'll need this in subsequent steps.
The output looks like this:
```bash theme={null}
Deploying contract with the account: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Calling createNFTCollection() with 15 HBAR to create the HTS collection...
createNFTCollection() tx hash: 0xaece990a241306d6c3e506347406232d1209e2e7037ccb3c808d872a3c91b280
MyHTSTokenPFWD contract deployed at: 0xFe70397079f479539977F60340ffa68Ff41d520f
Underlying HTS NFT Collection (ERC721 facade) address: 0x000000000000000000000000000000000068d4fd
```
## Step 9: Minting an HTS NFT
Create a `mintNFTPFWD.ts` script in your `scripts` directory to mint an NFT. Don't forget to replace the `` with the address you've just copied.
```typescript scripts/mintNFTPFWD.ts theme={null}
import { network } from "hardhat";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [signer] = await ethers.getSigners();
console.log("Using signer:", signer.address);
// Replace with your deployed MyHTSTokenPFWD contract address
const contractAddress = "";
const recipient = signer.address;
const myHTSTokenPFWDContract = await ethers.getContractAt(
"MyHTSTokenPFWD",
contractAddress,
signer
);
// Display the underlying HTS token address
const tokenAddress = await myHTSTokenPFWDContract.tokenAddress();
console.log("HTS ERC721 facade address:", tokenAddress);
// 1) Associate the signer via token.associate() (EOA -> token contract)
const tokenAssociateAbi = ["function associate()"];
const token = new ethers.Contract(tokenAddress, tokenAssociateAbi, signer);
console.log("Associating signer to token via token.associate() ...");
const assocTx = await token.associate({ gasLimit: 800_000 });
await assocTx.wait();
console.log("Associate tx hash:", assocTx.hash);
// 2) Prepare metadata (<= 100 bytes)
const metadata = ethers.hexlify(
ethers.toUtf8Bytes(
"ipfs://bafkreibr7cyxmy4iyckmlyzige4ywccyygomwrcn4ldcldacw3nxe3ikgq"
)
);
const byteLen = ethers.getBytes(metadata).length;
if (byteLen > 100) {
throw new Error(
`Metadata is ${byteLen} bytes; must be <= 100 bytes for HTS`
);
}
// 3) Mint the NFT via the wrapper (wrapper holds supply key)
console.log(`Minting NFT to ${recipient} with metadata: ${metadata} ...`);
// Note: Our mintNFT function is overloaded; we must use this syntax to disambiguate
// or we get a typescript error.
const tx = await myHTSTokenPFWDContract["mintNFT(address,bytes)"](
recipient,
metadata,
{
gasLimit: 400_000,
}
);
await tx.wait();
console.log("Mint tx hash:", tx.hash);
// Check recipient's NFT balance on the ERC721 facade (not on MyHTSTokenPFWD)
const erc721 = new ethers.Contract(
tokenAddress,
["function balanceOf(address owner) view returns (uint256)"],
signer
);
const balance = (await erc721.balanceOf(recipient)) as bigint;
console.log("Balance:", balance.toString(), "NFTs");
}
main().catch(console.error);
```
**How It Works**
1. Connects to Hedera testnet, gets the first signer, and attaches to your deployed MyHTSTokenPFWD contract.
2. Reads the underlying HTS ERC721 facade address (tokenAddress) from the contract.
3. Associates the signer via `token.associate()`(EOA -> token contract)
4. Constructs \<=100-byte UTF-8 metadata and calls mintNFT(recipient, metadata), then waits for the transaction receipt.
5. Mints NFT to recipient
6. Queries balanceOf(recipient) on the ERC721 facade and logs the current NFT count.
The code mints a new NFT to your account ( `signer.address` ). Then we verify the balance to see if we own an HTS NFT.
Mint an NFT:
```bash theme={null}
npx hardhat run scripts/mintNFTPFWD.ts --network testnet
```
Expected output:
```bash theme={null}
Using signer: 0xA98556A4deeB07f21f8a66093989078eF86faa30
HTS ERC721 facade address: 0x000000000000000000000000000000000068d4fd
Associating signer to token via token.associate() ...
Associate tx hash: 0xa99f461511b1dc497aaa1a03234dfd915b531cb4433eacb27fb63006f5310bdf
Minting NFT to 0xA98556A4deeB07f21f8a66093989078eF86faa30 with metadata: 0x697066733a2f2f6261666b7265696272376379786d79346979636b6d6c797a69676534797763637979676f6d7772636e346c64636c64616377336e786533696b6771 ...
Mint tx hash: 0x71e5e810ef098ceb45542e29a2c2f88dedf078f92b35847021e982ef5059c6cc
Balance: 1 NFTs
```
***
## Step 10: Burning an HTS NFT
Create a burn script (`burnNFTPFWD.ts` ) in your `scripts` directory. Make sure to replace `` to the MyHTSToken contract address you got from deploying and replace `` with the tokenId you want to burn(eg. "1") :
```typescript scripts/burnNFTPFWD.ts theme={null}
import { network } from "hardhat";
import type { ContractTransactionResponse } from "ethers";
const { ethers } = await network.connect({ network: "testnet" });
async function main() {
const [signer] = await ethers.getSigners();
console.log("Using signer:", signer.address);
// Replace with your deployed MyHTSTokenPFWD contract address and the tokenId to burn
const contractAddress = "";
const tokenId = BigInt("");
const myHTSTokenPFWDContract = await ethers.getContractAt(
"MyHTSTokenPFWD",
contractAddress,
signer
);
const tokenAddress: string = await myHTSTokenPFWDContract.tokenAddress();
console.log("HTS ERC721 facade address:", tokenAddress);
// Minimal ERC721 ABI for approvals and balance
const erc721 = new ethers.Contract(
tokenAddress,
[
"function approve(address to, uint256 tokenId) external",
"function getApproved(uint256 tokenId) external view returns (address)",
"function ownerOf(uint256 tokenId) external view returns (address)",
"function balanceOf(address owner) external view returns (uint256)",
],
signer
);
const ownerOfToken: string = await erc721.ownerOf(tokenId);
console.log("Current owner of token:", ownerOfToken);
// Check if already approved for this tokenId; if not, approve MyHTSTokenPFWD contract
const currentApproved: string = await erc721.getApproved(tokenId);
if (currentApproved.toLowerCase() !== contractAddress.toLowerCase()) {
console.log(
`Approving MyHTSTokenPFWD contract ${contractAddress} for tokenId ${tokenId.toString()}...`
);
const approveTx = (await erc721.approve(
contractAddress,
tokenId
)) as unknown as ContractTransactionResponse;
await approveTx.wait();
console.log("Approval tx hash:", approveTx.hash);
} else {
console.log(
"MyHTSTokenPFWD contract is already approved for this tokenId."
);
}
// Burn via MyHTSTokenPFWD
console.log(`Burning tokenId ${tokenId.toString()}...`);
const burnTx = (await myHTSTokenPFWDContract.burnNFT(tokenId, {
gasLimit: 200_000,
})) as unknown as ContractTransactionResponse;
await burnTx.wait();
console.log("Burn tx hash:", burnTx.hash);
// Show caller's balance after burn
const balanceAfter = (await erc721.balanceOf(signer.address)) as bigint;
console.log("Balance after burn:", balanceAfter.toString(), "NFTs");
}
main().catch(console.error);
```
**How It Works**
1. Connects to Hedera testnet, gets the signer, attaches to MyHTSTokenPFWD, and reads the ERC721 facade tokenAddress.
2. Checks token ownership and existing approval; if needed, approves the MyHTSTokenPFWD contract for the specific tokenId.
3. Calls burnNFT(tokenId) on MyHTSTokenPFWD and waits for the transaction receipt.
4. Reads and logs the signer’s NFT balance from the ERC721 facade after the burn.
The script will burn the HTS NFT with the ID set to `1`, which is the HTS NFT you've just minted. To be sure the token has been deleted, let's print the balance for our account to the terminal. The balance should show a balance of `0`.
Burn the NFT:
```bash theme={null}
npx hardhat run scripts/burnNFTPFWD.ts --network testnet
```
You should get an output similar to:
```bash theme={null}
Using signer: 0xA98556A4deeB07f21f8a66093989078eF86faa30
HTS ERC721 facade address: 0x000000000000000000000000000000000068d4fd
Current owner of token: 0xA98556A4deeB07f21f8a66093989078eF86faa30
Approving MyHTSTokenPFWD contract 0xFe70397079f479539977F60340ffa68Ff41d520f for tokenId 1...
Approval tx hash: 0x25b3db7091071adc56ec08ec3b15d341d535e8e7e2928d64890cfea259d2e9ce
Burning tokenId 1...
Burn tx hash: 0x6376fb7fee4278cac286c72d81dc787ed8e2e052b1ae719e2bb4dcf8bb279b30
Balance after burn: 0 NFTs
```
**Congratulations! 🎉 You have successfully learned how to deploy an HTS NFT collection smart contract using Hardhat, OpenZeppelin, and Ethers. Feel free to reach out in** [**Discord**](https://hedera.com/discord)**!**
## Step 11: Run tests(Optional)
You can find both types of tests in the [**Hedera-Code-Snippets repository**](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hts-evm-mint-nfts). You will find the following files:
* `contracts/MyHTSTokenPFWD.t.sol`
- **Ownership and access control:** Verifies the constructor sets owner
correctly and onlyOwner is enforced for
create/pause/unpause/freeze/unfreeze/wipe/delete (non-owners revert with
OwnableUnauthorizedAccount). \* **Pre-creation guards and validation:** Ensures
all HTS-dependent functions revert with "HTS: not created" before collection
setup, and rejects minting when metadata exceeds 100 bytes. \* **Native HBAR
flow:** Confirms the contract can receive HBAR (HBARReceived event), blocks
non-owner withdrawals, and allows the owner to withdraw all HBAR
(HBARWithdrawn event) leaving balance at zero.
* `test/MyHTSTokenPFWD.ts`
- **End-to-end setup:** Deploys the PFWD wrapper, creates the HTS NFT
collection (with PAUSE/FREEZE/WIPE/ADMIN keys), retrieves the ERC721 facade
address, generates/funds a second wallet, and associates both accounts
on-chain via token.associate(). \* **Minting and event parsing**: Mints NFT to
deployer (tokenIdA) and to user2 (tokenIdB), parsing the NFTMinted event from
wrapper logs; validates ownership/balances via the ERC721 facade. \*
**Pause/unpause lifecycle:** Asserts transfers revert while paused and succeed
after unpausing, confirming correct enforcement of the PAUSE key. \*
**Freeze/unfreeze enforcement:** Freezes user2 to block outgoing transfers,
then unfreezes and verifies transfers succeed again, demonstrating
account-level restrictions. \* **Cleanup and failure handling:** Attempts a
wipe of user2’s token (freezing if necessary) and falls back to user-approved
burn if wipe isn’t permitted; then approves and burns the remaining token,
deletes the token (when supply is zero), and verifies subsequent mints fail.
Copy these files and then run the tests:
```bash theme={null}
# This will run the tests via hardhat
npx hardhat test solidity
# This will run the tests via hedera testnet as the precompiles
# are not available on hardhat locally and we must use the testnet
npx hardhat test mocha
```
You can also run both the solidity and mocha tests altogether:
```bash theme={null}
npx hardhat test
```
***
## Conclusion
In this guide, you saw how to replicate key HTS operations (pause, freeze, wipe, delete) **directly in a Solidity contract** by calling the HTS System Contract functions on Hedera. This approach provides fine-grained control via the contract’s ownership and key management, which is especially useful if you need all relevant HTS functionality in a single deployable smart contract.
**Key Takeaways:**
* If you want to perform the respective operations later, you must set the **ADMIN**, **FREEZE**, **PAUSE**, **WIPE**, and **SUPPLY** keys when creating a token via a contract.
* Any account that needs to receive or send the token must be associated with it.
* Pausing affects **all** operations globally while freezing targets a **single** account.
* Wiping NFTs effectively burns them, reducing total supply.
* Deleting a token makes it unusable for future operations but remains queryable on the ledger.
***
## Additional Resources
Check out our GitHub repo to find the full contract and Hardhat test scripts, along with the configuration files you need to deploy and test on Hedera!
* [Full Contract and Tests Repository](https://github.com/hedera-dev/hedera-code-snippets/tree/main/hts-evm-mint-nfts)
[GitHub](https://github.com/jaycoolh) | [X](https://x.com/jaycoolh)
[GitHub](https://github.com/michielmulders) |
[LinkedIn](https://www.linkedin.com/in/michielmulders/)
[GitHub](https://github.com/theekrystallee) |
[X](https://x.com/theekrystallee)
[GitHub](https://github.com/kpachhai) |
[LinkedIn](https://www.linkedin.com/in/kiranpachhai/)
# Hybrid (HTS + EVM ) Tokenization
Source: https://docs.hedera.com/evm/tutorials/hedera/hybrid-hts-evm
## **Hybrid Tokenization: Combining HTS and Smart Contracts**
Hedera's system contracts allow EVM-based smart contracts to interact directly with HTS tokens. This integration enables smart contracts to manage HTS tokens as if they were standard ERC tokens, facilitating complex interactions and programmability. For example, the Hedera Account Service (HAS) system contract introduces an account proxy to interact with other contracts, enabling functionalities such as HBAR allowances and authorization checks directly within smart contracts.
By combining these features, Hedera provides a robust platform for developers to leverage both native token services and EVM-based smart contracts, ensuring scalability, security, and interoperability within the blockchain ecosystem.
### Smart Contract-Based Token Management
Smart contracts provide programmable, self-executing contracts to create, manage, and enforce conditions for tokens. Tokenized assets managed by smart contracts could represent various types of assets, such as cryptocurrencies, non-fungible tokens, and real-world assets (RWAs). Secure transfer and complex interactions across decentralized apps (dApps) are facilitated by tokens, beyond mere transactions.
Ethereum’s ERC-20 (fungible tokens) and ERC-721 (non-fungible tokens) standards offer universal interfaces, ensuring compatibility across exchanges, wallets, and dApps. Developers find it convenient to implement by adhering to these standards, while predictable platform behavior is guaranteed.
Hedera extends this compatibility further by allowing HTS-native tokens to act as ERC-20 or ERC-721. This makes it possible to make minimal, if any, adjustments while deploying EVM smart contracts on Hedera while still tapping HTS's native efficiencies, such as low-cost transactions and compliant-by-default.
***
## How HTS and the EVM Work Together
### **Token Creation & Management with HTS**
Hedera offers the Hedera API (HAPI), granting comprehensive access to services like account management, token transactions, and consensus. Developers can utilize Hedera SDKs to perform actions such as token transfers, contract calls, and consensus messaging. HTS is used for native token issuance, transfers, and compliance controls, ensuring fast and efficient transactions.;
* Mint, burn, and transfer tokens with low fees.
* Use built-in compliance tools (KYC, Freeze, Pause, Wipe).
* Enable atomic swaps between HTS tokens and HBAR.
### **Advanced Logic & Automation with Smart Contracts**
Solidity smart contracts add programmability to token operations, allowing developers to:
* Define automated rules for token transfers, staking, or rewards.
* Integrate with DeFi applications using lending, swaps, and pooling logic.
* Enforce complex business logic for RWAs, gaming economies, and NFT royalties.
HTS provides efficiency, while smart contracts enable custom behavior.
***
## **System Contracts for Direct HTS Token Interactions**
Hedera's system contracts allow EVM-based smart contracts to interact directly with HTS tokens. This integration enables smart contracts to manage HTS tokens as if they were standard ERC tokens, facilitating complex interactions and programmability. For example, the Hedera Account Service (HAS) system contract introduces an account proxy to interact with other contracts, enabling functionalities such as HBAR allowances and authorization checks directly within smart contracts. By leveraging system contracts, developers can:
* Hold and manage HTS tokens within smart contracts, just like ERC-20 and ERC-721 tokens.
* Transfer HTS tokens using EVM-based logic, enabling seamless token operations.
* Access Hedera accounts within smart contracts, unlocking new dApp functionalities.
This native integration eliminates the need for custom bridges or complex workarounds, making HTS token management within smart contracts more efficient and developer-friendly.
***
## **HTS vs. Smart Contract Performance Features**
By combining these features, Hedera provides a robust platform for developers to leverage both native token services and EVM-based smart contracts, ensuring scalability, security, and interoperability within the blockchain ecosystem.
Feature
HTS-Native Tokens
Smart Contract Tokens
Hybrid Approach
Transaction Speed
10,000+ TPS
\~350 TPS (gas-limited)
HTS speed with smart contract flexibility
Cost Efficiency
Fixed, low-cost fees
Higher gas costs
HTS transactions remain low-cost
Custom Logic
❌ Limited to built-in controls
✅ Fully programmable
✅ Smart contracts enhance HTS functionality
Compliance Features
✅ KYC, Freeze, Pause, Wipe
❌ Must be custom-coded
✅ Hybrid approach supports compliance via smart contracts
EVM Compatibility
✅ Via Facade Contracts
✅ Standard ERC-20/ERC-721
✅ HTS tokens accessible via smart contracts
For high-frequency transactions, HTS-native tokens provide superior performance and lower costs. For custom business logic, smart contract tokens offer greater flexibility. Hybrid tokenization lets developers leverage both.
# Create and Transfer an NFT using a Solidity Contract
Source: https://docs.hedera.com/evm/tutorials/hedera/nft-solidity
## Summary
Besides creating NFTs using Hedera SDK, you can use a Solidity Contract to create, mint, and transfer NFTs by calling contract functions directly. These are the contracts you will need to import into your working directory provided by Hedera that you can find in the **contracts** folder [here](https://github.com/hiero-ledger/hiero-contracts):
* HederaTokenService.sol
* HederaResponseCodes.sol
* IHederaTokenService.sol
* ExpiryHelper.sol
* FeeHelper.sol
* KeyHelper.sol
***
## Prerequisites
We recommend you complete the following introduction to get a basic understanding of Hedera transactions. This example does not build upon the previous examples.
1. Get a [Hedera testnet account](https://portal.hedera.com/register).
2. Set up your environment [here](/native/quickstart/javascript).
If you are interested in creating, minting, and transferring NFTs using Hedera SDKs you can find the example [here](https://docs.hedera.com/guides/getting-started/try-examples/create-and-transfer-your-first-nft).
In this example, you will set gas for smart contract transactions multiple times. If you don't have enough gas you will receive an`INSUFFICIENT_GAS` response. If you set the value too high you will be refunded a maximum of 20% of the amount that was set for the transaction.
***
## 1. Create an “NFT Creator” Smart Contract
You can find an NFTCreator Solidity contract sample below with the contract bytecode obtained by compiling the solidity contract using [Remix IDE](https://remix.ethereum.org/). If you are not familiar with Solidity, you can take a look at the docs [here](https://docs.soliditylang.org/en/v0.8.14/).
The following contract is composed of three functions:
* `createNft`
* `mintNft`
* `transferNft`
The important thing to know is that the NFT created in this example will have the contract itself as **Treasury Account, Supply Key,** and **Auto-renew account**. There’s **NO** admin key for the NFT or the contract.
```solidity NFTCreator.sol theme={null}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.5.0 <0.9.0;
import "./HederaResponseCodes.sol";
import "./IHederaTokenService.sol";
import "./HederaTokenService.sol";
import "./ExpiryHelper.sol";
import "./KeyHelper.sol";
contract NFTCreator is ExpiryHelper, KeyHelper, HederaTokenService {
function createNft(
string memory name,
string memory symbol,
string memory memo,
int64 maxSupply,
int64 autoRenewPeriod
) external payable returns (address){
IHederaTokenService.TokenKey[] memory keys = new IHederaTokenService.TokenKey[](1);
// Set this contract as supply for the token
keys[0] = getSingleKey(KeyType.SUPPLY, KeyValueType.CONTRACT_ID, address(this));
IHederaTokenService.HederaToken memory token;
token.name = name;
token.symbol = symbol;
token.memo = memo;
token.treasury = address(this);
token.tokenSupplyType = true; // set supply to FINITE
token.maxSupply = maxSupply;
token.tokenKeys = keys;
token.freezeDefault = false;
token.expiry = createAutoRenewExpiry(address(this), autoRenewPeriod); // Contract auto-renews the token
(int responseCode, address createdToken) = HederaTokenService.createNonFungibleToken(token);
if(responseCode != HederaResponseCodes.SUCCESS){
revert("Failed to create non-fungible token");
}
return createdToken;
}
function mintNft(
address token,
bytes[] memory metadata
) external returns(int64){
(int response, , int64[] memory serial) = HederaTokenService.mintToken(token, 0, metadata);
if(response != HederaResponseCodes.SUCCESS){
revert("Failed to mint non-fungible token");
}
return serial[0];
}
function transferNft(
address token,
address receiver,
int64 serial
) external returns(int){
int response = HederaTokenService.transferNFT(token, address(this), receiver, serial);
if(response != HederaResponseCodes.SUCCESS){
revert("Failed to transfer non-fungible token");
}
return response;
}
}
```
Store your contract on Hedera using `ContractCreateFlow()`. This single call performs `FileCreateTransaction()`,`FileAppendTransaction()`, and `ContractCreateTransaction()` for you. See the difference [here](https://docs.hedera.com/guides/docs/sdks/smart-contracts/create-a-smart-contract).
```java Java theme={null}
// Create contract
ContractCreateFlow createContract = new ContractCreateFlow()
.setBytecode(bytecode) // Contract bytecode
.setGas(4_000_000); // Increase if revert
TransactionResponse createContractTx = createContract.execute(client);
TransactionReceipt createContractRx = createContractTx.getReceipt(client);
// Get the new contract ID
ContractId newContractId = createContractRx.contractId;
System.out.println("Contract created with ID: " + newContractId);
```
```javascript JavaScript theme={null}
// Create contract
const createContract = new ContractCreateFlow()
.setGas(4000000) // Increase if revert
.setBytecode(bytecode); // Contract bytecode
const createContractTx = await createContract.execute(client);
const createContractRx = await createContractTx.getReceipt(client);
const contractId = createContractRx.contractId;
console.log(`Contract created with ID: ${contractId} \n`);
```
```go Go theme={null}
//Create the transaction
createContract := hedera.NewContractCreateFlow().
SetGas(4000000).
SetBytecode([]byte(bytecode))
//Sign the transaction with the client operator key and submit to a Hedera network
txResponse, err := createContract.Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the contract ID
newContractId := *receipt.ContractID
fmt.Printf("The new contract ID is %v\n", newContractId)
```
## 3. Execute the Contract to Create an NFT
The parameters you need to specify for this contract call are Name, Symbol, Memo, Maximum Supply, and Expiration. The smart contract "rent" feature is currently *NOT* enabled. Once enabled in the future, setting an expiration date *in seconds* is required because entities on Hedera will need to pay "rent" to persist. In this case, the contract entity will pay all NFT auto-renewal fees.
**Note:** The expiration must be between 82 and 91 days, **specified in seconds**. This window will change when [HIP-372](https://hips.hedera.com/hip/hip-372) replaces [HIP-16](https://hips.hedera.com/hip/hip-16).
```java Java theme={null}
// Create NFT using contract
ContractExecuteTransaction createToken = new ContractExecuteTransaction()
.setContractId(newContractId) // Contract id
.setGas(4_000_000) // Increase if revert
.setPayableAmount(new Hbar(50)) // Increase if revert
.setFunction("createNft", new ContractFunctionParameters()
.addString("Fall Collection") // NFT Name
.addString("LEAF") // NFT Symbol
.addString("Just a memo") // NFT Memo
.addInt64(250) // NFT max supply
.addInt64(7_000_000)); // Expiration: Needs to be between 6999999 and 8000001
TransactionResponse createTokenTx = createToken.execute(client);
TransactionRecord createTokenRx = createTokenTx.getRecord(client);
String tokenIdSolidityAddr = createTokenRx.contractFunctionResult.getAddress(0);
AccountId tokenId = AccountId.fromSolidityAddress(tokenIdSolidityAddr);
System.out.println("Token created with ID: " + tokenId);
```
```javascript JavaScript theme={null}
// Create NFT from precompile
const createToken = new ContractExecuteTransaction()
.setContractId(contractId)
.setGas(4000000) // Increase if revert
.setPayableAmount(50) // Increase if revert
.setFunction("createNft",
new ContractFunctionParameters()
.addString("Fall Collection") // NFT name
.addString("LEAF") // NFT symbol
.addString("Just a memo") // NFT memo
.addInt64(250) // NFT max supply
.addInt64(7000000) // Expiration: Needs to be between 6999999 and 8000001
);
const createTokenTx = await createToken.execute(client);
const createTokenRx = await createTokenTx.getRecord(client);
const tokenIdSolidityAddr = createTokenRx.contractFunctionResult.getAddress(0);
const tokenId = AccountId.fromSolidityAddress(tokenIdSolidityAddr);
console.log(`Token created with ID: ${tokenId} \n`);
```
```go Go theme={null}
contractParams := hedera.NewContractFunctionParameters().
AddString("Fall Collection"). // NFT name
AddString("LEAF"). // NFT symbol
AddString("Just a memo"). // NFT memo
AddInt64(250). // NFT max supply
AddInt64(7000000) // Expiration: Needs to be between 6999999 and 8000001
//Create NFT
createToken, err := hedera.NewContractExecuteTransaction().
//The contract ID
SetContractID(newContractId).
//The max gas
SetGas(4000000).
SetPayableAmount(hedera.NewHbar(50)).
//The contract function to call and parameters
SetFunction("createNft", contractParams).
Execute(client)
if err != nil {
panic(err)
}
//Get the record
txRecord, err := createToken.GetRecord(client)
if err != nil {
panic(err)
}
//Get transaction status
contractResult, err := txRecord.GetContractExecuteResult()
if err != nil {
panic(err)
}
tokenIdSolidityAddr := hex.EncodeToString(contractResult.GetAddress(0))
tokenId, err := hedera.AccountIDFromSolidityAddress(tokenIdSolidityAddr)
if err != nil {
panic(err)
}
fmt.Printf("Token created with ID: %v\n", tokenId)
```
## 4. Execute the Contract to Mint a New NFT
After the token ID is created, you mint each NFT under that ID using the `mintNft` function. For the minting, you must specify the token ID as a Solidity address and the NFT metadata.
Both the NFT image and metadata live in the InterPlanetary File System (IPFS), which provides decentralized storage. The file metadata.json contains the metadata for the NFT. An IPFS URI pointing to the metadata file is used during minting of a new NFT. Notice that the metadata file contains a URI pointing to the NFT image.
***Note:** For the latest NFT Token Metadata JSON Schema see* [*HIP-412*](https://hips.hedera.com/hip/hip-412)*.*
```java Java theme={null}
// Mint NFT
ContractExecuteTransaction mintToken = new ContractExecuteTransaction()
.setContractId(newContractId)
.setGas(4_000_000)
.setMaxTransactionFee(new Hbar(20)) //Use when HBAR is <10 cents
.setFunction("mintNft", new ContractFunctionParameters()
.addAddress(tokenIdSolidityAddr) // Token address
.addBytesArray(byteArray)); // Metadata
TransactionResponse mintTokenTx = mintToken.execute(client);
TransactionRecord mintTokenRx = mintTokenTx.getRecord(client);
// NFT serial number
long serial = mintTokenRx.contractFunctionResult.getInt64(0);
System.out.println("Minted NFT with serial: " + serial);
```
```javascript JavaScript theme={null}
// IPFS URI
metadata = "ipfs://bafyreie3ichmqul4xa7e6xcy34tylbuq2vf3gnjf7c55trg3b6xyjr4bku/metadata.json";
// Mint NFT
const mintToken = new ContractExecuteTransaction()
.setContractId(contractId)
.setGas(4000000)
.setMaxTransactionFee(new hbar(20)) //Use when HBAR is under 10 cents
.setFunction("mintNft",
new ContractFunctionParameters()
.addAddress(tokenIdSolidityAddr) // Token address
.addBytesArray([Buffer.from(metadata)]) // Metadata
);
const mintTokenTx = await mintToken.execute(client);
const mintTokenRx = await mintTokenTx.getRecord(client);
const serial = mintTokenRx.contractFunctionResult.getInt64(0);
console.log(`Minted NFT with serial: ${serial} \n`);
```
```go Go theme={null}
// ipfs URI
metadata := "ipfs://bafyreie3ichmqul4xa7e6xcy34tylbuq2vf3gnjf7c55trg3b6xyjr4bku/metadata.json"
bytesArray := [][]byte{}
bytesArray = append(bytesArray, []byte(metadata))
// Add token address to params
mintParams, err := hedera.NewContractFunctionParameters().
AddAddress(tokenIdSolidityAddr)
if err != nil {
panic(err)
}
// Add metadata to params
mintParams = mintParams.AddBytesArray(bytesArray)
// Mint NFT
mintToken, err := hedera.NewContractExecuteTransaction().
//The contract ID
SetContractID(newContractId).
//The max gas
SetGas(4000000).
//The contract function to call and parameters
SetFunction("mintNft", mintParams).
//The max transaction fee. Use when HBAR is under 10 cents
SetMaxTransactionFee(hedera.HbarFrom(20, hedera.HbarUnits.Hbar)).
Execute(client)
if err != nil {
panic(err)
}
//Get the record
mintRecord, err := mintToken.GetRecord(client)
if err != nil {
panic(err)
}
//Get transaction status
mintResult, err := mintRecord.GetContractExecuteResult()
if err != nil {
panic(err)
}
serial := mintResult.GetInt64(0)
fmt.Printf("Minted NFT with serial: %v\n", serial)
```
```json metadata.json theme={null}
{
"name": "LEAF1.jpg",
"creator": "Mother Nature",
"description": "Autumn",
"type": "image/jpg",
"format": "none",
"properties": {
"city": "Boston",
"season": "Fall",
"decade": "20's"
},
"image": "ipfs://bafybeig35bheyqpi4qlnuljpok54ud753bnp62fe6jit343hv3oxhgnbfm/LEAF1.jpg"
}
```
***
## 5. Execute the Contract to Transfer the NFT
The NFT is minted to the contract address because the contract is the treasury for the token. Now transfer the NFT to another account or contract address. In this example, you will transfer the NFT to Alice. For the transfer, you must specify the token address and NFT serial number.
The `transferNft` function in the Solidity contract contains a call to an `associateToken` function that will automatically associate Alice to the token ID. This association transaction must be signed using Alice's private key. After signing, Alice will receive the NFT.
**Note:** For a more comprehensive explanation of how auto token association works, check out the *Auto Token Associations* section [here](/learn/core-concepts/accounts/account-properties#automatic-token-associations). Reference Hedera Improvement Proposal: [HIP-23](https://hips.hedera.com/hip/hip-23)
```java Java theme={null}
// Transfer NFT to Alice
ContractExecuteTransaction transferToken = new ContractExecuteTransaction()
.setContractId(newContractId)
.setGas(4_000_000)
.setFunction("transferNft", new ContractFunctionParameters()
.addAddress(tokenIdSolidityAddr) // Token id
.addAddress(aliceId.toSolidityAddress()) // Token receiver (Alice)
.addInt64(serial)) // Serial number
.freezeWith(client) // Freeze transaction using client
.sign(aliceKey); //Sign using Alice Private Key
TransactionResponse transferTokenTx = transferToken.execute(client);
TransactionReceipt transferTokenRx = transferTokenTx.getReceipt(client);
System.out.println("Transfer status: " + transferTokenRx.status);
```
```javascript JavaScript theme={null}
// Transfer NFT to Alice
const transferToken = await new ContractExecuteTransaction()
.setContractId(contractId)
.setGas(4000000)
.setFunction("transferNft",
new ContractFunctionParameters()
.addAddress(tokenIdSolidityAddr) // Token address
.addAddress(aliceId.toSolidityAddress()) // Token receiver (Alice)
.addInt64(serial)) // NFT serial number
.freezeWith(client) // freezing using client
.sign(aliceKey); // Sign transaction with Alice
const transferTokenTx = await transferToken.execute(client);
const transferTokenRx = await transferTokenTx.getReceipt(client);
console.log(`Transfer status: ${transferTokenRx.status} \n`);
```
```go Go theme={null}
// Add token address to params
transferParams, err := hedera.NewContractFunctionParameters().
AddAddress(tokenIdSolidityAddr)
// Add Alice address to params
transferParams, err = transferParams.AddAddress(aliceAccountId.ToSolidityAddress())
if err != nil {
panic(err)
}
transferParams = transferParams.AddInt64(serial)
// Transfer NFT
transferToken, err := hedera.NewContractExecuteTransaction().
//The contract ID
SetContractID(newContractId).
//The max gas
SetGas(4000000).
//The contract function to call and parameters
SetFunction("transferNft", transferParams).
FreezeWith(client)
if err != nil {
panic(err)
}
transferSubmit, err := transferToken.Sign(aliceKey).Execute(client)
if err != nil {
panic(err)
}
//Get the record
transferRecord, err := transferSubmit.GetReceipt(client)
if err != nil {
panic(err)
}
fmt.Printf("Transfer status: %v\n", transferRecord.Status)
```
***
## Code Check ✅
```java theme={null}
package _nft_hscs_hts.hedera;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Objects;
import java.util.concurrent.TimeoutException;
import com.hedera.hashgraph.sdk.*;
import io.github.cdimascio.dotenv.Dotenv;
public class Deploy {
private static AccountId accountCreator(PrivateKey pvKey, int iBal, Client client)
throws TimeoutException, PrecheckStatusException, ReceiptStatusException {
AccountCreateTransaction transaction = new AccountCreateTransaction()
.setKey(pvKey.getPublicKey())
.setMaxAutomaticTokenAssociations(10)
.setInitialBalance(new Hbar(iBal));
TransactionResponse txResponse = transaction.execute(client);
TransactionReceipt receipt = txResponse.getReceipt(client);
return receipt.accountId;
}
public static void main(String[] args)
throws TimeoutException, PrecheckStatusException, ReceiptStatusException, IOException {
// ipfs URI
String metadata = ("ipfs://bafyreie3ichmqul4xa7e6xcy34tylbuq2vf3gnjf7c55trg3b6xyjr4bku/metadata.json");
byte[][] byteArray = new byte[1][metadata.length()];
byteArray[0] = metadata.getBytes();
AccountId operatorId = AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("ACCOUNT_ID")));
PrivateKey operatorKey = PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("PRIVATE_KEY")));
Client client = Client.forTestnet();
client.setOperator(operatorId, operatorKey);
PrivateKey aliceKey = PrivateKey.generateECDSA();
AccountId aliceId = accountCreator(aliceKey, 100, client);
System.out.print(aliceId);
String bytecode = Files.readString(Paths.get("./NFTCreator_sol_NFTCreator.bin"));
// Create contract
ContractCreateFlow createContract = new ContractCreateFlow()
.setBytecode(bytecode) // Contract bytecode
.setGas(4_000_000); // Increase if revert
TransactionResponse createContractTx = createContract.execute(client);
TransactionReceipt createContractRx = createContractTx.getReceipt(client);
// Get the new contract ID
ContractId newContractId = createContractRx.contractId;
System.out.println("Contract created with ID: " + newContractId);
// Create NFT using contract
ContractExecuteTransaction createToken = new ContractExecuteTransaction()
.setContractId(newContractId) // Contract id
.setGas(4_000_000) // Increase if revert
.setPayableAmount(new Hbar(50)) // Increase if revert
.setFunction("createNft", new ContractFunctionParameters()
.addString("Fall Collection") // NFT Name
.addString("LEAF") // NFT Symbol
.addString("Just a memo") // NFT Memo
.addInt64(250) // NFT max supply
.addInt64(7_000_000)); // Expiration: Needs to be between 6999999 and 8000001
TransactionResponse createTokenTx = createToken.execute(client);
TransactionRecord createTokenRx = createTokenTx.getRecord(client);
String tokenIdSolidityAddr = createTokenRx.contractFunctionResult.getAddress(0);
AccountId tokenId = AccountId.fromSolidityAddress(tokenIdSolidityAddr);
System.out.println("Token created with ID: " + tokenId);
// Mint NFT
ContractExecuteTransaction mintToken = new ContractExecuteTransaction()
.setContractId(newContractId)
.setGas(4_000_000)
.setMaxTransactionFee(new Hbar(20)) // Use when HBAR is <10 cents
.setFunction("mintNft", new ContractFunctionParameters()
.addAddress(tokenIdSolidityAddr) // Token address
.addBytesArray(byteArray)); // Metadata
TransactionResponse mintTokenTx = mintToken.execute(client);
TransactionRecord mintTokenRx = mintTokenTx.getRecord(client);
// NFT serial number
long serial = mintTokenRx.contractFunctionResult.getInt64(0);
System.out.println("Minted NFT with serial: " + serial);
// Transfer NFT to Alice
ContractExecuteTransaction transferToken = new ContractExecuteTransaction()
.setContractId(newContractId)
.setGas(4_000_000)
.setFunction("transferNft", new ContractFunctionParameters()
.addAddress(tokenIdSolidityAddr) // Token id
.addAddress(aliceId.toSolidityAddress()) // Token receiver (Alice)
.addInt64(serial)) // Serial number
.freezeWith(client) // Freeze transaction using client
.sign(aliceKey); // Sign using Alice Private Key
TransactionResponse transferTokenTx = transferToken.execute(client);
TransactionReceipt transferTokenRx = transferTokenTx.getReceipt(client);
System.out.println("Transfer status: " + transferTokenRx.status);
}
}
```
```javascript theme={null}
console.clear();
require("dotenv").config();
const fs = require("fs");
const {
AccountId,
PrivateKey,
Client,
ContractCreateFlow,
ContractExecuteTransaction,
ContractFunctionParameters,
AccountCreateTransaction,
Hbar,
} = require("@hashgraph/sdk");
// ipfs URI
metadata =
"ipfs://bafyreie3ichmqul4xa7e6xcy34tylbuq2vf3gnjf7c55trg3b6xyjr4bku/metadata.json";
const operatorKey = PrivateKey.fromString(process.env.OPERATOR_KEY);
const operatorId = AccountId.fromString(process.env.OPERATOR_ID);
const client = Client.forTestnet().setOperator(operatorId, operatorKey);
// Account creation function
async function accountCreator(pvKey, iBal) {
const response = await new AccountCreateTransaction()
.setInitialBalance(new Hbar(iBal))
.setKey(pvKey.publicKey)
.setMaxAutomaticTokenAssociations(10)
.execute(client);
const receipt = await response.getReceipt(client);
return receipt.accountId;
}
const main = async () => {
// Init Alice account
const aliceKey = PrivateKey.generateECDSA();
const aliceId = await accountCreator(aliceKey, 100);
const bytecode = fs.readFileSync("./binaries/NFTCreator_sol_NFTCreator.bin");
// Create contract
const createContract = new ContractCreateFlow()
.setGas(4000000) // Increase if revert
.setBytecode(bytecode); // Contract bytecode
const createContractTx = await createContract.execute(client);
const createContractRx = await createContractTx.getReceipt(client);
const contractId = createContractRx.contractId;
console.log(`Contract created with ID: ${contractId} \n`);
// Create NFT from precompile
const createToken = new ContractExecuteTransaction()
.setContractId(contractId)
.setGas(4000000) // Increase if revert
.setPayableAmount(50) // Increase if revert
.setFunction(
"createNft",
new ContractFunctionParameters()
.addString("Fall Collection") // NFT name
.addString("LEAF") // NFT symbol
.addString("Just a memo") // NFT memo
.addInt64(250) // NFT max supply
.addInt64(7000000) // Expiration: Needs to be between 6999999 and 8000001
);
const createTokenTx = await createToken.execute(client);
const createTokenRx = await createTokenTx.getRecord(client);
const tokenIdSolidityAddr =
createTokenRx.contractFunctionResult.getAddress(0);
const tokenId = AccountId.fromSolidityAddress(tokenIdSolidityAddr);
console.log(`Token created with ID: ${tokenId} \n`);
// Mint NFT
const mintToken = new ContractExecuteTransaction()
.setContractId(contractId)
.setGas(4000000)
.setMaxTransactionFee(new Hbar(20)) //Use when HBAR is under 10 cents
.setFunction(
"mintNft",
new ContractFunctionParameters()
.addAddress(tokenIdSolidityAddr) // Token address
.addBytesArray([Buffer.from(metadata)]) // Metadata
);
const mintTokenTx = await mintToken.execute(client);
const mintTokenRx = await mintTokenTx.getRecord(client);
const serial = mintTokenRx.contractFunctionResult.getInt64(0);
console.log(`Minted NFT with serial: ${serial} \n`);
// Transfer NFT to Alice
const transferToken = await new ContractExecuteTransaction()
.setContractId(contractId)
.setGas(4000000)
.setFunction(
"transferNft",
new ContractFunctionParameters()
.addAddress(tokenIdSolidityAddr) // Token address
.addAddress(aliceId.toSolidityAddress()) // Token receiver (Alice)
.addInt64(serial)
) // NFT serial number
.freezeWith(client) // freezing using client
.sign(aliceKey); // Sign transaction with Alice
const transferTokenTx = await transferToken.execute(client);
const transferTokenRx = await transferTokenTx.getReceipt(client);
console.log(`Transfer status: ${transferTokenRx.status} \n`);
};
main();
```
```go theme={null}
package main
import (
"encoding/hex"
"fmt"
"io/ioutil"
"os"
hedera "github.com/hiero-ledger/hiero-sdk-go/v2/sdk"
"github.com/joho/godotenv"
)
func main() {
godotenv.Load("../.env")
metadata := "ipfs://bafyreie3ichmqul4xa7e6xcy34tylbuq2vf3gnjf7c55trg3b6xyjr4bku/metadata.json"
bytesArray := [][]byte{}
bytesArray = append(bytesArray, []byte(metadata))
err := godotenv.Load(".env")
if err != nil {
panic(fmt.Errorf("Unable to load environment variables from .env file. Error:n%v\n", err))
}
//Grab your testnet account ID and private key from the .env file
operatorId, err := hedera.AccountIDFromString(os.Getenv("ACCOUNT_ID"))
if err != nil {
panic(err)
}
operatorKey, err := hedera.PrivateKeyFromString(os.Getenv("PRIVATE_KEY"))
if err != nil {
panic(err)
}
//Create your testnet client
client := hedera.ClientForTestnet()
client.SetOperator(operatorId, operatorKey)
aliceKey, err := hedera.PrivateKeyGenerateEcdsa()
if err != nil {
panic(err)
}
//Create the transaction
accountCreate := hedera.NewAccountCreateTransaction().
SetKey(aliceKey.PublicKey()).
//Do NOT set an alias if you need to update/rotate keys in the future
SetMaxAutomaticTokenAssociations(10).
SetInitialBalance(hedera.NewHbar(100))
//Sign the transaction with the client operator private key and submit to a Hedera network
accountCreateSubmit, err := accountCreate.Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
accountCreateReceipt, err := accountCreateSubmit.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the account ID
aliceAccountId := *accountCreateReceipt.AccountID
// Make sure to close client after running
defer func() {
err = client.Close()
if err != nil {
println(err.Error(), ": error closing client")
return
}
}()
// Read bytecode
bytecode, err := ioutil.ReadFile("./NFTCreator_sol_NFTCreator.bin")
if err != nil {
println(err.Error(), ": error reading bytecode")
return
}
//Create the transaction
createContract := hedera.NewContractCreateFlow().
SetGas(4000000).
SetBytecode([]byte(bytecode))
//Sign the transaction with the client operator key and submit to a Hedera network
txResponse, err := createContract.Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the contract ID
newContractId := *receipt.ContractID
fmt.Printf("The new contract ID is %v\n", newContractId)
contractParams := hedera.NewContractFunctionParameters().
AddString("Fall Collection"). // NFT name
AddString("LEAF"). // NFT symbol
AddString("Just a memo"). // NFT memo
AddInt64(250). // NFT max supply
AddInt64(7000000) // Expiration: Needs to be between 6999999 and 8000001
//Create NFT
createToken, err := hedera.NewContractExecuteTransaction().
//The contract ID
SetContractID(newContractId).
//The max gas
SetGas(4000000).
SetPayableAmount(hedera.NewHbar(50)).
//The contract function to call and parameters
SetFunction("createNft", contractParams).
Execute(client)
if err != nil {
panic(err)
}
//Get the record
txRecord, err := createToken.GetRecord(client)
if err != nil {
panic(err)
}
//Get transaction status
contractResult, err := txRecord.GetContractExecuteResult()
if err != nil {
panic(err)
}
tokenIdSolidityAddr := hex.EncodeToString(contractResult.GetAddress(0))
tokenId, err := hedera.AccountIDFromSolidityAddress(tokenIdSolidityAddr)
if err != nil {
panic(err)
}
fmt.Printf("Token created with ID: %v\n", tokenId)
// Add token address to params
mintParams, err := hedera.NewContractFunctionParameters().
AddAddress(tokenIdSolidityAddr)
if err != nil {
panic(err)
}
// Add metadata to params
mintParams = mintParams.AddBytesArray(bytesArray)
// Mint NFT
mintToken, err := hedera.NewContractExecuteTransaction().
//The contract ID
SetContractID(newContractId).
//The max gas
SetGas(1000000).
//The contract function to call and parameters
SetFunction("mintNft", mintParams).
//The max transaction fee. Use when HBAR is under 10 cents
SetMaxTransactionFee(hedera.HbarFrom(20, hedera.HbarUnits.Hbar)).
Execute(client)
if err != nil {
panic(err)
}
//Get the record
mintRecord, err := mintToken.GetRecord(client)
if err != nil {
panic(err)
}
//Get transaction status
mintResult, err := mintRecord.GetContractExecuteResult()
if err != nil {
panic(err)
}
serial := mintResult.GetInt64(0)
fmt.Printf("Minted NFT with serial: %v\n", serial)
// Add token address to params
transferParams, err := hedera.NewContractFunctionParameters().
AddAddress(tokenIdSolidityAddr)
// Add Alice address to params
transferParams, err = transferParams.AddAddress(aliceAccountId.ToSolidityAddress())
if err != nil {
panic(err)
}
transferParams = transferParams.AddInt64(serial)
// Transfer NFT
transferToken, err := hedera.NewContractExecuteTransaction().
//The contract ID
SetContractID(newContractId).
//The max gas
SetGas(4000000).
//The contract function to call and parameters
SetFunction("transferNft", transferParams).
FreezeWith(client)
if err != nil {
panic(err)
}
transferSubmit, err := transferToken.Sign(aliceKey).Execute(client)
if err != nil {
panic(err)
}
//Get the record
transferRecord, err := transferSubmit.GetReceipt(client)
if err != nil {
panic(err)
}
fmt.Printf("Transfer status: %v\n", transferRecord.Status)
}
```
# EVM Tutorials
Source: https://docs.hedera.com/evm/tutorials/index
Step-by-step guides for building on Hedera with Solidity, Hardhat, Foundry, and EVM-compatible tools.
Hands-on tutorials for every stage of EVM development on Hedera, from deploying your first token to building upgradeable contracts and Hedera-native integrations.
## Beginner
Configure Hedera Mainnet and Testnet in MetaMask using network settings or one-click configuration.
Deploy and mint an ERC-20 token on Hedera Testnet using Hardhat and OpenZeppelin's audited contracts. The workflow is identical to any other EVM chain.
## Intermediate
Write Solidity contracts that hold and transfer HBAR — the foundation for DeFi, NFT marketplaces, and DAO contracts on Hedera.
Verify deployed bytecode against source files using Sourcify. Once verified, HashScan displays the full source code automatically.
Connect Hardhat, Foundry, or ethers.js to Hedera via HashIO or Validation Cloud JSON-RPC endpoints.
## Advanced: ERC-721 with Hardhat
Three-part series building a production-grade NFT contract from scratch to upgradeable.
Deploy an ERC-721 contract with OpenZeppelin, mint an NFT to your account, and burn it — the complete token lifecycle.
Add role-based access control, URI storage, and Pausable functionality to your ERC-721 contract.
Upgrade an ERC-721 contract in place using the OpenZeppelin UUPS upgradeable proxy pattern.
## Advanced: ERC-721 with Foundry
Deploy, mint, and burn an ERC-721 token on Hedera Testnet using Foundry scripts and OpenZeppelin.
Write and run Solidity unit tests for an ERC-721 mint-and-burn contract using Foundry's test framework.
## HTS + EVM
Three-part series using Hedera Token Service system contracts from Solidity — native token management without custom bytecode.
Mint, transfer, and burn NFTs using HTS system contracts from Solidity. No separate token contract required.
Grant and revoke KYC, update token metadata, and manage compliance permissions on native HTS tokens via a smart contract.
Pause transfers, freeze accounts, wipe balances, and delete HTS tokens from Solidity.
## HSS + EVM
Schedule future smart contract calls using Hedera's native Schedule Service — no off-chain bots or keeper networks required.
Build a capacity-aware DeFi rebalancer that adjusts its scheduling strategy dynamically based on network conditions.
## Hedera Native
Create and transfer a Hedera-native NFT through a Solidity contract using the HTS system contract interface.
Combine HTS native tokenization with EVM smart contracts for compliance features and Solidity composability in the same token.
# Configuring Hashio RPC endpoints
Source: https://docs.hedera.com/evm/tutorials/intermediate/json-rpc-connections/hashio
How to configure a JSON-RPC endpoint that enables communication between EVM-compatible developer tools using Hashio.
Hashio is a public RPC endpoint hosted by [Hashgraph](https://www.hashgraph.com/) that runs an instance of the [Hiero JSON-RPC Relay](https://github.com/hiero-ledger/hiero-json-rpc-relay). As a *public* endpoint, it:
* Is free to use
* Does not have any sign-up requirements
* Has significantly restrictive rate limits
While this combination may be considered less reliable, it offers the highest levels of ease of use among RPC endpoints.
To connect to the Hedera networks via Hashio, simply use one of these URLs when initializing the wallet or web3 provider instance:
```text Mainnet theme={null}
https://mainnet.hashio.io/api
```
```text Testnet theme={null}
https://testnet.hashio.io/api
```
```text Previewnet theme={null}
https://previewnet.hashio.io/api
```
The corresponding chain IDs are:
| Network | Chain ID (decimal) | Chain ID (hex) |
| -------------- | :----------------: | :------------: |
| **Mainnet** | `295` | `0x127` |
| **Testnet** | `296` | `0x128` |
| **Previewnet** | `297` | `0x129` |
No further settings or configurations are needed!
**Please note**: Hashio is for development and testing purposes only. Production use cases are strongly encouraged to use [commercial-grade JSON-RPC relays](/evm/development/json-rpc#community-hosted-json-rpc-relays) or host their own instance of the [Hiero JSON-RPC Relay](https://github.com/hiero-ledger/hiero-json-rpc-relay).
## Additional resources
* [Hiero JSON-RPC Relay repository](https://github.com/hiero-ledger/hiero-json-rpc-relay)
* [JSON-RPC Relay overview](/evm/development/json-rpc)
* [How to connect MetaMask to Hedera](/evm/tutorials/beginner/connect-metamask)
* [Add Hedera to MetaMask](/evm/quickstart/setup-metamask)
***
[GitHub](https://github.com/theekrystallee) | [X](https://X.com/theekrystallee) | [LinkedIn](https://linkedin.com/in/theekrystallee)
[GitHub](https://github.com/quiet-node) | [LinkedIn](https://www.linkedin.com/in/logann131/)
# How to Connect to Hedera Networks Over RPC
Source: https://docs.hedera.com/evm/tutorials/intermediate/json-rpc-connections/index
Compare the available JSON-RPC providers for Hedera and pick the right endpoint for your project.
The [JSON-RPC Relay](https://github.com/hiero-ledger/hiero-json-rpc-relay) is the open-source service that bridges EVM-style JSON-RPC clients (MetaMask, ethers.js, web3.js, Hardhat, Foundry, viem, etc.) to the Hedera network. You have three ways to access it: use the free public instance ([Hashio](/evm/tutorials/intermediate/json-rpc-connections/hashio)), use a managed third-party provider, or run your own instance.
## Choose a connection option
Free public Hiero JSON-RPC Relay instance hosted by Hashgraph. No sign-up required. Best for prototyping and tutorials. Rate-limited.
Freemium managed JSON-RPC and Mirror Node service. Requires an account but no credit card. Scales for production workloads.
Commercial JSON-RPC provider with global edge infrastructure and SLAs. Best for high-throughput production apps.
Run your own instance of the open-source Hiero JSON-RPC Relay for full control over rate limits, caching, and infrastructure.
## Network support
| Option | Mainnet | Testnet | Previewnet | Sign-up |
| ------------------------------- | :-----: | :-----: | :--------: | :-----: |
| **Hashio** (public Hiero Relay) | ✅ | ✅ | ✅ | ❌ |
| **Validation Cloud** | ✅ | ✅ | ❌ | ✅ |
| **QuickNode** | ✅ | ✅ | ❌ | ✅ |
| **Self-hosted Relay** | ✅ | ✅ | ✅ | — |
## Network reference
| Network | Chain ID (decimal) | Chain ID (hex) | Hashio endpoint |
| -------------- | :----------------: | :------------: | ---------------------------------------------------------------------- |
| **Mainnet** | `295` | `0x127` | [`https://mainnet.hashio.io/api`](https://mainnet.hashio.io/api) |
| **Testnet** | `296` | `0x128` | [`https://testnet.hashio.io/api`](https://testnet.hashio.io/api) |
| **Previewnet** | `297` | `0x129` | [`https://previewnet.hashio.io/api`](https://previewnet.hashio.io/api) |
**Hashio is for development and testing purposes only.** Production use cases are strongly encouraged to use [commercial-grade JSON-RPC relays](/evm/development/json-rpc#community-hosted-json-rpc-relays) or host their own instance of the [Hiero JSON-RPC Relay](https://github.com/hiero-ledger/hiero-json-rpc-relay).
## Additional resources
* [JSON-RPC Relay overview](/evm/development/json-rpc)
* [Community-hosted JSON-RPC relays](/evm/development/json-rpc#community-hosted-json-rpc-relays)
* [Add Hedera to MetaMask](/evm/quickstart/setup-metamask)
* [How to connect MetaMask to Hedera](/evm/tutorials/beginner/connect-metamask)
* [Hiero JSON-RPC Relay on GitHub](https://github.com/hiero-ledger/hiero-json-rpc-relay)
***
[GitHub](https://github.com/bguiz) | [Blog](https://blog.bguiz.com/)
[GitHub](https://github.com/aaron-cottrell-vc)
[GitHub](https://github.com/theekrystallee) | [X](https://X.com/theekrystallee) | [LinkedIn](https://linkedin.com/in/theekrystallee)
# Configuring Validation Cloud RPC endpoints
Source: https://docs.hedera.com/evm/tutorials/intermediate/json-rpc-connections/validation-cloud
[Validation Cloud](https://www.validationcloud.io/node)
is a third-party organization that runs a JSON-RPC and Mirror Node managed service for Hedera as well as other
popular blockchain networks. It is a "freemium" offering, meaning it has a free tier and a paid offering. As a
managed service, it:
* Is free to use up to a point, with a free usage allowance that resets every month
* Does not require a credit card to sign up, only an email address
* Does not apply specific rate limits and can scale to be used by high volume apps
This combination makes it fairly straightforward to use and more reliable than the public RPC endpoint.
To connect to Hedera networks via Validation Cloud, simply use this URL when initializing the wallet or web3
provider instance:
```text Mainnet theme={null}
https://mainnet.hedera.validationcloud.io/v1/
```
```text Testnet theme={null}
https://testnet.hedera.validationcloud.io/v1/
```
The corresponding chain IDs are:
| Network | Chain ID (decimal) | Chain ID (hex) |
| ----------- | :----------------: | :------------: |
| **Mainnet** | `295` | `0x127` |
| **Testnet** | `296` | `0x128` |
**Note:** Validation Cloud provides RPC endpoints for Hedera Mainnet and Hedera Testnet but not for Hedera Previewnet.
You will need to replace ``
with a Validation Cloud API Endpoint Key, and that requires the following prerequisite steps:
* (1) Sign up for an account at [`app.validationcloud.io/api/auth/signup`](https://app.validationcloud.io/api/auth/signup)
* (2) Accept the terms of use and then verify your email address
* (3) Click the "Create endpoint" button at the top of the Validation Cloud Node API dashboard:
* (4) Fill in a name for the endpoint, select Hedera and pick whether you want Testnet or Mainnet, then click
on the "Confirm" button:
* (5) Your key should now be created and you can click the copy to clipboard icon to use it to make requests:
Now you're ready to connect to an RPC endpoint or query a Mirror Node via Validation Cloud!
## Additional resources
* [Validation Cloud documentation](https://docs.validationcloud.io/v1)
* [Hedera JSON-RPC Relay API reference](https://docs.validationcloud.io/v1/hedera/json-rpc-relay-api)
* [Hedera REST Mirror Node API reference](https://docs.validationcloud.io/v1/hedera/rest-mirror-node-api)
* [Validation Cloud Node API dashboard](https://app.validationcloud.io/)
***
[GitHub](https://github.com/theekrystallee) | [X](https://X.com/theekrystallee) | [LinkedIn](https://linkedin.com/in/theekrystallee)
[GitHub](https://github.com/quiet-node) | [LinkedIn](https://www.linkedin.com/in/logann131/)
# Send and Receive HBAR Using Solidity Smart Contracts
Source: https://docs.hedera.com/evm/tutorials/intermediate/send-receive-hbar
Smart contracts on Hedera can hold and exchange value in the form of HBAR, Hedera Token Service (HTS) tokens, and even ERC tokens. This is fundamental for building decentralized applications that rely on contracts in areas like DeFi, ESG, NFT marketplaces, DAOs, and more.
Let’s learn how to send and receive HBAR to and from Hedera contracts. [Part 1](https://hedera.com/blog/how-to-send-and-receive-hbar-using-smart-contracts-part-1-using-the-sdk) of the series focused on using the Hedera SDKs. This second part goes over transferring HBAR to and from contracts using Solidity.
Follow these main 3 steps:
1. Create the Hedera accounts needed for testing and deploy a smart contract on the Testnet
2. Move HBAR to the contract using ***fallback*** and ***receive*** functions, a ***payable*** function, and the **SDK**
3. Move HBAR from the contract to Alice using the ***transfer***, ***send***, and ***call*** methods
Throughout the tutorial, you also learn how to check the HBAR balance of the contract by calling a function of the contract itself and by using the SDK query. The last step is to review the transaction history for the contract and the operator account in a mirror node explorer, like [HashScan](https://hashscan.io/#/mainnet/dashboard).
***
## Prerequisites
We recommend you complete the following introduction to get a basic understanding of Hedera transactions. This example does not build upon the previous examples.
* Get a [Hedera testnet account](/native/tutorials/getting-started/create-fund-account).
* Set up your environment here.
***
## Table of Contents
1. [Create Accounts and Deploy a Contract](#create-accounts-and-deploy-a-contract)
2. [Get HBAR to ➡ Contract](#getting-hbar-to-the-contract)
3. [Get HBAR from ⬅ Contract](#getting-hbar-from-the-contract)
4. [Summary](#summary)
5. [Additional Resources](#additional-resources)
***
## **Create Accounts and Deploy a Contract**
This example involves 3 Hedera accounts, 1 contract, and 1 Hedera Token Service (HTS) token. The Operator account ([your Testnet account credentials](https://portal.hedera.com/register)) is used to build the Hedera client to submit transactions to the Hedera network – that’s the first account. The Treasury and Alice are new accounts (created by the Operator) to represent additional parties in your test – those are the second and third accounts respectively.
A portion of the application file (***index.js***) and the entire Solidity contract (***hbarToAndFromContract.sol***) are shown in the tabs below.
The Solidity file has functions for getting HBAR to the contract (***receive***, ***fallback***, ***tokenAssociate***), getting HBAR from the contract (***transferHbar***, ***sendHbar***, ***callHbar***), and checking the HBAR balance of the contract (***getBalance***).
This portion of ***index.js*** configures and creates the accounts, deploys the contract, and stores the HTS token ID. The functions ***accountCreatorFcn*** and ***contractDeployFcn*** create new accounts and deploy the contract to the network, respectively. These functions simplify the account creation and contract deployment process and are reusable in case you need them in the future. This modular approach is used throughout the tutorial.
```js index.js theme={null}
// Configure accounts and client
const operatorId = AccountId.fromString(process.env.OPERATOR_ID);
const operatorKey = PrivateKey.fromString(process.env.OPERATOR_PVKEY);
const client = Client.forTestnet().setOperator(operatorId, operatorKey);
async function main() {
// Create other necessary accounts
console.log(`\n- Creating accounts...`);
const initBalance = 100;
const treasuryKey = PrivateKey.generateECDSA();
const [treasuryAccSt, treasuryId] = await accountCreatorFcn(
treasuryKey,
initBalance
);
console.log(
`- Created Treasury account ${treasuryId} that has a balance of ${initBalance} ℏ`
);
const aliceKey = PrivateKey.generateECDSA();
const [aliceAccSt, aliceId] = await accountCreatorFcn(aliceKey, initBalance);
console.log(
`- Created Alice's account ${aliceId} that has a balance of ${initBalance} ℏ`
);
// Import the compiled contract bytecode
const contractBytecode = fs.readFileSync("hbarToAndFromContract.bin");
// Deploy the smart contract on Hedera
console.log(`\n- Deploying contract...`);
let gasLimit = 100000;
const [contractId, contractAddress] = await contractDeployFcn(
contractBytecode,
gasLimit
);
console.log(`- The smart contract ID is: ${contractId}`);
console.log(
`- The smart contract ID in Solidity format is: ${contractAddress}`
);
const tokenId = AccountId.fromString("0.0.47931765");
console.log(`\n- Token ID (for association with contract later): ${tokenId}`);
}
main();
```
```solidity hbarToAndFromContract.sol theme={null}
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
// Compile with remix for remote imports to work - otherwise keep precompiles locally
import "https://github.com/hiero-ledger/hiero-contracts/blob/main/contracts/token-service/HederaTokenService.sol";
import "https://github.com/hiero-ledger/hiero-contracts/blob/main/contracts/common/HederaResponseCodes.sol";
contract hbarToAndFromContract is HederaTokenService{
//============================================
// GETTING HBAR TO THE CONTRACT
//============================================
receive() external payable {}
fallback() external payable {}
function tokenAssociate(address _account, address _htsToken) payable external {
require(msg.value > 2000000000,"Send more HBAR");
int response = HederaTokenService.associateToken(_account, _htsToken);
if (response != HederaResponseCodes.SUCCESS) {
revert ("Token association failed");
}
}
//============================================
// GETTING HBAR FROM THE CONTRACT
//============================================
function transferHbar(address payable _receiverAddress, uint _amount) public {
_receiverAddress.transfer(_amount);
}
function sendHbar(address payable _receiverAddress, uint _amount) public {
require(_receiverAddress.send(_amount), "Failed to send Hbar");
}
function callHbar(address payable _receiverAddress, uint _amount) public {
(bool sent, ) = _receiverAddress.call{value:_amount}("");
require(sent, "Failed to send Hbar");
}
//============================================
// CHECKING THE HBAR BALANCE OF THE CONTRACT
//============================================
function getBalance() public view returns (uint) {
return address(this).balance;
}
}
```
These helper functions in ***index.js*** use the [**AccountCreateTransaction()**](/native/accounts/create#create-an-account-via-an-account-alias) and [**ContractCreateFlow()**](/native/smart-contracts/create#contractcreateflow) classes of the Hedera SDK. [**ContractCreateFlow()**](/native/smart-contracts/create#contractcreateflow) stores the bytecode and deploys the contract on Hedera. This single call handles for you the operations [**FileCreateTransaction()**](/native/files/create), [**FileAppendTransaction()**](/native/files/append), and [**ContractCreateTransaction()**](/native/smart-contracts/create#contractcreatetransaction).
**Helper Functions:**
```javascript AccountCreatorFcn theme={null}
async function accountCreatorFcn(pvKey, iBal) {
const response = await new AccountCreateTransaction()
.setInitialBalance(new Hbar(iBal))
.setKey(pvKey.publicKey)
.execute(client);
const receipt = await response.getReceipt(client);
return [receipt.status, receipt.accountId];
}
```
```javascript ContractDeployFcn theme={null}
async function contractDeployFcn(bytecode, gasLim) {
const contractCreateTx = new ContractCreateFlow()
.setBytecode(bytecode)
.setGas(gasLim);
const contractCreateSubmit = await contractCreateTx.execute(client);
const contractCreateRx = await contractCreateSubmit.getReceipt(client);
const contractId = contractCreateRx.contractId;
const contractAddress = contractId.toSolidityAddress();
return [contractId, contractAddress];
}
```
* *Creating accounts...* \* *Created Treasury account 0.0.47938602 that has a
balance of 100 ℏ* \* *Created Alice's account 0.0.47938603 that has a balance
of 100 ℏ* \* *Deploying contract...* \* *The smart contract ID is: 0.0.47938605*
* *The smart contract ID in Solidity format is:
0000000000000000000000000000000002db7c2d* \* *Token ID (for association with
contract later): 0.0.47931765*
***
## **Getting HBAR to the Contract**
### **The receive/fallback** Functions
In this scenario, you (Operator) transfer 10 HBAR to the contract by triggering either the ***receive*** or ***fallback*** functions of the contract. As described in this [Solidity by Example](https://solidity-by-example.org/sending-ether/) page, the ***receive*** function is called when ***msg.data*** is empty, otherwise the ***fallback*** function is called.
In this case, the helper function ***contractExecuteNoFcn*** pays HBAR to the contract by using [**ContractExecuteTransaction()**](/native/smart-contracts/call) and specifying a ***.setPayableAmount()*** without calling any specific contract function – thus triggering ***fallback***. Note from the Solidity code that ***receive*** and ***fallback*** are ***external*** and ***payable*** functions.
The helper function ***contractCallQueryFcn*** checks the HBAR balance of the contract by calling the ***getBalance*** function of the contract – this call is done using [**ContractCallQuery()**](/native/smart-contracts/get-function).
```javascript wrap theme={null}
console.log(`
====================================================
GETTING HBAR TO THE CONTRACT
====================================================`);
// Transfer HBAR to the contract using .setPayableAmount WITHOUT specifying a function (fallback/receive triggered)
let payableAmt = 10;
console.log(
`- Caller (Operator) PAYS ${payableAmt} ℏ to contract (fallback/receive)...`
);
const toContractRx = await contractExecuteNoFcn(
contractId,
gasLimit,
payableAmt
);
// Get contract HBAR balance by calling the getBalance function in the contract AND/OR using ContractInfoQuery in the SDK
await contractCallQueryFcn(contractId, gasLimit, "getBalance"); // Outputs the contract balance in the console
```
**Helper Functions:**
```javascript ContractExecuteNoFcn theme={null}
async function contractExecuteNoFcn(cId, gasLim, amountHbar) {
const contractExecuteTx = new ContractExecuteTransaction()
.setContractId(cId)
.setGas(gasLim)
.setPayableAmount(amountHbar);
const contractExecuteSubmit = await contractExecuteTx.execute(client);
const contractExecuteRx = await contractExecuteSubmit.getReceipt(client);
return contractExecuteRx;
}
```
```javascript ContractCallQueryFcn theme={null}
async function contractCallQueryFcn(cId, gasLim, fcnName) {
const contractQueryTx = new ContractCallQuery()
.setContractId(cId)
.setGas(gasLim)
.setFunction(fcnName);
const contractQuerySubmit = await contractQueryTx.execute(client);
const contractQueryResult = contractQuerySubmit.getUint256(0);
console.log(
`- Contract balance (getBalance fcn): ${contractQueryResult * 1e-8} ℏ`
);
}
```
*====================================================*
*GETTING HBAR TO THE CONTRACT*
*====================================================*
* *Caller (Operator) PAYS 10 ℏ to contract (fallback/receive)...*
* *Contract balance (getBalance fcn): 10 ℏ*
### **Executing a Payable Function**
Now, you (Operator) transfer 21 HBAR to the contract by calling a specific contract function (***tokenAssociate***) that is ***payable*** using the [**ContractExecuteTransaction()**](/native/smart-contracts/call) class and specifying a ***.setPayableAmount()***. This is done with the helper function ***contractExecuteFcn***.
In this scenario, ***contractParamsBuilderFcn*** is used to build the parameters that will be passed to the contract function – that is, the contract and token IDs which are then converted to Solidity addresses.
From the Solidity code, note that the ***tokenAssociate*** function associates the contract to the HTS token from the first step, and requires more than 20 HBAR to execute (just for fun).
```javascript theme={null}
// Transfer HBAR to the contract using .setPayableAmount SPECIFYING a contract function (tokenAssociate)
payableAmt = 21;
gasLimit = 800000;
console.log(
`\n- Caller (Operator) PAYS ${payableAmt} ℏ to contract (payable function)...`
);
const Params = await contractParamsBuilderFcn(contractId, [], 2, tokenId);
const Rx = await contractExecuteFcn(
contractId,
gasLimit,
"tokenAssociate",
Params,
payableAmt
);
gasLimit = 50000;
await contractCallQueryFcn(contractId, gasLimit, "getBalance"); // Outputs the contract balance in the console
```
```javascript ContractParamsBuilderFcn theme={null}
async function contractParamsBuilderFcn(aId, amountHbar, section, tId) {
let builtParams = [];
if (section === 2) {
builtParams = new ContractFunctionParameters()
.addAddress(aId.toSolidityAddress())
.addAddress(tId.toSolidityAddress());
} else if (section === 3) {
builtParams = new ContractFunctionParameters()
.addAddress(aId.toSolidityAddress())
.addUint256(amountHbar * 1e8);
} else {
}
return builtParams;
}
```
```javascript ContractExecuteFcn theme={null}
async function contractExecuteFcn(cId, gasLim, fcnName, params, amountHbar) {
const contractExecuteTx = new ContractExecuteTransaction()
.setContractId(cId)
.setGas(gasLim)
.setFunction(fcnName, params)
.setPayableAmount(amountHbar);
const contractExecuteSubmit = await contractExecuteTx.execute(client);
const contractExecuteRx = await contractExecuteSubmit.getReceipt(client);
return contractExecuteRx;
}
```
* *Caller (Operator) PAYS 21 ℏ to contract (payable function)...* \* *Contract
balance (getBalance fcn): 31 ℏ*
### **Using `TransferTransaction`** in the SDK
Lastly in this scenario, the Treasury transfers 30 HBAR to the contract using [**TransferTransaction()**](/native/accounts/transfer)**.** This is done with the helper function ***hbar2ContractSdkFcn***. This scenario is just a quick recap and reminder of [Part 1 of the series](https://hedera.com/blog/how-to-send-and-receive-hbar-using-smart-contracts-part-1-using-the-sdk), so be sure to give that a read for more details.
```javascript theme={null}
// Transfer HBAR from the Treasury to the contract deployed using the SDK
let moveAmt = 30;
const transferSdkRx = await hbar2ContractSdkFcn(
treasuryId,
contractId,
moveAmt,
treasuryKey
);
console.log(
`\n- ${moveAmt} ℏ from Treasury to contract (via SDK): ${transferSdkRx.status}`
);
await contractCallQueryFcn(contractId, gasLimit, "getBalance"); // Outputs the contract balance in the console
```
**Helper Functions:**
```javascript theme={null}
async function hbar2ContractSdkFcn(sender, receiver, amount, pKey) {
const transferTx = new TransferTransaction()
.addHbarTransfer(sender, -amount)
.addHbarTransfer(receiver, amount)
.freezeWith(client);
const transferSign = await transferTx.sign(pKey);
const transferSubmit = await transferSign.execute(client);
const transferRx = await transferSubmit.getReceipt(client);
return transferRx;
}
```
* *30 ℏ from Treasury to contract (via SDK): SUCCESS* \* *Contract balance
(getBalance fcn): 61 ℏ*
***
## **Getting HBAR from the Contract**
In this section the contract transfers HBAR to Alice using three different methods: ***transfer***, ***send***, ***call***. Each transfer is of 20 HBAR, so by the end the contract should have 1 HBAR left in its balance.
This tutorial focuses on implementation. For additional background and details of these Solidity methods, check out [Solidity by Example](https://solidity-by-example.org/sending-ether/) and [this external article](https://medium.com/daox/three-methods-to-transfer-funds-in-ethereum-by-means-of-solidity-5719944ed6e9) – just remember that on Hedera, the native cryptocurrency transacted is HBAR, not ETH. One thing worth noting from those resources is that ***call*** is currently the recommended method to use.
### **Contract Transfers HBAR to Alice**
The helper function ***contractExecuteFcn*** executes the ***transferHbar*** function of the contract. The helper function ***contractParamsBuilderFcn*** now builds the contract function parameters from the receiver ID (Alice’s) and the amount of HBAR to be sent. Also note from the previous section that the contract function is executed with a ***gasLimit*** of only 50,000 gas.
```javascript theme={null}
console.log(`
====================================================
GETTING HBAR FROM THE CONTRACT
====================================================`);
payableAmt = 0;
moveAmt = 20;
console.log(`- Contract TRANSFERS ${moveAmt} ℏ to Alice...`);
const tParams = await contractParamsBuilderFcn(aliceId, moveAmt, 3, []);
const tRx = await contractExecuteFcn(
contractId,
gasLimit,
"transferHbar",
tParams,
payableAmt
);
// Get contract HBAR balance by calling the getBalance function in the contract AND/OR using ContractInfoQuery in the SDK
await showContractBalanceFcn(contractId); // Outputs the contract balance in the console
```
**Helper Functions:**
```javascript theme={null}
async function showContractBalanceFcn(cId) {
const info = await new ContractInfoQuery().setContractId(cId).execute(client);
console.log(`- Contract balance (ContractInfoQuery SDK): ${info.balance.toString()}`);
```
*====================================================*
*GETTING HBAR FROM THE CONTRACT*
*====================================================*
* *Contract TRANSFERS 20 ℏ to Alice...*
* *Contract balance (ContractInfoQuery SDK): 41 ℏ*
### **Contract Sends HBAR to Alice**
The same helper function from before now executes the ***sendHbar*** function of the contract.
```javascript theme={null}
console.log(`\n- Contract SENDS ${moveAmt} ℏ to Alice...`);
const sParams = await contractParamsBuilderFcn(aliceId, moveAmt, 3, []);
const sRx = await contractExecuteFcn(
contractId,
gasLimit,
"sendHbar",
sParams,
payableAmt
);
await showContractBalanceFcn(contractId); // Outputs the contract balance in the console
```
* *Contract SENDS 20 ℏ to Alice...* \* *Contract balance (ContractInfoQuery
SDK): 21 ℏ*
### **Contract Calls HBAR to Alice**
Just like above, the helper function ***contractExecuteFcn*** executes the ***sendHbar*** function of the contract.
Examine the transaction history for the contract and the operator in the mirror node explorer, [HashScan](https://hashscan.io/#/mainnet/dashboard). You can also obtain additional information of interest using the [mirror node REST API](/reference/rest-api). Additional context for that API is provided in [this blog post](https://hedera.com/blog/how-to-look-up-transaction-history-on-hedera-using-mirror-nodes-back-to-the-basics).
The last step is to [**join the Hedera Developer Discord!**](https://hedera.com/discord)
```javascript theme={null}
console.log(`\n- Contract CALLS ${moveAmt} ℏ to Alice...`);
const cParams = await contractParamsBuilderFcn(aliceId, moveAmt, 3, []);
const cRx = await contractExecuteFcn(contractId, gasLimit, "callHbar", cParams, payableAmt);
await showContractBalanceFcn(contractId); // Outputs the contract balance in the console
console.log(`\n- SEE THE TRANSACTION HISTORY IN HASHSCAN (FOR CONTRACT AND OPERATOR):
https://hashscan.io/#/testnet/contract/${contractId}
https://hashscan.io/#/testnet/account/${operatorId}`);
console.log(`
====================================================
THE END - NOW JOIN: https://hedera.com/discord
====================================================\n`);
}
```
* *Contract CALLS 20 ℏ to Alice...*
* *Contract balance (ContractInfoQuery SDK): 1 ℏ*
* *SEE THE TRANSACTION HISTORY IN HASHSCAN (FOR CONTRACT AND OPERATOR):*
[*https://hashscan.io/#/testnet/contract/0.0.47938605*](https://hashscan.io/#/testnet/contract/0.0.47938605)[*https://hashscan.io/#/testnet/account/0.0.2520793*](https://hashscan.io/#/testnet/account/0.0.2520793)
***
## **Summary**
If you run the entire example successfully, your console should look something like:
This tutorial used the Hedera JavaScript SDK. However, you can try this with the other officially supported [SDKs](/native/fundamentals) for Java and Go.
**Congratulations! 🎉 Now you know how to send HBAR to and from a contract on Hedera using both the SDK and Solidity! Feel free to reach out in** [**Discord**](https://hedera.com/discord) **if you have any questions!**
***
## Additional Resources
**➡** [**Project Repository**](https://github.com/ed-marquez/hedera-smart-contracts/tree/examples/examples/transfer-hbar2contracts-solidity)
[GitHub](https://github.com/ed-marquez) |
[LinkedIn](https://www.linkedin.com/in/ed-marquez/)
[GitHub](https://github.com/theekrystallee) |
[Hashnode](https://hashnode.com/@theekrystallee)
# Verify a Smart Contract on HashScan
Source: https://docs.hedera.com/evm/tutorials/intermediate/verify-hashscan
Verifying smart contracts proves that the deployed bytecode matches the source files you publish. On Hedera, verification is handled by [Sourcify](https://sourcify.dev), which natively supports Hedera Mainnet (chain ID `295`) and Testnet (chain ID `296`). Once a contract is verified on Sourcify, [HashScan](https://hashscan.io/) automatically picks up the verified status and displays the source code on the contract page.
The in-app HashScan verification form is temporarily disabled. Verify your contracts directly at [sourcify.dev](https://sourcify.dev) using the steps below, or use the Foundry / Hardhat tutorials linked at the bottom of this page.
***
## Prerequisites
* Solidity [source code file](/evm/development/verifying#smart-contract-source-code) of the deployed smart contract.
* Solidity [JSON (metadata) file](/evm/development/verifying#the-metadata-file) of the deployed smart contract.
* EVM address of the smart contract deployed on the Hedera network.
***
## Table of Contents
1. [Verify on Sourcify](#step-1%3A-verify-on-sourcify)
2. [Confirm on HashScan](#step-2%3A-confirm-on-hashscan)
3. [Bundled Metadata for Complex Contracts](#bundled-metadata-for-complex-contracts)
4. [Recommended: Programmatic Verification](#recommended%3A-programmatic-verification)
5. [Additional Resources](#additional-resources)
***
## Step 1: Verify on Sourcify
1. Open [sourcify.dev](https://sourcify.dev) in your browser.
2. Choose the network that matches your deployment: **Hedera Mainnet** (chain ID `295`) or **Hedera Testnet** (chain ID `296`).
3. Paste the deployed contract's EVM address.
4. Upload your Solidity source files (`.sol`) and the metadata file (`.json`). The metadata file is generated when you compile the contract; for example, with Hardhat it lives under `artifacts/build-info/`, and with Foundry the metadata is included in the per-contract JSON under `out/`.
5. Submit the verification request. Sourcify will recompile your sources and compare them against the deployed bytecode.
If the comparison succeeds, Sourcify returns either a [Full Match](https://docs.sourcify.dev/docs/full-vs-partial-match/#full-perfect-matches) or a [Partial Match](https://docs.sourcify.dev/docs/full-vs-partial-match/#partial-matches):
* **Full Match**: the bytecode and metadata are an exact match. Source code, comments, and variable names line up with the deployed contract.
* **Partial Match**: the bytecode mostly matches, but the metadata hash differs (typically due to comments or variable names). This is sufficient for most verification purposes.
1. **Remix**:
* **Required for Full Match Verification**: Both the metadata file found in the `contracts/artifacts/` folder and the smart contract's Solidity file. More details [here](/evm/development/verifying#remix-ide-beginner).
2. **Hardhat**:
* **Required for Full Match Verification**: Only the output of the compilation JSON file found in the `/artifacts/build-info/` folder. More details [here](/evm/development/verifying#hardhat-intermediate).
3. **Solidity Compiler (solc)**:
* **Required for Full Match Verification**: Both the metadata file (generated by `solc --metadata`) and the smart contract's Solidity file. More details [here](/evm/development/verifying#solidity-compiler-advanced).
4. **Foundry**:
* **Required for Full Match Verification**: Both the metadata file (generated by `forge build`) and the smart contract's Solidity file.
***Note: Uploading only the Solidity file without the metadata file will result in a Partial Match.***
***
## Step 2: Confirm on HashScan
Open the contract on [HashScan](https://hashscan.io/), making sure you're on the same network you verified against. The contract's source code, ABI, and verification badge will appear on the **Contract** tab once HashScan picks up the verified status from Sourcify.
To learn more about each verification match status, head over to the official Sourcify documentation [here](https://docs.sourcify.dev/docs/full-vs-partial-match/).
**Congratulations! 🎉 You have successfully verified a smart contract on Hedera. Feel free to reach out on** [**Discord**](https://hedera.com/discord) **if you have any questions!**
***
## Bundled Metadata for Complex Contracts
For projects with multiple dependencies (such as OpenZeppelin libraries) or upgradeable proxy contracts, manually uploading individual source files can be **extremely difficult and error-prone**. Each dependency must be uploaded separately, and ensuring all imports resolve correctly is challenging.
A more robust manual approach is to generate a single, self-contained `metadata.json` file that includes all source code dependencies inline. This single file can then be uploaded to Sourcify in place of dozens of individual files.
### Why Use This Approach?
When a contract imports many external libraries and files, the standard manual upload requires you to provide:
* The main contract `.sol` file
* The metadata `.json` file
* **Every** imported dependency file
The bundled approach packages everything into one file.
### Generating the Single Metadata File
Both Hardhat and Foundry projects can be configured to generate this single file using community-developed scripts that leverage the Sourcify standard.
1. **Download the metadata generation script** from [Generate Hedera SC Metadata Repo](https://gist.github.com/kpachhai/972d63c5f5ecd9bbc718ab4dd34d5f29):
```bash theme={null}
curl -O https://gist.githubusercontent.com/kpachhai/972d63c5f5ecd9bbc718ab4dd34d5f29/raw/generate_hedera_sc_metadata.sh
chmod +x generate_hedera_sc_metadata.sh
```
2. **Run the script** to generate the bundles:
```bash theme={null}
# USAGE:
# ./generate_hedera_sc_metadata.sh [ContractName] [ContractName=0xAddress] ...
# EXAMPLES:
# ./generate_hedera_sc_metadata.sh MyToken
# ./generate_hedera_sc_metadata.sh MyToken=0x1234567890abcdef...
# ./generate_hedera_sc_metadata.sh src/MyContract.sol:MyContract=0x9876...
# For example, for a contract "MyToken":
./generate_hedera_sc_metadata.sh MyToken
```
3. This produces a directory (e.g., `verify-bundles/`) containing a single `metadata.json` file for each contract.
```bash theme={null}
>> tree verify-bundles/
verify-bundles/
├── MANIFEST.txt
└── MyToken
└── metadata.json
```
4. On [sourcify.dev](https://sourcify.dev), select the Hedera network, enter the contract address, and upload the corresponding single `metadata.json` file.
***
## Recommended: Programmatic Verification
While the bundled metadata approach above is more robust than uploading individual files, programmatic verification through Foundry or Hardhat is even more reliable and integrates cleanly into a deployment pipeline. See the standalone guides for fully automated workflows:
***
## Additional Resources
**➡** [**Smart Contract Verification API**](/reference/verification-api)
**➡** [**HashScan Network Explorer**](https://hashscan.io/)
**➡** [**Sourcify Verification UI**](https://sourcify.dev)
**➡** [**Sourcify v2 API Docs**](https://docs.sourcify.dev/docs/api/)
**➡** [**Sourcify Documentation**](https://docs.sourcify.dev/docs/intro)
**➡** [**Smart Contract Documentation**](/evm/development/verifying)
[GitHub](https://github.com/theekrystallee) |
[X](https://X.com/theekrystallee)
[GitHub](https://github.com/LukeForrest-Hashgraph) |
[X](https://x.com/_LukeForrest)
[GitHub](https://github.com/Nana-EC) |
[LinkedIn](https://www.linkedin.com/in/nconduah/)
[GitHub](https://github.com/ed-marquez) |
[LinkedIn](https://www.linkedin.com/in/ed-marquez/)
[GitHub](https://github.com/quiet-node) |
[LinkedIn](https://www.linkedin.com/in/logann131/)
# Account Creation
Source: https://docs.hedera.com/learn/core-concepts/accounts/account-creation
Create a Hedera account using the Developer Portal, a supported wallet, or the Hiero SDKs, and learn the HBAR fees required to register on-chain.
New accounts are created on the Hedera ledger by submitting a transaction to the network and paying the transaction fee to create the account. The transaction fee to create the account includes the costs required to use network resources, reach consensus amongst the nodes, and share the data across the network.
You will need access to an existing account with enough HBAR to cover the transaction fee to create the account. Suppose you don't have access to an existing account. In that case, you can use a supported wallet or visit the [Hedera Developer Portal](https://portal.hedera.com/register) to create an account. You can also ask a friend with an existing Hedera account to generously create one for you. Applications can check out the "[Auto Account Creation](/learn/core-concepts/accounts/auto-account-creation)" feature to make free Hedera user accounts.
When an account is created, it is stored in the state on the Hedera network. The current state can be queried from the ledger and viewed in a [Network Explorer](/networks/community-mirror-nodes-explorers). Each account has at least one public and private key pair. The private key(s) on the account is used to sign and authorize transactions that involve the account. To view the properties that can be set for an account, check out the "[Account Properties](/learn/core-concepts/accounts/account-properties)" section.
**Recommended default for EVM compatibility:** Create accounts with an ECDSA key and set the **EVM Address from Public Key** at creation. This enables native compatibility with Ethereum wallets, JSON-RPC tooling, and smart-contract interactions. See [Create an account](/native/accounts/create#setting-the-key-and-alias) for the SDK methods.
The **EVM Address from Public Key** is **immutable** and can only be set at account creation. ***It cannot be added later***. If account keys are later rotated (for example, via `CryptoUpdateTransaction`), this address will no longer match the new public key, and integrations that depend on the original EVM identity can break. For recovery from compromised keys, create a new ECDSA account and migrate assets and state rather than relying on key rotation.
An account can be created through any of the following methods. To create accounts using the SDKs, you will need access to an existing account to pay for the transaction fee to create a new account.
Supported wallets may or may not support creating testnet and previewnet accounts.
❌ mainnet
✅ testnet
✅ previewnet
✅ mainnet
🔶 testnet
🔶 previewnet
✅ mainnet
✅ testnet
✅ previewnet
# Account Properties
Source: https://docs.hedera.com/learn/core-concepts/accounts/account-properties
Reference for every Hedera account property: account ID, keys, balance, memo, auto-renew period, staking, max automatic token associations, and more.
## Account ID
The account ID is the ID of the account **entity** on the Hedera network. The account ID includes the **shard number**, **realm number**, and an **account** `..`. The account ID is used to specify the account in all Hedera transactions and queries. There can be more than one account ID that can represent an account.
**Mainnet default:** Every Hedera mainnet account ID is `0.0.` (e.g., `0.0.1234`). Both the shard and realm segments are `0` and have been since launch. Hardcode `0.0` when constructing `AccountId` values for mainnet. Do not take shard/realm as user-configurable parameters unless your application explicitly targets a private network.
Non-zero realm IDs (e.g., `0.100.0`) are supported for **private networks**. Non-zero shard IDs are not yet active on any network. The rest of this section documents the underlying format for completeness.
#### Support for Arbitrary Shards & Realms
**Mainnet default:** Every Hedera mainnet account ID is `0.0.` (e.g., `0.0.1234`). Both the shard and realm segments are `0` and have been since launch. Hardcode `0.0` when constructing `AccountId` values for mainnet; do not take shard and realm as user-configurable parameters unless your application explicitly targets a private network.
Non-zero realm IDs (e.g., `0.100.0`) are supported for **private networks**. Non-zero shard IDs are not yet active on any network. The accordions below document the underlying format for completeness.
Format: **`shardNum`**`.realmNum.account`
The shard number is the number of the shard the account exists in. A shard is a partition of the data received by the nodes participating in a given shard. Today, Hedera operates in only one shard. This value will remain zero until Hedera operates in more than one shard. This value is non-negative and is 4 bytes.
Default Hedera Mainnet: `0`
Format: `shardNum.`**`realNum`**`.account`
The realm number is the number of the realm the account exists within a given shard. Today, Hedera operates in only one realm. This value will remain zero until Hedera operates in more than one shard. This value is non-negative and is `8` bytes. The account can only belong to precisely one realm. The realm ID can be reused in other shards.
Default for Hedera Mainnet: `0`
Format: `shardNum.realNum.`**`account`**
The `account` can be one of the following:
* [Account Number](#account-number) \\
* [Account Alias](#account-alias)
#### Examples of Valid Account IDs
* `0.0.123` - Account 123 in `realm 0` of `shard 0` (traditional format)
* `0.100.456` - Account 456 in `realm 100` of `shard 0` (non-zero realm ID)
* `1.0.789` - Account 789 in `realm 0` of `shard 1` (future support for non-zero shard ID)
### ID Requirements
* A shard number must be unique across the network
* A realm number must be unique within a shard (but may be reused in different shards)
* An entity number must be unique within a realm (but may be reused in different realms)
### Entity Counter
Each realm maintains a single counter for assigning entity numbers. This ensures that if there is a file with ID `0.1.2`, then there won't be an account or smart contract instance with ID `0.1.2` in the same realm.
### **Account Number**
Each Hedera account has a system-provided **account number** when the account is created. An account number is a non-negative number of 8 bytes. You can use the account number to specify the account in all Hedera transactions and query requests. Account numbers are unique and immutable. The account number for a newly created account is returned in the transaction receipt or transaction record for the transaction ID that created the account. The account number ID has the following format `..`.
| Account Number ID | Description |
| ----------------- | -------------------------------------------------- |
| `0.0.10` | The account number 10 in account number ID format. |
#### EVM Address from Account ID (long-zero form)
All accounts can have an **EVM Address from Account ID**. It is a hex-encoded form of the account number prefixed with 20 bytes of zeros, the "long-zero" form. It is an EVM-compatible address that references the Hedera account. The EVM Address from Account ID does not contain the shard ID and realm ID.
This account property is not stored in consensus node state. You will not see this value returned when querying the consensus nodes for the account object and inspecting the account alias field.
The mirror node will calculate the EVM Address from Account ID. It is calculated and returned in account REST APIs only when the account does not have an existing EVM Address from Public Key. For example, if the account was created through the [auto account creation](/learn/core-concepts/accounts/auto-account-creation) flow with an EVM Address from Public Key, the EVM Address from Account ID will not be populated. If the account was normally created then the account alias field will store the EVM Address from Account ID.
| Account ID | EVM Address from Account ID Example |
| ---------- | ---------------------------------------------------------------------------------------------- |
| `0.0.10` | The hex encoding value for 10 is "0a."
`000000000000000000000000000000000000000a` |
### Account Alias
Some Hedera accounts will have an **account alias**. Account aliases are a pointer to the account object in addition to being identified by the account number. Account aliases are assigned to the account when the account is created via the [auto account creation](/learn/core-concepts/accounts/auto-account-creation) flow. The network does not generate the account alias; instead, the user specifies the account alias upon account creation. This property will be null if an account is created through the normal account creation flow. The account aliases are unique and immutable. The account alias ID has the following format `..`.
This format is only acceptable when specified in the `TransferTransaction`, `AccountInfoQuery` and `AccountBalanceQuery`. If this format is used to reference an account in any other transaction type the transaction will not succeed.
For new accounts, the recommended `alias` value is the account's [EVM Address from Public Key](#evm-address-from-public-key).
#### EVM Address from Public Key (recommended)
The EVM Address from Public Key is the rightmost 20 bytes of the 32-byte Keccak-256 hash of the account's ECDSA public key, computed per the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). The recovery ID is not part of the public key and is not included in the hash. On mainnet, the corresponding account-ID-style identifier is `0.0.` (the full format is `..`; shard and realm are `0` on mainnet, see [Shards & Realms](#support-for-arbitrary-shards--realms)).
It makes the account natively addressable from EVM wallets, JSON-RPC, and Solidity (`msg.sender`), and is the recommended default for new accounts. To set it at creation, use `setECDSAKeyWithAlias()` (see [Create an account](/native/accounts/create)).
**Immutable identity:** The EVM Address from Public Key is bound to the original ECDSA public key. If you later rotate keys via `CryptoUpdateTransaction`, this address will no longer match the new public key. Integrations that depend on the original EVM identity (smart-contract permissions, address-based access) will continue to reference the original address.
If you expect to rotate keys soon after creation, defer setting the EVM Address from Public Key. If keys are compromised, create a new account with a new ECDSA key and migrate assets/state rather than relying on key rotation.
Both the EVM Address from Public Key and the [EVM Address from Account ID](#evm-address-from-account-id-long-zero-form) are 20-byte values. They are distinguishable because the EVM Address from Account ID is always prefixed with 12 zero bytes (the "long-zero" form).
#### Value Transfer Behavior with Account Identifiers
* `CryptoTransfer` Transactions: When transferring value via the SDK, any of the following account identifiers can be used:
* Standard Hedera account ID, on mainnet `0.0.` (e.g., `0.0.1234`)
* **EVM Address from Public Key** (the rightmost 20 bytes of the Keccak-256 hash of the ECDSA public key), recommended for EVM compatibility
* **EVM Address from Account ID** (the account number prefixed with 12 zero bytes, the "long-zero" form)
* EVM Transactions: The address used for a value transfer recipient in EVM transactions (`ContractCreate`, `ContractCall`, and `EthereumTransaction`) must be the main one for the account. This will be the EVM Address from Public Key if set on the account. If not, the EVM Address from Account ID (long-zero form) will be used. This ensures compatibility with the EVM's expected behavior for account addresses.
The shard number and realm number are set to `0` followed by the EVM address.
**Example**
EVM Address: `b794f5ea0ba39494ce839613fffba74279579268`
HEX Encoded EVM Address: `0xb794f5ea0ba39494ce839613fffba74279579268`
Account ID with EVM Address:
`0.0.b794f5ea0ba39494ce839613fffba74279579268`
Reference Hedera Improvement Proposal: [HIP-583](https://hips.hedera.com/hip/hip-583)
## Auto Renewals & **Expiration**
Auto-renewals and expiration (rent) are currently not enabled.
Like the other Hedera entities, accounts take up network storage. To cover the cost of storing an account, a renewal fee will be charged for the storage utilized on the network. This feature is not enabled on the network today; however, in the future, when it is enabled, the account must have sufficient funds to pay for the renewal fees.
The amount charged for renewal will be charged every pre-determined period in seconds. The interval of time the account will be charged is the auto-renew period. The system will automatically charge the account renewal fees. If the account does not have an HBAR balance, it will be suspended for one week before it is deleted from the ledger. You can renew an account during the suspension period.
The effective consensus timestamp at (and after) which the entity is set to expire.
The auto-renewal account is the account that will be charged for the auto-renewal fee when the account expires. By default, the auto-renewal account is the account itself. The auto-renewal account can be updated to another account by the account owner. The auto-renewal account must sign the transaction to update the auto-renewal account.
The interval at which this account will be charged the auto renewal fees. The maximum auto renew period for an account is be limited to 3 months (8000001 sec seconds). The minimum auto renew period is 30 days. The auto renew period is mutable and can be updated at any time. If there are insufficient funds, then it extends as long as possible. If it is empty when it expires, then it is deleted.
Reference: [HIP-16](https://hips.hedera.com/hip/hip-16)
## Account Memo
A memo is like a short note that lives with the account object in the ledger state and can be viewed on a network explorer when looking up the account. This account memo is limited to 100 characters. The account memo is mutable and can be updated or removed from the account at any time. The account key is required to sign the transaction to facilitate any changes to this property.
Do not post any private information in the account memo field. This field is visible to all participants in the network.
## Account Nonce
Accounts on Hedera can submit `EthereumTransaction` types processed by the Ethereum Virtual Machine (EVM) on a consensus node. The nonce on the account represents a sequentially incrementing count of the transactions submitted by an account through the `EthereumTransaction` type. The default account nonce value is set to zero.
Reference Hedera Improvement Proposal: [HIP-410](https://hips.hedera.com/hip/hip-410)
## Automatic Token Associations
Hedera accounts must generally approve custom tokens before transferring them into the receiving account. The receiving account must sign the transaction that will associate the tokens, allowing the specified tokens to be deposited into their account. The automatic token association feature allows the account to bypass manually associating the custom token before transferring it into the account.
Accounts can automatically approve up to 5,000 tokens without manually preauthorizing each custom token. Suppose an account needs to hold a balance for custom tokens greater than 5,000. In that case, the account must manually approve each additional token using the transaction to associate the tokens. There is no limit on the total number of tokens an account can hold. This property is mutable and can be changed after it is set.
## Maximum Auto-Associations
The property `maxAutoAssociations` defines how many tokens an account can automatically associate with.
| Property Value | Description |
| :------------: | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0` | Automatic token associations or airdrops are not allowed; the account must manually associate each token. |
| `-1` | Unlimited automatic token associations are allowed. This is the default for accounts created via **auto account creation** and for accounts that originated as hollow accounts and have since been completed. A value of `-1` allows the account to receive new tokens without manually associating them. |
| `> 0` | The account can automatically associate up to that number of tokens. The sender covers the `maxAutoAssociations` fee and the first auto-renewal rent for the association. |
This feature is enabled on Hedera mainnet as part of frictionless airdrops (hip‑904). When tokens are sent to an account with available auto‑association slots, one slot is consumed and the account becomes associated automatically.
Reference Hedera Improvement Proposal: [HIP-23](https://hips.hedera.com/hip/hip-23), [HIP-904](https://hips.hedera.com/hip/hip-904)
## Balances
When a new account is created, you can specify an initial HBAR balance for the account. The initial HBAR balance for the token is deducted from the account that is paying to create the new account. Creating an account with an initial balance is optional.
A Hedera account can hold a balance of HBAR and custom fungible and non-fungible tokens (NFTs). Account balances can be viewed on a [Network Explorer](/networks/community-mirror-nodes-explorers) and queried from mirror node REST APIs or consensus nodes.
| Token Type | Description | Token ID Example |
| ----------------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------------------- |
| **HBAR** | The native Hedera fungible token used to pay for transaction fees and secure the network. | None |
| **Fungible Token** | Custom fungible tokens created on Hedera. | The fungible token ID is represented as `0.0.tokenNum`, ex: `0.0.100` |
| **Non-Fungible Token (NFTs)** | Custom non-fungible tokens (NFTs) created on Hedera. | NFT ID is represented as `0.0.tokenNum-serialNum`, ex: `0.0.101-1` |
## Keys
Each account is required to have at least one key upon creation. If a key is not supplied at the time of account creation, the network will reject the transaction. The individual(s) that have access to the account's private key(s) have access to authorize the transfer of tokens into or out of the account and are required to sign transactions that modify the account. Modifying the account includes changing any property, like the balance, keys, memo, etc.
Accounts can optionally have more than one key associated with them. These kinds of accounts are multi-signature accounts meaning you will require more than one key to sign the transaction to change a property on an account or debit HBARs. The signing requirements for a multi-signature account depend on the account's key chosen key structure. For support of key structures and key types, follow the link below.
Warning: The private key(s) associated with the account is not to be shared with anyone as it will allow others to authorize transactions from your account on your behalf. Sharing your private key is like sharing your bank account password. Please make sure your private keys are stored in a secure wallet.
## Receiver Signature Required
Accounts can optionally require the account to sign any transactions depositing tokens into the account. This feature is set to false by default. If this feature is set to true, the account will be required to sign all transactions that deposit tokens into the account. This property is mutable and can be updated after the account is created.
## Staking
Staking in Hedera is taking an account and associating the HBAR balance to a node in the network. Custom fungible or non-fungible token balances an account holds do not contribute to staking on the network. The purpose of staking accounts to a node on the network is to strengthen the security of the network. To contribute to the security of the network, staked accounts can earn rewards in HBAR. Please see this [guide](/learn/core-concepts/staking) for additional information about the staking rewards program. Contracts can also stake their accounts to earn rewards.
An account can only stake to one node or one account at any given time.
An account can optionally elect to stake its HBAR to a node in the Hedera network. The staked node ID is the node an account can stake to. The full balance of the account is staked to the node. Do not confuse the node ID with the node's account ID. If you stake to the node's account ID, your account will not earn staking rewards.
The staked account balance is liquid at all times. This means you can transfer HBAR tokens in and out of the account, and your account will continue to be staked to the node without disruption.
There is no lock-up period. This means the HBAR tokens in your account are not held for a period of time before you can use them.
The node ID for a node can be found [here](/operators/consensus-node) or can be queried from the [nodes REST API](https://testnet.mirrornode.hedera.com/api/v1/docs/#/network/getNetworkNodes).\\
Example:
Node ID: `1`
An account can optionally elect to stake its HBAR to another account in the Hedera network. This is known as **indirect staking**. The staked account ID is the ID of the account to stake to. The full balance of the account is staked to the specified account.
There is no lock-up period and the balance is always liquid just like staking to a node.
Accounts that stake to another account do not earn the staking rewards. For example, If account A is staked to account B, account B will need to be staked to a node in order to contribute to network security and earn staking rewards. Account B will earn the rewards for staking when staked to a node for both the HBAR balances in both Account A + Account B. Account A will not earn rewards for staking.
Example:
Account ID: `0.0.10`
Accounts can decline to earn staking rewards when they stake to a node or an account. The staked account still contributes to the staking weight of the node, but does not earn rewards or is calculated as part of the payment of the rewards to the other accounts that have elected to earn rewards. By default, all staked accounts will earn rewards unless this boolean flag is set to true. This election can be changed by updating the account properties. Hedera treasury accounts enable this flag to decline earning staking rewards.
Default: `true` (all accounts accept earning staking rewards if the account is staked)
#### Daily Rewards for Active Nodes
Hedera now guarantees minimum daily rewards for active nodes on the network. Key features include:
* **Active Node Definition**: Nodes that create a "judge" in a significant fraction of rounds during a staking period
* **Minimum Guaranteed Rewards**: Active nodes receive a minimum daily reward amount, configurable by the network
* **Opt-Out Option**: Node operators can decline rewards by setting the `declineReward` flag to true
For node operators: To opt out of receiving rewards, use the `setDeclineReward(true)` method when creating or updating your node.
***Reference:*** [***HIP-1064***](https://hips.hedera.com/hip/hip-1064)
### Staking Information
The network stores the staking metadata for an account and contract accounts. This information is returned in account information query requests (`AccountInfoQuery` or`ContractInfoQuery`). The staking metadata for an account includes the following information:
* **decline\_reward:** whether or not the account declined to earn staking rewards
* **stake\_period\_start:** The staking period during which either the staking settings for this account or contract changed (such as starting staking or changing staked\_node\_id) or the most recent reward was earned, whichever is later. If this account or contract is not currently staked to a node, then this field is not set. The stake period is 24 hours, starting UTC midnight.
* **pending\_reward:** The amount in tinybars that will be received in the next staking reward payment
* **staked\_to\_me:** The total tinybar balance of all accounts staked to this account
* **staked\_id:** ID of the account or node to which this account or contract is staking
* **staked\_account\_id:** The account to which this account or contract is staking to
* **staked\_node\_id:** The ID of the node this account or contract is staked to
Reference Hedera Improvement Proposal: [HIP-406](https://hips.hedera.com/hip/hip-406)
## Auto Renewals & **Expiration**
Auto-renewals and expiration (rent) are currently not enabled.
Like the other Hedera entities, accounts take up network storage. To cover the cost of storing an account, a renewal fee will be charged for the storage utilized on the network. This feature is not enabled on the network today; however, in the future, when it is enabled, the account must have sufficient funds to pay for the renewal fees.
The amount charged for renewal will be charged every pre-determined period in seconds. The interval of time the account will be charged is the auto-renew period. The system will automatically charge the account renewal fees. If the account does not have an HBAR balance, it will be suspended for one week before it is deleted from the ledger. You can renew an account during the suspension period.
The effective consensus timestamp at (and after) which the entity is set to expire.
The auto renew account is the account that will be used to pay for the auto renewal fees. If there is no auto renew account specified, the auto renewal fees will be charged to the account.
The interval at which this account will be charged the auto renewal fees. The maximum auto renew period for an account is be limited to 3 months (8000001 sec seconds). The minimum auto renew period is 30 days. The auto renew period is mutable and can be updated at any time. If there are insufficient funds, then it extends as long as possible. If it is empty when it expires, then it is deleted.
Reference Hedera Improvement Proposal: [HIP-16](https://hips.hedera.com/hip/hip-16)
# Auto Account Creation
Source: https://docs.hedera.com/learn/core-concepts/accounts/auto-account-creation
Auto-create free Hedera accounts by sending HBAR or HTS tokens to an EVM Address from Public Key, as defined in HIP-32 and HIP-542.
Auto account creation is a unique flow in which applications like wallets and exchanges can create free user accounts instantly, even without an internet connection. Applications do this by generating an **EVM address** derived from an ECDSA public key and using it as the alias. The alias account ID format is `0.0.` on mainnet (the full format is `..`, shard and realm are `0` on mainnet today), an alternative to the standard account number format `0.0.`.
**For EVM-compatible accounts, the alias is an EVM address derived from the account's ECDSA public key**.
The EVM Address from Public Key is created by using the rightmost 20 bytes of the 32 byte `Keccak-256` hash of an `ECDSA secp256k1` public key. This calculation is in the manner described by the [Ethereum Yellow Paper](https://ethereum.github.io/yellowpaper/paper.pdf). The EVM address is not equivalent to the ECDSA public key.
The acceptable format for Hedera transactions is the account ID with the EVM address (`0.0.`). The acceptable format for Ethereum public addresses to denote an account address is the hex encoded public address.
**Example**
EVM Address: `b794f5ea0ba39494ce839613fffba74279579268`
HEX Encoded EVM Address: `0xb794f5ea0ba39494ce839613fffba74279579268`
Account ID with EVM Address: `0.0.b794f5ea0ba39494ce839613fffba74279579268`
The `..` format is only acceptable when specified in the `TransferTransaction`, `AccountInfoQuery`, and `AccountBalanceQuery` transaction types. If this format is used to specify an account in any other transaction type, the transaction will not succeed.
Reference Hedera Improvement Proposal: [HIP-583](https://hips.hedera.com/hip/hip-583)
## **Auto Account Creation Flow**
### **1. Create an account alias**
Create an account alias and convert it to the alias account ID format. The alias account ID format requires appending the shard number and realm numbers to the account alias. This form of account is purely a local account, i.e., not registered with the Hedera network.
### **2. Deposit tokens to the account alias account ID**
Once the alias Account ID exists, create a **TransferTransaction** that sends HBAR or HTS tokens to that alias. Sending tokens triggers auto account creation on the network, and the first token transferred is automatically associated with the new account.
When the transfer executes, the network:
* Creates a **child** account-creation transaction just before the transfer
* Assigns a new account number and stores the alias on the account
* Sets the memo to indicate an auto-created account
* Leaves the account **hollow** until the matching ECDSA key signs a transaction (see [Complete a Hollow Account](#complete-a-hollow-account))
The **parent** transaction is the token transfer. The **child** is the account creation. They share a transaction ID; the child uses the same ID with a nonce increment. To fetch the new account ID, query the child record or request the parent record with child records included.
The **payer** of the transfer covers both the token transfer fee and the account creation fee.
#### **Note**
The account-creation child transaction is timestamped just before the transfer (a minimal offset).\
Parent and child share the same Transaction ID; the child uses the same ID with a nonce increment.\
Fees for both the account creation and the transfer are charged **in tinybars** to the payer of the parent transfer.\
To retrieve the new Account ID, either request the parent record with child records included or query the child record directly by incrementing the nonce.
### **3. Get the new account number**
You can obtain the new account number in any of the following ways:
* Request the parent transaction record or receipt and set the child transaction record boolean flag equal to true.
* Request the transaction receipt or record of the account create transaction by using the transaction ID of the parent transfer transaction and incrementing the nonce value from 0 to 1.
* Specify the account alias account ID in an `AccountInfoQuery` transaction request. The response will return the account's account number account ID.
* Inspect the parent transfer transaction record transfer list for the account with a transfer equal to the token transfer value.
## Auto Account Creation with an EVM Address
When the alias is an EVM address, the network creates a **hollow account**. A hollow account has an account number and alias but no key. It can receive tokens, but it cannot send tokens or modify account properties until it is a complete account.
### Complete a Hollow Account
To complete a hollow account, submit a Hedera transaction that:
1. Make the hollow account the **fee payer** for a Hedera transaction, and
2. Sign that transaction with the **ECDSA private key** that matches the EVM address.
If either condition is missing, the transaction is rejected. After completion, the account behaves like a regular Hedera account.
#### **Using HAPI (SDKs)**
* Build a transaction (for example, a small [`TransferTransaction`](/native/accounts/transfer)).
* Set the transaction’s payer to the hollow account in your SDK.
* Sign with the corresponding ECDSA key and execute.
#### **Using EVM Wallets via JSON-RPC**
* Send the first transaction from the new account in the wallet.
* EVM wallets will set the new account as the transaction fee payer when users sign transactions to complete the account. No further action is required as the RPC will set the users account ID as the fee payer.
## **Automatic Token Associations for Completed Accounts**
Once a hollow account has been converted into a complete account by acting as the payer for a transaction and signing with its ECDSA private key, it inherits the default automatic association settings. Specifically, the account’s `maxAutoAssociations` property defaults to `–1`, enabling unlimited automatic token associations. This means that any subsequent HTS tokens transferred to the completed account will be automatically associated, and the recipient does not need to manually associate with each token. This behavior is part of frictionless airdrops ([HIP‑904](https://hips.hedera.com/hip/hip-904)) and differs from earlier network versions where auto‑association for new tokens was not available.
## EVM Developer Reference
Building a dApp? See [Account Model for EVM Developers](/evm/development/accounts) for how hollow accounts and long-zero accounts affect EVM tooling, wallet compatibility, and token association.
## Examples
* [Java](https://github.com/hiero-ledger/hiero-sdk-java/blob/main/examples/src/main/java/com/hedera/hashgraph/sdk/examples/AutoCreateAccountTransferTransactionExample.java)
* [JavaScript](https://github.com/hiero-ledger/hiero-sdk-js/blob/main/examples/account/transfer-using-evm-address.js)
* [Go](https://github.com/hiero-ledger/hiero-sdk-go/blob/main/examples/account_create_token_transfer/main.go)
* [C++](https://github.com/hiero-ledger/hiero-sdk-cpp/blob/main/src/sdk/examples/AutoCreateAccountTransferTransactionExample.cpp)
# Hiero Hooks
Source: https://docs.hedera.com/learn/core-concepts/accounts/hiero-hooks
Hiero Hooks provide programmable extension points to inject Solidity-based logic directly into the network's transaction pipeline. Hooks attach to accounts to enforce custom rules on actions like token transfers, but they do not run automatically—a hook is triggered only when explicitly referenced in a `TransferTransaction` (e.g., `CryptoTransfer`).
Unlike regular smart contracts, hooks execute in a special EVM context where `address(this)` is always the reserved system address `0x16d`, enabling them to act with the privileges of the account they're attached to. This model combines smart contract flexibility with native HAPI transaction efficiency, allowing custom validation without deploying full-scale contracts.
## Core Concepts
Hooks are a mechanism for [**Account Abstraction**](/support/glossary#account-abstraction) on Hedera, enabling custom validation and logic without migrating entire applications to the EVM. A hook is a small piece of Solidity logic that is **triggered only when referenced/specified in a `TransferTransaction`**—not automatically.
Think of it like a webhook for the ledger itself. Instead of waiting for an off-chain call, the hook runs inside the network when a transaction explicitly references it. Hooks can check conditions before execution, update state, log data, or stop a transfer if validation fails.
### Why Hooks?
Before Hooks, developers faced two major limitations:
1. **Protocol dependency**: New functionality required network-wide upgrades through HIPs (slow and heavyweight)
2. **EVM migration**: Moving applications to smart contracts sacrificed the performance and cost-efficiency of native HAPI transactions
Hooks solve this by allowing developers to inject custom logic directly into native flows, offering better performance and lower cost than general-purpose `ContractCall` operations.
### Key Characteristics
| Concept | Description |
| :------------------ | :----------------------------------------------------------------------------------------------------- |
| **Trigger Model** | **Triggered only when referenced/specified** in a `TransferTransaction`—not automatic event listeners. |
| **Implementation** | EVM Hooks: Solidity contracts executed by the network's EVM. |
| **Extension Point** | Account Allowance Hooks validate transfers during a `CryptoTransfer`. |
| **Key Advantage** | Custom logic on native assets (HBAR and HTS tokens) without `ContractCall` overhead. |
| **Use Cases** | Compliance rules, transfer constraints, one-time passcodes, receiver signature waivers. |
***
## Extension Points
Hooks attach to specific extension points in a transaction's lifecycle. An extension point defines the type of hook allowed for a transaction but doesn't specify when or why a hook is activated.
Currently, the first supported extension point is the Account Allowance Hook (`ACCOUNT_ALLOWANCE_HOOK`). This hook runs when a `TransferTransaction` references the hook on a transfer entry, acting as a programmable replacement for traditional ERC-style allowances.
Future extension points may include other native transaction types or entity lifecycle events, enabling hooks to validate or augment a wide range of on-chain operations.
***
## Propose a New Hook
Hooks are designed to be extended by the community. If you have a use case that would benefit from a new extension point — such as hooks for topic submissions, token minting, or scheduled transactions — you can propose it through the [Hiero Improvement Proposal (HIP) process](https://hips.hedera.com/).
To get started:
1. Review the [HIP-1195 specification](https://hips.hedera.com/hip/hip-1195) to understand the existing hooks architecture and extension point model
2. Draft a new HIP that defines your proposed extension point, its trigger conditions, and the Solidity interface hooks would implement
3. Submit your proposal to the [Hiero HIP repository](https://github.com/hiero-ledger/hiero-improvement-proposal) for community review and discussion
The hooks framework is built to support new extension points without protocol-level changes to the core hook infrastructure.
# Accounts
Source: https://docs.hedera.com/learn/core-concepts/accounts/index
Learn how Hedera accounts work: account IDs, keys, HBAR and HTS token balances, transaction fees, and the core entity model behind every network interaction.
Accounts are the central starting point when interacting with the Hedera network and using Consensus Node services. A Hedera account is an entity, a distinct object type, stored in the ledger, that holds tokens. Accounts can hold the native Hedera fungible token (HBAR), custom fungible, and custom non-fungible tokens (NFTs) created on the Hedera network.
The Hedera native token HBAR (ℏ) is a utility token primarily used to pay for transactions and query fees when interacting with the network. The HBAR symbol is represented as "ℏ." Applications may reference HBAR as the token denomination; however, the network returns information in tinybars (tℏ), a denomination of HBAR. 100,000,000 tℏ are equivalent to 1 ℏ. This includes things like transaction fees or accounts HBAR balances.
You interact with the network by submitting transactions that modify the ledger's state or submitting query requests that read data from the ledger. Most transactions and queries have a [transaction fee](/networks/fees) that is charged in HBAR. Unlike custom tokens users create on the Hedera network, no token ID represents the native HBAR token.
## FAQs
A Hedera account is a unique entity in the Hedera Network that can hold tokens. These can be Hedera's native fungible token (HBAR), custom fungible, or [non-fungible tokens (NFTs)](/support/glossary#non-fungible-token-nft).
New accounts are created by submitting a transaction to the network and paying
the transaction fee. You'll need access to an existing account with sufficient
HBAR to cover this fee. If you don't have access to an existing account, you
can use a supported wallet, visit the [Hedera Developer
Portal](https://portal.hedera.com/), or use the "Auto Account Creation"
feature for applications.
[Auto Account Creation](/learn/core-concepts/accounts/auto-account-creation)
allows applications to generate free user accounts instantly, even without an
internet connection, by creating an account alias.
A hollow account is created when value is transferred to an [EVM Address from Public Key](/learn/core-concepts/accounts/auto-account-creation#auto-account-creation-with-an-evm-address) via [Auto Account Creation](/learn/core-concepts/accounts/auto-account-creation) without a corresponding key on file. It has an account number and an EVM address but no account key. It can receive tokens but cannot send tokens or modify account properties until the matching ECDSA key is added, completing the account. This is the standard onboarding path for EVM-wallet users.
# Network Accounts
Source: https://docs.hedera.com/learn/core-concepts/accounts/network-accounts
The Hedera network uses several special, network-controlled accounts for its operations. These accounts are fundamental to the network's fee structure, staking rewards, and overall economic model.
### Special Accounts Comparison
| Account ID | Name | Purpose | Accepts Deposits? | Has Keys? |
| :--------- | :------------------------ | :--------------------------------------------------------------------------------------------- | :---------------- | :-------- |
| `0.0.98` | Network Admin Fee Account | Receives the majority of network transaction fees (typically 80%). | Yes | Yes |
| `0.0.800` | Staking Rewards Account | Holds funds for staking reward distribution. Receives a portion of daily fees (typically 10%). | Yes (donations) | No |
| `0.0.801` | Node Rewards Account | Holds funds for node reward distribution. Receives a portion of daily fees (typically 10%). | No | No |
| `0.0.802` | Fee Collection Account | Consolidates all transaction fees before daily distribution. | No | No |
### Account Details
#### Fee Collection Account (`0.0.802`)
Introduced in [HIP-1259](https://hips.hedera.com/hip/hip-1259), this account serves as a temporary holding account for all transaction fees. It is fully code-controlled, has no keys, and cannot accept HBAR deposits from users. This design simplifies transaction records and improves network efficiency.
#### Staking Rewards Account (`0.0.800`)
This account holds the HBAR that will be distributed to users who are staking their tokens. It receives a portion of the daily accumulated fees from the Fee Collection Account. While it does not have keys, it can accept HBAR donations from the community.
#### Node Rewards Account (`0.0.801`)
This account holds the HBAR that will be distributed to node operators for their services. It receives a portion of the daily accumulated fees from the Fee Collection Account. It does not have keys and cannot accept HBAR deposits.
#### Network Admin Fee Account (`0.0.98`)
This account receives the largest portion of the daily accumulated fees from the Fee Collection Account (typically 80%). These funds are used to support the long-term growth and development of the Hedera network. Unlike the other special fee accounts, this account has keys and can accept HBAR deposits.
# Fee Model
Source: https://docs.hedera.com/learn/core-concepts/fee-model
Understand the simplified `base-fee-plus-extras` model for Hedera transaction and query fees introduced by [HIP-1261](https://hips.hedera.com/hip/hip-1261)
## Overview
Hedera uses a simplified fee model where every transaction cost is calculated as a **base fee plus extras**. Introduced in [HIP-1261](https://hips.hedera.com/hip/hip-1261), this model replaces the previous resource-weighted fee schedule with transparent, predictable pricing.
All fees are defined in **USD as tinycents** and converted to HBAR at the current network exchange rate before being charged. The fee schedule is stored as a JSON document in system file `0.0.113` on the network.
**What is a tinycent?** One cent USD = 10⁸ tinycents. One dollar USD = 10¹⁰ tinycents. Tinycents provide high precision for fee calculations without floating-point math.
| Term | Definition |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Base Fee** | The fixed minimum fee in tinycents for a transaction or query before any extras are applied. |
| **Extras** | Additional cost factors on top of the base fee, such as signatures, bytes, keys, or gas. Each has a per-unit fee and an optional included count. |
| **Included Count** | Units of an extra included for free in the base fee before additional charges apply. |
| **Tinycent** | The smallest fee unit. 10⁸ tinycents = 1 cent USD. 10¹⁰ tinycents = 1 USD. |
| **Node Fee** | Fee paid to the submitting node. Same calculation for all transaction types. |
| **Network Fee** | A multiplier of the node fee covering consensus and storage. |
| **Service Fee** | Covers execution costs. Varies by transaction type. |
## Fee Components
Every transaction fee is split into three components:
| Component | What It Covers | How It's Calculated |
| ----------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| **Node** | Compensates the submitting node for pre-checking and forwarding the transaction | `baseFee` + extras (processing bytes, signatures). Identical formula for **all** transaction types. |
| **Network** | Covers gossip, consensus, signature verification, and blockchain storage | A configurable multiplier of the node fee (default: 9×). |
| **Service** | Covers execution costs, state changes, and blockstream output | `baseFee` + transaction-specific extras (keys, token types, gas, etc.). Varies by transaction type. |
```text theme={null}
totalFee = nodeFee + networkFee + serviceFee
```
The node and network fees are uniform across all transaction types — only the service fee varies per transaction.
## Extras
Extras are additional cost factors applied on top of a base fee. Each extra has a **name**, a **per-unit fee** (in tinycents), and an optional **included count** — the number of units included for free before additional charges apply. Hedera transaction and query fees follow a `base fee + extras` [fee model](/learn/core-concepts/fee-model) defined by [HIP-1261 (Simple Fees)](https://hips.hedera.com/hip/hip-1261). For current per-unit fees in USD, see the [mainnet fees page](/networks/fees#extras).
The following extras are defined in the fee schedule:
| Extra | Description |
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `Signatures` | Signature verifications on the transaction. 1 included in base fee. |
| `Keys` | Keys defined when creating or updating an entity. 1 included in base fee. |
| `Accounts` | Accounts loaded during handling. 2 included on `CryptoTransfer`. |
| `TokenTypes` | Distinct token types referenced in a transfer. 1 included in base fee. |
| `Gas` | Gas consumed by hook program execution within transfers and by `ContractCallLocal` queries. |
| `Allowances` | Allowances granted on `ApproveAllowance`, or NFT allowances deleted on `DeleteAllowance`. 1 included in base fee. |
| `Airdrops` | Pending airdrops created — applies only when the recipient hasn't pre-associated the token. |
| `TokenTransferBase` | Applies once per `CryptoTransfer` with one or more token transfers when no token has custom fees. |
| `TokenTransferBaseCustomFees` | Applies once per `CryptoTransfer` with at least one custom-fee token. |
| `TokenCreateWithCustomFee` | Added to `TokenCreate` when custom fees or a `fee_schedule_key` are defined. |
| `TokenMintNft` | NFT serials minted on `TokenMint`. 1 included in base fee. |
| `TokenMintNftBase` | Added once per `TokenMint` operation that mints NFTs (not fungible). |
| `NftUpdate` | NFTs updated on `TokenUpdateNfts`. 1 included in base fee. |
| `TokenAssociate` | Token associations on `TokenAssociate` or auto-associations during a transfer. 1 included in base fee. |
| `ConsensusCreateTopicWithCustomFee` | Added to `ConsensusCreateTopic` when the new topic has custom fees defined. |
| `ConsensusSubmitMessageWithCustomFee` | Per message submitted to a topic with custom fees. |
| `ConsensusSubmitMessageWithCustomFeeBytes` | Per byte of message payload on a custom-fee topic. First 1,024 bytes included in base fee. |
| `ConsensusSubmitMessageWithoutCustomFeeBytes` | Per byte of message payload on a regular topic. First 100 bytes included in base fee. |
| `ScheduleCreateContractCallBase` | Added to `ScheduleCreate` when the scheduled inner operation is a contract call. |
| `Records` | Records returned by `TransactionGetRecord`. 1 included in base fee. |
| `StateBytes` | Bytes persisted to state on `FileCreate`, `FileUpdate`, or `FileAppend`. 1,000 bytes included per transaction. |
| `ProcessingBytes` | Transaction body bytes processed by the node. 1,350 included in the base fee. |
| `EvmDispatchSurcharge` | A 20% premium added to the HAPI fee of any operation invoked from a smart contract via Hedera's system contracts (HTS, HAS, HSS). Does not apply to pure-EVM operations. |
Hedera transaction and query fees follow a `base fee + extras` [fee model](/learn/core-concepts/fee-model) defined by [HIP-1261 (Simple Fees)](https://hips.hedera.com/hip/hip-1261).
The **included count** means you don't pay extra for typical usage. For example, the node fee includes a default allotment of processing bytes and one signature — a small, single-signature transaction pays zero byte and signature extras on the node component.
## Fee Calculation Example
Consider a basic `CryptoCreate` transaction with a single key and 150 bytes:
The node fee applies the same formula to all transactions:
```text wrap theme={null}
Node baseFee: 100,000 tinycents
ProcessingBytes extra: 150 bytes used, 1,024 included → 0 charged → 0
Signatures extra: 1 signature, 1 included → 0 charged → 0
─────────────────────────────────────────────────────────────────────
Node fee total: 100,000 tinycents
```
The network fee is a multiplier of the node fee:
```text theme={null}
Network fee = 9 × 100,000 = 900,000 tinycents
```
The service fee is specific to `CryptoCreate`:
```text wrap theme={null}
Service baseFee: 499,000,000 tinycents
Keys extra: 1 key used, 1 included → 0 charged → 0
──────────────────────────────────────────────────────────
Service fee total: 499,000,000 tinycents
```
```text wrap theme={null}
Total = 100,000 + 900,000 + 499,000,000 = 500,000,000 tinycents ≈ $0.05 USD
```
This amount is converted to HBAR at the current exchange rate and charged to the payer.
If the same transaction used **two** keys instead of one, the service fee would increase by the per-key extra fee (e.g., 10,000,000 tinycents), because the included count of 1 key is exceeded by 1.
## Transaction Outcomes and Fees
Not all transactions succeed. The fee charged depends on how far the transaction progresses:
| Outcome | Description | Who Pays | Components Charged |
| -------------- | ---------------------------------------------------------------------------------------------------------------- | --------------- | ------------------------------- |
| **Successful** | Transaction executed normally | Payer | Node + Network + Service |
| **Bad** | Passed due-diligence but failed during execution (e.g., out of gas, semantically wrong, inconsistent with state) | Payer | Node + Network + Service (full) |
| **Unhandled** | Well-formed but not executed (e.g., throttled, duplicate, unexecuted portion of an atomic batch) | Payer | Node + Network |
| **Invalid** | Failed due-diligence checks by the submitting node (e.g., payer can't afford the fee, incompatible fields) | Submitting node | Network only |
| **Unreadable** | Bytes cannot be parsed as a valid protobuf `Transaction` | Submitting node | Punitive flat fee |
Bad transactions are charged full freight (node + network + service) to protect the network from denial-of-service attacks. This applies even if the transaction fails due to a bug (`FAIL_INVALID`).
## Congestion Pricing
[HIP-1313](https://hips.hedera.com/hip/hip-1313#hip-1313) introduces an optional high-volume lane for entity-creation transactions above the standard throttle. When a transaction opts in with `setHighVolume(true)`, the network may apply a fee multiplier if the high-volume throttle bucket is under load. The multiplier scales with congestion level and is reflected in the `high_volume_multiplier` field of the fee estimate response.
The `high_volume_multiplier` field uses a 1-based scale `(1 = 1×, 4 = 4×)`. `TransactionRecord.highVolumePricingMultiplier` — available after execution — uses a `1000-based scale (1000 = 1×, 4000 = 4×)`. Both represent the same multiplier.
To simulate the fee at a specific congestion level before committing, use `setHighVolumeThrottle()` on `FeeEstimateQuery`. See [Estimating Fees](/native/fees/fee-estimation) with the SDK for code examples.
## Fee Schedule Configuration
The fee schedule is a JSON document stored in system file **`0.0.113`**. It defines:
| Section | Purpose |
| ------------ | --------------------------------------------------------------- |
| `extras` | All available extra fee definitions (name + per-unit fee) |
| `node` | Node fee configuration (base fee + extras with included counts) |
| `network` | Network fee configuration (multiplier) |
| `services` | Per-service groupings of transaction and query fee definitions |
| `unreadable` | Punitive fee for unparsable transaction bytes |
```json theme={null}
{
"version": 0,
"extras": [
{ "name": "Signatures", "fee": 100000 },
{ "name": "ProcessingBytes", "fee": 10000 },
{ "name": "Keys", "fee": 10000000 }
],
"node": {
"baseFee": 100000,
"extras": [
{ "name": "ProcessingBytes", "includedCount": 1024 },
{ "name": "Signatures", "includedCount": 1 }
]
},
"network": { "multiplier": 9 },
"services": [
{
"name": "CryptoService",
"transactions": [
{
"name": "CryptoCreate",
"baseFee": 499000000,
"extras": [
{ "name": "Keys", "includedCount": 1 }
]
}
],
"queries": []
}
],
"unreadable": { "fee": 100000000000 }
}
```
The legacy fee schedule in system file `0.0.111` remains available in its existing format for backward compatibility, but it will not receive further updates.
## Fee Estimation
You can estimate transaction fees before submitting them using the Mirror Node REST API:
```bash theme={null}
POST /api/v1/network/fees?mode=intrinsic
Content-Type: application/protobuf
```
The endpoint supports two modes:
| Mode | Behavior |
| --------------------- | ------------------------------------------------------------------------------------- |
| `intrinsic` (default) | Estimates based on the transaction's inherent properties (size, signatures, keys) |
| `state` | Estimates using the mirror node's latest known state (e.g., checks if accounts exist) |
See [Mirror Node REST API Network](/reference/rest-api/network) for the full endpoint specification and response format.
## Queries
Queries follow the same base-fee-plus-extras structure as transactions. Some queries are marked as `free` in the fee schedule (e.g., `CryptoGetAccountBalance`, `TransactionGetReceipt`). For non-free queries, the SDK creates a `CryptoTransfer` payment transaction to pay the node, network, and service fees.
## Fee Schedule Schema (Protobuf)
The fee schedule is defined as a set of protobuf messages. The wire format is JSON, stored in system file `0.0.113`.
### FeeSchedule
Top-level message defining the complete fee configuration.
| Field | Type | Description |
| ------------ | -------------------------------- | -------------------------------------------------------- |
| `node` | NodeFeeSchedule | How to compute the node fee component. **Required.** |
| `network` | NetworkFeeSchedule | How to compute the network fee component. **Required.** |
| `unreadable` | UnreadableTransactionFeeSchedule | Fee for unparsable transaction bytes. Optional. |
| `extras` | repeated ExtraFeeDefinition | All available extra fee definitions. No duplicate names. |
| `services` | repeated ServiceFeeSchedule | Fee configs per network service. No duplicate names. |
### ExtraFeeDefinition
Defines a single extra fee — an additional charge for a specific cost factor.
| Field | Type | Description |
| ------ | ------ | --------------------------------------------------------------- |
| `name` | string | Unique name. Must match `[A-Za-z].*[A-Za-z0-9]*`. **Required.** |
| `fee` | uint64 | Fee per unit in tinycents. Must be > 0. **Required.** |
### NodeFeeSchedule
Node fee configuration. Applied identically to **all** transaction types.
| Field | Type | Description |
| ---------- | -------------------------- | ----------------------------------------------------------- |
| `base_fee` | uint64 | Base fee in tinycents. Defaults to 0. |
| `extras` | repeated ExtraFeeReference | Extras for computing the node fee. No duplicate references. |
### NetworkFeeSchedule
Network fee configuration. Calculated as a multiplier of the node fee.
| Field | Type | Description |
| ------------ | ------ | ------------------------------------------------------ |
| `multiplier` | uint32 | Multiplied by the node fee. Must be ≥ 1. **Required.** |
### ServiceFeeSchedule
Groups transaction and query fee configs for a single gRPC service.
| Field | Type | Description |
| ---------- | ----------------------------- | --------------------------------------------------- |
| `name` | string | Service name (e.g., `CryptoService`). **Required.** |
| `schedule` | repeated ServiceFeeDefinition | Transaction/query fee configs. Must not be empty. |
### ServiceFeeDefinition
Fee definition for a single transaction or query.
| Field | Type | Description |
| ---------- | -------------------------- | ----------------------------------------------------------------------- |
| `name` | string | Transaction/query name (e.g., `CryptoCreate`). **Required.** |
| `base_fee` | uint64 | Base fee in tinycents. Defaults to 0. |
| `extras` | repeated ExtraFeeReference | Extras for this transaction/query. No duplicate references. |
| `free` | bool | If `true`, `base_fee` and `extras` are ignored — the operation is free. |
### ExtraFeeReference
References an ExtraFeeDefinition with an optional included count.
| Field | Type | Description |
| ---------------- | ------ | ----------------------------------------------------------------------- |
| `name` | string | Name of the referenced extra. Must match a defined extra. **Required.** |
| `included_count` | uint32 | Units included for free in the base fee. Defaults to 0. |
### UnreadableTransactionFeeSchedule
Punitive fee for nodes that submit unparsable bytes.
| Field | Type | Description |
| ----- | ------ | ----------------------------------------------- |
| `fee` | uint64 | Punitive fee in tinycents. Optional (may be 0). |
### Validation Rules
Before a new fee schedule takes effect, the network validates it. If any rule fails, the schedule is rejected.
The JSON must parse and conform to the `FeeSchedule` protobuf message. All required fields must be present and types must match. No unrecognized fields.
All `baseFee` and `fee` fields must be non-negative integers. For extras, `fee` must be strictly > 0.
The `multiplier` in `network` must be a positive integer ≥ 1.
All names must match `[A-Za-z].*[A-Za-z0-9]*`. Extra names, service names, and transaction/query names within each service must be unique.
Every extra reference must point to a defined extra. No duplicate references within a single list.
If `free` is `true`, `baseFee` and `extras` are ignored during calculation but must still comply with all validation rules if present.
## Related
The full Hiero Improvement Proposal specification.
Fee tables for all transaction and query types on mainnet.
Gas schedule and fee calculation for smart contracts.
The fee collection account model that Simple Fees depends on.
# Gossip About Gossip
Source: https://docs.hedera.com/learn/core-concepts/hashgraph/gossip-about-gossip
Hashgraph consensus uses a **gossip protocol**. This means that a member such as Alice will choose another member at random, such as Bob, and then Alice will tell Bob all of the information she knows so far. Alice then repeats with a different random member. Bob repeatedly does the same, and all other members do the same. In this way, if a single member becomes aware of new information, it will spread exponentially fast through the community until every member is aware of It.
The synchronization of information between two members through the gossip protocol is called a **gossip sync**. Upon completion of a gossip sync, each participating member commemorates the gossip sync with an event. An **event** is stored in memory as a data structure composed of a timestamp, an array of zero or more transactions, two parent hashes, and a cryptographic signature. The two parent hashes are the hash of the last event created by the self-parent prior to the gossip sync and the hash of the last event created by the other-parent prior to the gossip sync. For example, if Alice and Bob perform a gossip sync, Alice would create a new event commemorating the gossip sync where the self-parent hash would be the hash of the last event Alice created prior to the gossip sync and the other-parent hash would be the hash of the last event Bob created prior to the gossip sync. Bob would also create an event commemorating the gossip sync, but the self-parent hash would be the hash of the last event he created before the gossip sync and the other-parent hash would be the hash of the last event Alice created before the gossip sync. Gossip continues until all members have received the newly created event.
## Gossip About Gossip
The history of how these events are related to each other through their parent hashes is called **gossip about gossip**. This history expresses itself as a type of directed acyclic graph (DAG), a graph of hashes, or a hashgraph. The hashgraph records the history of how members communicated. It grows directionally over time as more gossip syncs take place and events are created. All members keep a local copy of the hashgraph which continues to update as members sync with one another.
These hashgraphs may be slightly different at any given moment, but they will always be consistent. Consistent means that if \[Alice] and \[Bob] both contain event x, then they will both contain exactly the same set of ancestors for x, and will both contain exactly the same set of edges between those ancestors.
Each event contains the following:
* Timestamp
* Two hashes of two events below itself
* Self-parent
* Other-parent
* Transactions
* Digital signature
| Item | Description |
| ---------------------- | -------------------------------------------------------------------------------- |
| **Timestamp:** | The timestamp of when the member created the event commemorating the gossip sync |
| **Transactions:** | The event can hold zero or more transactions |
| **Hash 1:** | Self-parent hash |
| **Hash 2:** | Other-parent hash |
| **Digital Signature:** | Cryptographically signed by the creator of the event |
# Hashgraph Consensus Algorithm
Source: https://docs.hedera.com/learn/core-concepts/hashgraph/index
Distributed consensus algorithm
The hashgraph consensus algorithm enables distributed consensus in an innovative, efficient way. Hashgraph is a distributed consensus algorithm and data structure that is fast, fair, and secure. This indirectly creates a trusted community, even when members do not necessarily trust each other.
The [hashgraph consensus algorithm](/learn/core-concepts/hashgraph) and platform code are open-source under an Apache 2.0 license.
## Performance
### Cost
The hashgraph is inexpensive, in the sense of avoiding proof-of-work. Individuals and organizations running hashgraph nodes do not need to purchase expensive custom mining rigs. Instead, they can run readily available, cost-effective hardware. The hashgraph is 100% efficient, wasting no resources on computations that slow it down.
### Efficiency
The hashgraph is 100% efficient, as that term is used in the blockchain community. In blockchain, work is sometimes wasted mining a block that later is considered stale and is discarded by the community. In hashgraph, the equivalent of a “block” never becomes stale. Hashgraph is also efficient in its use of bandwidth. Whatever is the amount of bandwidth required merely to inform all the nodes of a given transaction (even without achieving consensus on a timestamp for that transaction), hashgraph adds only a very small overhead beyond that absolute minimum. Additionally, hashgraph’s voting algorithm does not require any additional messages be sent in order for nodes to vote (or those votes to be counted) beyond those messages by which the community learned of the transaction itself.
### Throughput
The hashgraph is fast. It is limited only by the bandwidth. If each member has enough bandwidth to download and upload a given number of transactions per second, the system as a whole can handle close to that many. Even a fast home internet connection could be fast enough to handle all of the transactions of the entire VISA card network, worldwide.
### **State Efficiency**
Once an event occurs, within seconds everyone in the community will know where it should be placed in history with 100% certainty. More importantly, everyone will know that everyone else knows this. At that point, they can just incorporate the effects of the transaction and, unless needed for future audit or compliance, then discard it. So in a minimal cryptocurrency system, each member would only need to store the current balance of each account that isn’t empty. They wouldn’t need to remember the full history of the transactions that resulted in those balances all the way back to ‘genesis’.
## Security
### Asynchronous Byzantine Fault Tolerance
The hashgraph consensus algorithm is asynchronous Byzantine Fault Tolerant. This means that no single member (or small group of members) can prevent the community from reaching a consensus. Nor can they change the consensus once it has been reached. Each member will eventually reach a point where they know for sure that they have reached consensus. Blockchain does not have a guarantee of Byzantine agreement, because a member never reaches certainty that agreement has been achieved (there’s just a probability that rises over time). Blockchain is also non-Byzantine because it doesn’t automatically deal with network partitions. If a group of miners is isolated from the rest of the internet, that can allow multiple chains to grow, which conflict with each other on the order of transactions.
It is worth noting that the term “Byzantine Fault Tolerant” (BFT) is sometimes used in a weaker sense by other consensus algorithms. But here, it is used in its original, stronger sense that (1) every member eventually knows consensus has been reached, (2) attackers may collude, and (3) attackers even control the internet itself (with some limits). Hashgraph is Byzantine, even by this stronger definition.
There are different degrees of BFT, depending on the assumptions made about the network and transmission of messages. The strongest form of BFT is asynchronous BFT- meaning that it can achieve consensus even if malicious actors are able to control the network and delete or slow down messages of their choosing. The only assumptions made are that more than 2⁄3 are following the protocol correctly and that if messages are repeatedly sent from one node to another over the internet, eventually one will get through, and then eventually another will, and so on. Some systems are partially asynchronous, which are secure only if the attackers do not have too much power and do not manipulate the timing of messages too much. For instance, a partially asynchronous system could prove Byzantine under the assumption that messages get passed over the internet in ten seconds. This assumption ignores the reality of botnets, Distributed Denial of Service attacks, and malicious firewalls.
### ACID Compliance
The hashgraph is ACID compliant. ACID (Atomicity, Consistency, Isolation, Durability) is a database term and applies to the hashgraph when it is used as a distributed database. A community of nodes uses it to reach a consensus on the order in which transactions occurred. After reaching consensus, each node feeds those transactions to that node’s local copy of the database, sending in each one in the consensus order. If the local database has all the standard properties of a database (ACID), then the community as a whole can be said to have a single, distributed database with those same properties. In blockchain, there is never a moment when you know that consensus has been reached, so it would not be ACID compliant.
### Distributed Denial of Service (DDoS) Attack Resilience
One form of Denial of Service (DoS) attack occurs when an attacker is able to flood an honest node on a network with meaningless messages, preventing that node from performing other (valid) duties and roles. A Distributed Denial of Service (DDoS) uses public services or devices to unwittingly amplify that DoS attack - making them an even greater threat.
In a distributed ledger, a DDoS attack could target the nodes that contribute to the definition of consensus and, potentially, prevent that consensus from being established.
Hashgraph is DDoS resilient as it empowers no single node or a small number of nodes with special rights or responsibilities in establishing consensus. Both Bitcoin and hashgraph are distributed in a way that resists DDoS attacks. An attacker might flood one member or miner with packets, to temporarily disconnect them from the internet. But the community as a whole will continue to operate normally. An attack on the system as a whole would require flooding a large fraction of the members with packets, which is more difficult. There have been a number of proposed alternatives to blockchain-based on leaders or round-robin. These have been proposed to avoid the proof-of-work costs of Bitcoin. But they have the drawback of being sensitive to DDoS attacks. If the attacker attacks the current leader, and switches to attacking the new leader as soon as one is chosen, then the attacker can freeze the entire system while still attacking only one computer at a time. Hashgraph avoids this problem, while still not needing proof-of-work.
## Fairness
Hashgraph is fair because there is no leader or miner given special permissions for determining the consensus timestamp assigned to a transaction. Instead, the consensus timestamp for transactions are calculated via a voting process in which the nodes collectively and democratically establish the consensus. We can distinguish between three aspects of fairness.
### Fair Access
Hashgraph is fundamentally fair because no individual can stop a transaction from entering the system, or even delay it very much. If one (or few) malicious nodes attempts to prevent a given transaction from being delivered to the rest of the community and so be added into consensus, then the random nature of the gossip protocol will ensure that the transaction flows around that blockage.
### Fair Timestamps
Hashgraph gives each transaction a consensus timestamp that reflects when the majority of the network members received that transaction. This consensus timestamp is “fair”, because it is not possible for a malicious node to corrupt it and make it differ by very much from that time. Every transaction is assigned a consensus time, which is the median of the times at which each member says it first received it. Received here refers to the time that a given node was first passed the transaction from another node through gossip. This is part of the consensus, and so has all the guarantees of being Byzantine. If more than 2⁄3 of participating members are honest and have reliable clocks on their computer, then the timestamp itself will be honest and reliable, because it is generated by an honest and reliable member or falls between two times that were generated by honest and reliable members. Because hashgraph takes the median of all these times, the consensus timestamp is robust. Even if a few of the clocks are a bit off, or even if a few of the nodes maliciously give times that are far off, the consensus timestamp is not significantly impacted.
This consensus timestamping is useful for things such as a legal obligation to perform some action by a particular time. There will be a consensus on whether an event happened by a deadline, and the timestamp is resistant to manipulation by an attacker. In a blockchain, each block contains a timestamp, but it reflects only a single clock: the one on the computer of the miner who mined that block.
### Fair Transaction Order
Transactions are put into order according to their timestamps. Because the timestamps assigned to individual transactions are fair, so is the resulting order. This is critically important for some use cases. For example, imagine a stock market, where Alice and Bob both try to buy the last available share of a stock at the same moment for the same price. In a blockchain, a miner might put both of those transactions in a single block, and have complete freedom to choose what order they occur. Or the miner might choose to only include Alice’s transaction, and delay Bob’s to a future block. In hashgraph, there is no way for an individual to unduly affect the consensus order of those transactions. The best Alice can do is to invest in a better internet connection so that her transaction reaches everyone before Bob’s. That’s the fair way to compete.
## FAQ
The hashgraph consensus algorithm is a distributed consensus mechanism used by Hedera. It uses a data structure called a [hashgraph](/support/glossary#hashgraph), and a consensus mechanism called the Gossip protocol. This combination allows for fast, fair, and secure consensus. The algorithm works by each node in the network sharing information (or “gossiping”) about the transactions it knows about with other nodes in random order.
Hashgraph is secure because it is asynchronous Byzantine Fault Tolerant (aBFT). This means that no single member or small group of members can prevent the community from reaching a consensus or changing the consensus once it has been reached. It is also ACID compliant when used as a distributed database, and it is resilient to [Distributed Denial of Service (DDoS)](/support/glossary#distributed-denial-of-service-ddos) attacks.
Virtual voting is an integral part of the hashgraph consensus algorithm. It allows nodes to know what others would vote for without needing actual votes sent over the internet. This is accomplished by examining the history of gossip (who spoke to whom and in what order) to determine how a node would vote based on the information it is likely to have.
# Virtual Voting
Source: https://docs.hedera.com/learn/core-concepts/hashgraph/virtual-voting
It is not enough to ensure that every member knows every event. It is also\
necessary to agree on a linear ordering of the events, and thus of the transactions\
recorded inside the events. Most Byzantine fault tolerance protocols without a\
leader depend on members sending each other votes...Some of these\
protocols require receipts on votes sent to everyone...And they\
may require multiple rounds of voting, which further increases the number of voting\
messages sent.
This pure voting approach becomes bandwidth prohibitive and impractical in a network of any significant size but has the properties of being the fairest and most secure method of reaching consensus. The hashgraph algorithm implements voting that achieves the same fair and secure properties but is also very fast and practical. It accomplishes this through **virtual voting**.
The hashgraph algorithm does not require any votes to be sent across the network to calculate the votes of each member. Members can calculate every other member’s votes by internally looking at each of their copies of the hashgraph and applying the virtual voting algorithm. Votes are calculated locally as a function of the ancestors of a given event.
This virtual voting has several benefits. In addition to saving bandwidth, it ensures that members always calculate their votes according to the rules. If Alice is honest, she will calculate virtual votes for the virtual Bob that are honest. Even if the real Bob is a cheater, he cannot attack Alice by making the virtual Bob vote incorrectly.
With this virtual voting algorithm, Byzantine agreement is guaranteed.
Virtual voting happens in 3 steps:
1. Divide Rounds
2. Decide Fame
3. Find Order
## Divide Rounds
To begin the process of virtual voting, we must first define rounds and witnesses. In the hashgraph history, the first event for a member’s node is that node’s first **witness**. The first witness is the beginning of the first round (r) for that node. All subsequent events are part of that first round until a new witness is discovered. A witness is discovered when a node creates a new event that can **strongly see** ⅔ of the witnesses in the current round. For example, event w can strongly see event x when w can trace its ancestry through parent relationships that pass through other events that reside on at least ⅔ of the member nodes. When an event is determined to strongly see ⅔ of the witness of the current round, that event is considered the next witness for that node. That new witness is the first event in the next round (r+1) for that node. Each event is assigned a round as the event is added to the hashgraph.
```
procedure divideRounds
for each event x
r <- max round of parents of x (or 1 if none exist)
if x can strongly see more than 2n/3 round r witnesses
x.round <- r+1
else
x.round <- r
x.witness <- (x has no self parent)
or (x.round > x.selfParent.round)
```
## Decide Fame
The next step is deciding whether a witness is a famous witness or not. A witness is famous if many of the witnesses in the next round can see it, and it is not famous if many can’t. Event A can **see** Event B if Event B is an ancestor of Event A. When deciding the fame of Witness A, we must look at the witnesses of the following round. If the witnesses of the following round can see Witness A, they count as a vote in favor of Witness A’s fame. Likewise, if a witness in the next round can not see Witness A, then that witness’ vote is that Witness A is not famous. In order for Witness A to be considered famous, a future witness must be able to strongly see that at least ⅔ of voting witnesses have voted in favor of Witness A being famous. If ⅔ of voting witnesses have voted that Witness A is not famous, then Witness A will be decided to be not famous.
## Find Order
Now that we have calculated the all witnesses of a round to be famous or not famous, we can determine the order of events that occurred before the famous witness events. This is done by calculating:
1. The **round received** for all events that have yet to be ordered and that have occurred before a round where the fame of all witnesses has been decided. The event’s round received is the first round where all famous witnesses of that round can see (or are descendants of) the event in question.
2. The **timestamp** for each event. This is done by gathering the earliest ancestors of the famous witnesses of the round received that are also descendants of the event in question, and taking the median timestamp of those gathered events.
3. The **ordering of events** first by: round received, consensus timestamp, then signature.
# High-Volume Entity Creation
Source: https://docs.hedera.com/learn/core-concepts/high-volume-entity-creation
Understand how high-volume throttles work, how variable-rate pricing affects your transaction fees, and how to use the high_volume flag effectively.
Hedera's standard throttle system limits entity creation transactions (like `CryptoCreate`,
`TokenCreate`, and `TokenMint`) to relatively modest per-second rates. This protects the
network but can be a bottleneck for applications that need to create entities at scale.
For example, bulk user onboarding, large NFT drops, or automated token deployments.
[HIP-1313](https://hips.hedera.com/hip/hip-1313) introduces a **high-volume throttle system**
that runs in parallel alongside the standard system. By setting `high_volume = true` on a
supported transaction, you opt into dedicated capacity with higher throughput limits. The
tradeoff: **fees are variable and increase with utilization.**
The standard throttle system is completely unaffected. Existing applications that do not
set the `high_volume` flag will work exactly as before - same capacity, same pricing.
***
## How It Works
### Two Parallel Throttle Systems
When you submit an entity creation transaction, the network routes it to one of two
independent throttle systems based on the `high_volume` flag:
| | Standard Throttles | High-Volume Throttles |
| ------------ | ------------------------ | --------------------------------------- |
| **Opt-in** | Default (no flag needed) | Set `high_volume = true` |
| **Capacity** | Current published limits | Dedicated high-volume buckets |
| **Pricing** | Fixed fee schedule | Variable-rate (scales with utilization) |
| **Priority** | Consensus order | Consensus order (no priority boost) |
The two systems have completely separate capacity pools. Using one does not consume
capacity from the other.
### Variable-Rate Pricing
This is the most important thing to understand before using high-volume mode.
Unlike standard transactions where fees are predictable from the
[fee schedule](/networks/fees), high-volume fees change dynamically based on how
much of the high-volume capacity is currently in use:
The network calculates how much of the high-volume throttle capacity is currently being
consumed, expressed as a utilization percentage from 0% (idle) to 100% (saturated).
A governance-configured **pricing curve** converts the utilization percentage into a fee
multiplier. The curve is defined as a piecewise linear function — a series of
(utilization, multiplier) breakpoints with linear interpolation between them. For
example, given points (0%, 1.0×), (50%, 2.0×), and (100%, 5.0×), a utilization of 75%
would produce a multiplier of 3.5×.
The base transaction fee (from the normal fee schedule) is multiplied by the current
multiplier to produce the final fee. A `max_multiplier` caps how high the multiplier can
go, preventing extreme pricing even at 100% utilization.
If the calculated fee exceeds the `maxTransactionFee` you set on the transaction, the
transaction fails with `INSUFFICIENT_TX_FEE` and you are **not** charged. This is your
primary cost-control mechanism.
Depending on the governance-configured pricing curve, high-volume transactions **may**
cost more than the same transaction sent through the standard throttle — even when
there is no congestion. Always check the current multiplier before committing to a batch.
### Pricing Curve
The base curve below is set by Hedera Council and defines how the fee
multiplier scales with throughput in the high-volume lane. The multiplier is
expressed relative to the standard base fee, and the throughput rate is
expressed as a multiplier of the base entity creation rate.
| **High-Volume Throughput (× base create rate)** | **Fee Multiplier (× base price)** |
| ----------------------------------------------- | --------------------------------- |
| 1 | 4× |
| 1.5 | 8× |
| 2.5 | 10× |
| 3.5 | 15× |
| 5 | 20× |
| 7.5 | 30× |
| 10 | 40× |
| 25 | 60× |
| 50 | 80× |
| 100 | 100× |
| 250 | 150× |
| 500 | 200× |
| 5,000 | 200× |
Between breakpoints, the multiplier is linearly interpolated. The curve
assumes the standard (low-throughput) lane is running at its maximum rate
concurrently.
At the base create rate (1×), high-volume transactions already cost **4× the
standard fee**. At 100× throughput, the multiplier reaches **100×**. The curve
caps at **200×** regardless of how much higher throughput goes. Always set
`maxTransactionFee` to protect against unexpected costs.
This curve may be updated by Hedera governance. Use the Mirror Node fee
estimation endpoint to see the **current** multiplier in effect.
***
## Supported Transaction Types
The `high_volume` flag is supported on the following entity creation transactions:
| **Transaction Type** | **SDK Class** | **Notes** |
| ------------------------- | ------------------------------------ | --------------------------------- |
| `ConsensusCreateTopic` | `TopicCreateTransaction` | |
| `ContractCreate` | `ContractCreateTransaction` | HAPI only, not EVM |
| `CryptoApproveAllowance` | `AccountAllowanceApproveTransaction` | |
| `CryptoCreate` | `AccountCreateTransaction` | |
| `CryptoTransfer` | `TransferTransaction` | Only the account-creation portion |
| `FileAppend` | `FileAppendTransaction` | |
| `FileCreate` | `FileCreateTransaction` | |
| `HookStore` | — | Future transaction type |
| `ScheduleCreate` | `ScheduleCreateTransaction` | |
| `TokenAirdrop` | `TokenAirdropTransaction` | |
| `TokenAssociateToAccount` | `TokenAssociateTransaction` | |
| `TokenClaimAirdrop` | `TokenClaimAirdropTransaction` | |
| `TokenCreate` | `TokenCreateTransaction` | |
| `TokenMint` | `TokenMintTransaction` | Fungible and NFT |
The official Hedera SDKs expose `setHighVolume()` only on the transaction types
listed above. If you are using a custom SDK or constructing protobuf transactions
directly and set `high_volume = true` on a transaction type not in this list, the
transaction **will not fail**. The flag is silently ignored and the transaction
processes through the standard throttle system at standard pricing.
#### **EVM transactions are excluded**
Contract creations via `CREATE` / `CREATE2` opcodes
in the EVM do not participate in the high-volume system. A subsequent HIP will address
EVM-specific high-volume behavior.
***
## High-Volume Throttle Capacity
The high-volume system uses a two-level throttle structure. The HIP provides the
following example configuration (actual mainnet values will be set by governance):
| Throttle Bucket | Transaction Types | Example Capacity |
| ------------------------- | --------------------------------- | ---------------- |
| HighVolumeCryptoThrottles | `CryptoCreate` + `ScheduleCreate` | 10,500 ops/sec |
| HighVolumeTotalThrottles | All 14 supported types combined | 31,500 ops/sec |
These values are from the HIP's example throttle configuration. Final mainnet and
testnet capacity values will be set by Hedera governance and may differ.
A transaction must pass **both** its per-type bucket and the total bucket to be accepted.
If either is exhausted, the transaction receives a `BUSY` response.
For comparison, the current standard `AccountCreateTransaction` throttle is **2 tps** on
mainnet. The high-volume system offers orders-of-magnitude more capacity for applications
willing to pay the variable-rate fees.
***
## How to Use High-Volume Mode
Before submitting high-volume transactions, price the **exact** transaction you intend
to submit against the Mirror Node fee estimation endpoint. The `GET /api/v1/network/fees`
endpoint returns generic per-type averages and does **not** include a
`high_volume_multiplier`. To get an accurate high-volume estimate, build and freeze your
transaction, then POST the serialized protobuf bytes to `/api/v1/network/fees`.
The SDK's `toBytes()` returns a `TransactionList` wrapper; the mirror node fee endpoint
expects a single `Transaction` protobuf, so unwrap the list before sending (shown as
`extractFirstTransaction` below).
```javascript Account creation wrap theme={null}
const tx = await new AccountCreateTransaction()
.setKey(publicKey)
.setInitialBalance(Hbar.from(10))
.setHighVolume(true)
.setMaxTransactionFee(new Hbar(100))
.freezeWith(client);
const txBytes = extractFirstTransaction(tx.toBytes());
const response = await fetch(`${MIRROR_NODE_URL}/api/v1/network/fees`, {
method: "POST",
headers: { "Content-Type": "application/x-protobuf" },
body: txBytes,
});
const estimate = await response.json();
const multiplier = estimate.high_volume_multiplier / 1000;
const highVolumeTotal = estimate.total * multiplier;
```
```javascript Token creation wrap theme={null}
const tx = await new TokenCreateTransaction()
.setTokenName("Fee Check Token")
.setTokenSymbol("FEE")
.setTokenType(TokenType.FungibleCommon)
.setTreasuryAccountId(treasuryAccountId)
.setInitialSupply(0)
.setHighVolume(true)
.setMaxTransactionFee(new Hbar(100))
.freezeWith(client);
const txBytes = extractFirstTransaction(tx.toBytes());
const response = await fetch(`${MIRROR_NODE_URL}/api/v1/network/fees`, {
method: "POST",
headers: { "Content-Type": "application/x-protobuf" },
body: txBytes,
});
const estimate = await response.json();
const multiplier = estimate.high_volume_multiplier / 1000;
const highVolumeTotal = estimate.total * multiplier;
```
The response includes a `high_volume_multiplier` field scaled by 1000
(e.g., `1000` = 1.0×, `2000` = 2.0×). The `total` field is **not** pre-multiplied —
multiply `total` by `high_volume_multiplier / 1000` to get the high-volume price in
tinycents.
Mirror node data is near real-time but brief lag is possible between consensus and
ingestion. Use this value to gauge current pricing. The multiplier applied when your
transaction is processed may differ; expect the actual fee to vary.
```javascript Account creation wrap theme={null}
const tx = new AccountCreateTransaction()
.setKey(publicKey)
.setInitialBalance(Hbar.from(10))
.setHighVolume(true)
.setMaxTransactionFee(new Hbar(5));
const response = await tx.execute(client);
const receipt = await response.getReceipt(client);
const newAccountId = receipt.accountId;
console.log("Account created: " + newAccountId);
```
```javascript Token creation wrap theme={null}
const tx = new TokenCreateTransaction()
.setTokenName("My Token")
.setTokenSymbol("MTK")
.setTokenType(TokenType.FungibleCommon)
.setTreasuryAccountId(treasuryAccountId)
.setInitialSupply(0)
.setHighVolume(true)
.setMaxTransactionFee(new Hbar(5));
const response = await tx.execute(client);
const receipt = await response.getReceipt(client);
const newTokenId = receipt.tokenId;
console.log("Token created: " + newTokenId);
```
If the current multiplier pushes the fee above your `maxTransactionFee`, the transaction
will fail with `INSUFFICIENT_TX_FEE`. Your application should:
1. Catch the error
2. Re-query the current multiplier
3. Decide whether to retry with a higher cap, wait for utilization to drop, or fall
back to the standard throttle system
***
## Best Practices
**Always set `maxTransactionFee`.** This is not optional guidance — it is the only
mechanism protecting you from unexpectedly high fees during utilization spikes.
**Check the multiplier before batches.** If you are about to submit 10,000 account
creations, query the fee estimation endpoint first. A multiplier of 5× on 10,000
transactions is the difference between $500 and $2,500.
**Implement adaptive fee caps.** For long-running batch jobs, periodically re-check
the multiplier and adjust your `maxTransactionFee` up or down. If the multiplier rises
above your acceptable threshold, pause and retry later.
**Consider time-of-day patterns.** Like any shared resource, high-volume utilization
may follow patterns. Off-peak hours may offer lower multipliers.
***
## Verifying High-Volume Transactions
After a high-volume transaction reaches consensus, you can confirm its status via the
Mirror Node REST API:
```bash wrap theme={null}
curl "https://mainnet.mirrornode.hedera.com/api/v1/transactions/{transactionId}"
```
The response includes:
* `high_volume`: `true` — confirms the transaction used the high-volume system
* `charged_tx_fee` — the actual fee charged (reflecting the variable-rate multiplier)
The `TransactionResult` protobuf (exposed via block streams) includes a
`high_volume_pricing_multiplier` field (`uint64`, field number 13) showing the exact
multiplier that was applied. This value is divided by 1000 to get the actual multiplier.
***
## What High-Volume Mode Does NOT Do
**No priority.** High-volume transactions are processed in the same consensus order as
all other transactions. Paying more does not make your transaction execute sooner.
**No guaranteed throughput.** The high-volume throttle provides additional *capacity*,
but if that capacity is fully utilized by other users, your transaction will receive a
`BUSY` response just like the standard system.
**No impact on standard users.** Applications that do not use the `high_volume` flag
are completely unaffected — same throttle limits, same fixed-fee pricing.
***
## Related Resources
Full technical specification including pricing curve protobuf definitions
Standard fee schedule for all transaction types
Simplified `base-fee-plus-extras` pricing model
Current standard and high-volume throttle limits
SDK reference for `setHighVolume()` and other TransactionBody fields
SDK reference for FeeEstimateQuery and fee estimation workflows
# Keys and Signatures
Source: https://docs.hedera.com/learn/core-concepts/keys
Understand ECDSA secp256k1 and Ed25519 keys on Hedera: when to use each, public and private key roles, signature verification, and EVM compatibility.
## Key Types: ECDSA vs Ed25519
A key can be a [public key](/support/glossary#public-key) of a supported type — [ECDSA secp256k1](/support/glossary#ecdsa-secp256k1) or [Ed25519](/support/glossary#ed25519) — or an ID of a [smart contract](/support/glossary#smart-contract). The corresponding algorithm generates a public and private key pair that are unique to one another. The public key can be shared and is visible to other network users in a [Network Explorer](/support/glossary#network-explorer) or [REST APIs](/support/glossary#rest-api). The [private key](/support/glossary#private-key) is kept secret by the owner and grants access to modify entities (accounts, tokens, etc.).
Private keys *can only* be recovered once lost if created with an associated recovery phrase that you can access. Keys are mutable and can be updated once set for an entity. Generally, you will need the current key to sign the transaction to update the keys.
### ECDSA secp256k1 (Recommended)
**ECDSA secp256k1 is the recommended key type for all new accounts and applications on Hedera.** It is the same cryptographic curve used by Ethereum and the broader EVM ecosystem, which means:
* ECDSA accounts should set an **EVM Address from Public Key**, an EVM address derived from the ECDSA public key. It is the rightmost 20 bytes of the 32-byte Keccak-256 hash of the ECDSA public key, calculated in the manner described by the Ethereum Yellow Paper. Note that the recovery ID is not formally part of the public key and is not included in the hash. The EVM address is also commonly known as the public address. On mainnet, the corresponding account ID is `0.0.`.
* The EVM Address from Public Key enables full compatibility with Solidity smart contracts, `msg.sender` checks, and EVM-based tooling such as MetaMask, Hardhat, and Foundry.
* ECDSA keys work seamlessly with wallets and dApps built for EVM-compatible networks.
For maximum compatibility with Solidity and smart contracts, set the EVM Address from Public Key when creating an account by using `setECDSAKeyWithAlias()`. This ensures the account's EVM address is derived from its ECDSA public key, so `msg.sender` in a contract resolves correctly to the account's EVM address.
```javascript theme={null}
// Generate a new ECDSA key for the account
const accountPrivateKey = PrivateKey.generateECDSA();
const accountPublicKey = accountPrivateKey.publicKey;
const txCreateAccount = new AccountCreateTransaction()
.setECDSAKeyWithAlias(accountPublicKey);
```
**Key rotation trade-off:** The EVM Address from Public Key is permanently bound to the original ECDSA public key and does not change when the key is rotated. If your operational model requires key rotation, use `setKeyWithoutAlias()` at creation, and the account will fall back to its EVM Address from Account ID (long-zero form). See the [Create an Account](/native/accounts/create) reference for details.
### Ed25519 (Supported)
Ed25519 is a supported key type on Hedera. It does **not** produce an EVM Address from Public Key, so Ed25519 accounts cannot use EVM tooling, MetaMask, or Solidity's native `ECRECOVER` function directly. However, Ed25519 accounts can interact with smart contracts through the [HIP-632 system contract functions](/evm/hedera-services/system-contracts/account-service) (`isAuthorized` and `isAuthorizedRaw`), which enable on-chain verification of Ed25519 signatures.
Ed25519 is appropriate for HCS-based applications, Hedera-native workflows, and scenarios where EVM compatibility is not required
### Comparison
| | **ECDSA secp256k1** | **Ed25519** |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Recommendation** | ✅ Recommended for all new accounts and applications | Supported, not recommended for new development |
| **EVM Address (alias)** | Yes. Set at account creation using `setECDSAKeyWithAlias()`, derived from the Keccak-256 hash of the ECDSA public key | No. Ed25519 accounts have no EVM address |
| **Smart Contract Compatibility** | Full. Works with `msg.sender`, `ECRECOVER`, Solidity, and EVM tooling | Limited. Requires [HIP-632 system contract functions](/evm/hedera-services/system-contracts/account-service) (`isAuthorized`/`isAuthorizedRaw`) for on-chain signature verification. Not compatible with `ECRECOVER` or standard EVM tooling |
| **Wallet Support** | MetaMask, HashPack, and all EVM-compatible wallets | HashPack and Hedera-native wallets only |
| **Use Cases** | All use cases, especially dApps, DeFi, smart contracts, and EVM-compatible integrations | HCS-based applications, Hedera-native workflows, and scenarios where EVM compatibility is not required |
**Note**: Hedera wallets such as [HashPack](https://www.hashpack.app/) support both key types.
## Key Structures
Hedera supports the following key structure types:
Description
Example
Simple Key
A single key on an account.
AccountKey \{ Key 1 } Only one key is required to sign for the account.
Key List
All keys in the key list are required to sign transactions involving the account.
Account Key KeyList (3/3) \{ Key 1 Key 2 Key 3 } All three keys in the list are required to sign for the account.
Threshold Key
A subset of keys defined as the threshold are required to sign the transaction that involve the account out of the total number of keys.
Account Key ThresholdKey (1/3) \{ Key 1 Key 2 Key 3 } One out of the three keys in the key list is required to sign for the account.
🔔 Key structures can be nested. This means you can have a more complex key system with key lists inside of threshold keys, threshold keys inside keys lists, etc. An example of a nested key list can be viewed [here](https://hashscan.io/mainnet/adminKey/0.0.2).
All transaction types support the above key structures that specify a key field. For a transaction to be successful, the provided signatures must match the defined key structure requirements.
## FAQ
A key in Hedera can be a [public key](/support/glossary#public-key) of a supported type — [ECDSA secp256k1](/support/glossary#ecdsa-secp256k1) (recommended) or [ED25519](/support/glossary#ed25519) — or an ID of a [smart contract](/support/glossary#smart-contract). The corresponding algorithm generates public and private keys which are unique to one another. The public key can be shared and visible to other network users in a [Network Explorer](/support/glossary#network-explorer) or REST APIs. The [private key](/support/glossary#private-key) is kept secret and grants access to the owner to modify entities (accounts, tokens, etc.).
Use **ECDSA secp256k1** for all new accounts and applications. ECDSA keys are compatible with Ethereum and the EVM ecosystem, can be assigned an EVM Address from Public Key at account creation using `setECDSAKeyWithAlias()`, and work with tools like MetaMask, Hardhat, and Solidity smart contracts. Ed25519 is supported but does not provide an EVM address and is not compatible with EVM tooling. Ed25519 accounts can still interact with smart contracts through [HIP-632 system contract functions](/evm/hedera-services/system-contracts/account-service).
The EVM Address from Public Key is the rightmost 20 bytes of the 32-byte Keccak-256 hash of the ECDSA public key of the account, calculated in the manner described by the Ethereum Yellow Paper. The recovery ID is not formally part of the public key and is not included in the hash. To set it, use `setECDSAKeyWithAlias()` when creating the account. This address is what Solidity contracts see as `msg.sender`, and it enables full compatibility with smart contracts and EVM-based tooling. See [ECDSA secp256k1 (Recommended)](/learn/core-concepts/keys#ecdsa-secp256k1-recommended) above for the full derivation details.
Private keys can only be recovered once lost if created with an associated recovery phrase that you can access. It's crucial to keep your private keys safe and secure as they grant access to modify your Hedera entities, like accounts and tokens.
# Mirror Nodes
Source: https://docs.hedera.com/learn/core-concepts/mirror-nodes
Store history and cost-effectively query data
Mirror nodes provide a way to store and cost-effectively query historical data from the public ledger while minimizing the use of Hedera network resources. Mirror nodes support the Hedera network services currently available and can be used to retrieve the following information:
* Transactions and records
* Event files
## Understanding Mirror Nodes
Hedera Mirror Nodes receive information from Hedera network consensus nodes, either mainnet or testnet, and provide a more effective means to perform:
* Queries
* Analytics
* Audit support
* Monitoring
While mirror nodes receive information from the consensus nodes, they do not contribute to consensus themselves. The trust of Hedera is derived based on the consensus reached by the consensus nodes. That trust is transferred to the mirror nodes using signatures, chain of hashes, and state proofs.
To make the initial deployments easier, mirror nodes historically provided periodic files containing processed information (such as account balances or transaction records). However, starting from [Hedera release v0.42.0](https://github.com/hashgraph/hedera-services/releases/tag/v0.42.0), the generation and availability of account balance files by consensus nodes have been discontinued due to scalability challenges (see [HIP-794](https://hips.hedera.com/hip/hip-794)). Users needing balance information are now required to generate it from record files processed by mirror nodes.
The mirror node software reduces the processing burden by receiving pre-constructed files from the network, validating them, populating a database, and providing REST APIs.
Mirror nodes work by validating the signature files associated with record and event files (previously also balance files) from the consensus nodes that were uploaded to a cloud storage solution from the network.
As transactions reach consensus on the Hedera network, either mainnet or testnet, Hedera consensus nodes add the transaction and its associated records to a record file. A record file contains a series of ordered transactions and their associated records. After a given amount of time, a record file is closed and a new one is created. This process repeats as the network continues to receive transactions.
Once a record file is closed, the consensus nodes generate a signature file. The signature file contains a signature for the corresponding record file’s hash. Along with the signature file of the consensus node, the record file also contains the hash of the previous record file. The former record file can now be verified by matching the hash of the previous record file.
Hedera consensus nodes push new record files and signature files to the cloud storage provider – currently AWS S3 and Google File Storage are supported. Mirror nodes download these files, verify their signatures based on their hashes, and only then make them available to be processed.
## Understanding Block Streams After [`HIP-1259`](https://hips.hedera.com/hip/hip-1259)
For Mirror Node and [block stream](/support/glossary#block-stream) consumers, [HIP-1259](https://hips.hedera.com/hip/hip-1259) significantly simplifies the data related to transaction fees, leading to smaller block stream files and reduced data ingestion costs.
### Simplified Fee Structure
The primary change is the consolidation of all transaction fees into a single transfer to the **Fee Collection Account (`0.0.802`)**.
* **Before HIP-1259:** Each transaction record contained multiple fee-related transfers, increasing the size and complexity of the block stream.
* **After HIP-1259:** Each transaction record now contains only a single, clear fee transfer to `0.0.802`. This reduces the amount of data per transaction, making block streams more efficient.
### The Daily Synthetic Transaction
A new pattern to be aware of is the **daily synthetic distribution transaction**. This is a network-generated transaction that occurs once per day at the end of each staking period. It is responsible for distributing the fees accumulated in `0.0.802`.
As a block stream consumer, you can identify this transaction by the following characteristics:
* It will contain a single large debit from account `0.0.802`.
* It will have a long list of credit transfers to node operator accounts, the staking rewards account (`0.0.800`), the node rewards account (`0.0.801`), and the network treasury (`0.0.98`).
* It appears in the block stream like any other transaction.
This synthetic transaction is the **only** time you will see funds being transferred out of the [Fee Collection Account](/support/glossary#fee-collection-account). All other transactions involving [`0.0.802`](https://hashscan.io/mainnet/account/0.0.802) will be credit transfers into the account.
### Benefits for Mirror Node Operators
* **Reduced Storage Costs:** Smaller block stream files mean lower storage requirements for running a mirror node.
* **Faster Data Ingestion:** Simpler transaction records can be processed more quickly, improving the speed of data ingestion and synchronization.
* **Easier Data Analysis:** The fee structure is more straightforward, making it easier to analyze transaction costs and network revenue.
### Smart Contract Synthetic Logs
Starting with [v0.79](/networks/release-notes/mirror-node#v0.79) of Hedera Mirror Node release, synthetic event logs for Hedera Token Service (HTS) token transactions have been introduced to mimic the behavior of smart contract tokens. Synthetic events are generated for transactions such as:
* `CryptoTransfer`
* `CryptoApproveAllowance`
* `CryptoDeleteAllowance`
* `TokenMint`
* `TokenWipe`
* `TokenBurn`
This feature enables developers to effectively monitor HTS token activities as if they were smart contract tokens. An example code implementation demonstrating using ethers.js to listen to synthetic events can be found [here](https://github.com/ed-marquez/hedera-example-hts-synthetic-events-sdk-ethers).
### REST API from Hedera
Hedera provides REST APIs to easily query a mirror node that is hosted by Hedera, removing the complexity of having to run your own. Check out the mirror node REST API docs below.
### Run a Mirror Node
Anyone can run a Hedera Mirror Node by downloading and configuring the software on their computer. By running a mirror node, you are able to connect to the appropriate cloud storage and store account balance files, record files, and event files as described above. Please check out the below links on how to get started.
## FAQ
Hedera Mirror Nodes use [PostgreSQL](/support/glossary#postgresql) databases to store the transaction and event data organized in a structure that mirrors the Hedera Network. Once the mirror node receives record files from Hedera Consensus nodes, the data is validated and loaded into the database.
Setting up a Hedera Mirror Node involves both hardware and software components. The requirements can be found [here](/operators/mirror-node/run-your-own).
To run your mirror node, follow the steps in the "[Run Your Own Mirror Node](/operators/mirror-node/run-your-own)" guide.
No, Hedera does not charge for running a mirror node. However, there are costs associated with purchasing the hardware, internet connection, and potential cloud service fees. The hardware and software requirements can be found [here](/operators/mirror-node/run-your-own).
You can configure your own Hedera Mirror Node by following the step-by-step instructions provided in the "[How to Configure a Mirror Node and Query Data](/native/tutorials/advanced/configure-mirror-node)" guide. The guide provides instructions on prerequisites, node setup, configuration, and querying the node. Additionally, you can find more details about retention and transaction and entity filtering in the guide.
To provide feedback or log errors, please refer to the [Contributing Guide](/support/contributing) and submit an issue in the Hedera Docs [GitHub repository](https://github.com/hashgraph/hedera-json-rpc-relay/issues).
# Smart Contracts
Source: https://docs.hedera.com/learn/core-concepts/services/smart-contracts
# Staking
Source: https://docs.hedera.com/learn/core-concepts/staking/index
The Hedera public ledger uses a [proof-of-stake](/support/glossary#proof-of-stake-pos) consensus mechanism, in which each node’s influence on consensus is proportional to the amount of cryptocurrency it has staked. A transaction is validated and placed into consensus after it is processed by nodes representing an aggregate stake of over two-thirds of the total amount of HBAR currently staked and dedicated to securing the network. Stake is expressed as an amount in HBAR. It is important to ensure that most of the cryptocurrency is actually being staked, so that the network continues to run. This information can be referenced from the latest Hedera [whitepaper](https://hedera.com/hh_whitepaper_v2.1-20200815.pdf).
## FAQ
Staking is the process of participating in a [proof-of-stake](/support/glossary#proof-of-stake-pos) system to validate transactions and earn rewards. When staked, coins are locked but can be unlocked for trading. Staking allows participants (stakeholders) to earn rewards on their holdings, typically in tokens or coins.
No, there is no lock-up period when accounts are staked to a node. The staked account balance is liquid at all times.
The staking reward rate is determined by the Hedera Council and updated on the mainnet. Learn more about staking rewards [here](/learn/core-concepts/staking/staking).
[Staking rewards distribution](/learn/core-concepts/staking/stake-hbar#staking-reward-distribution) can be triggered by several different mechanisms, such as when an account is staked to a different node, when the total number of HBAR staked to an account changes, or when the staked account is auto-renewed.
Staking rewards do not expire but can only be collected for up to 365 days without a rewards payment being triggered. If more than 365 days pass without a rewards payment, rewards can only be collected for the latest 365 days periods.
# Stake HBAR
Source: https://docs.hedera.com/learn/core-concepts/staking/stake-hbar
### Get Started with Staking
The Hedera Council (via the Coin Committee) votes on the maximum reward rate. The maximum reward can change over time and is not a fixed value. For the latest reward rate, check out the "[Nodes](https://hashscan.io/mainnet/nodes)" page in HashScan. The actual reward rate will vary depending on how many HBAR are staked for rewards, but the rate will not exceed the cap. In the future, when nodes are down or inactive the staked account will not be eligible to earn rewards.
To view network nodes, their current stake, and reward rate, please visit [HashScan.io](https://hashscan.io/mainnet/nodes).
**Supported Wallets:**
**Exchanges:**
**Custodians:**
### **Staking Reward Distribution**
Rewards distributions can be triggered by one of the following mechanisms:
* When your staked account has HBAR transferred to it or debited from it
* When you update the staked account to stake to a different node
* When you update the staked account to decline rewards
* When the total number of HBAR staked to an account changes
* When the staked account is auto-renewed (auto-renew for accounts is not enabled at this time)
* When an account staked to this one has its account balance change
* You can continue to collect rewards earned for up to 365 days without a rewards payment being triggered
* If you go more than 365 days without a rewards payment, you can only collect on the last 365 days
* Example: Staker stakes for 1000 days, never collecting a reward, and on the 1001st day collect your rewards
* You will only get rewards for the latest 365 periods
* You will not earn rewards for the preceding 635 periods (1,000 days - 365 days)
**✅ For complete staking program details, check out the** [**Staking Program**](/learn/core-concepts/staking/staking) **page.**
# Staking Program
Source: https://docs.hedera.com/learn/core-concepts/staking/staking
The Hedera staking program allows you to earn rewards by staking your HBAR to a network node. Staking helps secure the network by contributing to the node's consensus weight (voting power).
## Staking Nodes
All consensus nodes run by the Hedera Council distribute rewards to the accounts staked to them. You can find information about each node by visiting a Hedera network explorer or by querying the [address book API](/reference/rest-api#api-v1-network-nodes).
Nodes have a minimum stake and maximum stake, both configurable per node. The minimum stake is currently set to 0, meaning any amount of staked HBAR makes a node eligible for rewards. The maximum stake is a per-node value set by the network. Staked HBAR that exceeds it does not increase the proportion of rewards returned.
## Lockup Period
There is **no lock-up period** when you stake your account. Your account's entire balance is automatically staked to the selected node or account, and your HBAR remains liquid at all times. There is no "bonding" or "slashing."
## Staking Reward Account
The staking reward account [(`0.0.800`)](https://hashscan.io/mainnet/account/0.0.800) distributes rewards to eligible staked accounts. Its primary funding comes from the daily distribution from the [Fee Collection Account](/support/glossary#fee-collection-account) [(`0.0.802`)](https://hashscan.io/mainnet/account/0.0.802).
Anyone in the community can also contribute to the rewards pool by transferring HBAR into this account. This account has no keys; any HBAR transferred into it cannot be returned.
The account must meet a minimum balance before rewards can be distributed. Once this threshold is met, rewards will continue to be paid out as long as there is a balance in the account.
### Staking Rewards
To be eligible for rewards, your account must be staked for a minimum of **one full staking period (24 hours)**, which begins and ends at midnight UTC.
For a staked account to earn rewards, the following must be true:
* The staking reward account (`0.0.800`) must have met its initial threshold balance.
* The node the account is staked to must meet the minimum node stake threshold.
* The account must be staked for the entire 24-hour staking period.
### Node Removal and Pending Rewards
Nodes are occasionally removed from the network address book, most commonly when transitional nodes are replaced by permanent ones. When this happens, staked accounts stop accruing new rewards, and accounts will not be able to claim the pending rewards that accrued while staked to the node that was removed.
Rewards are paid out when your account balance changes (HBAR sent or received), when you update your staking settings, or when the account auto-renews. Rewards can also only be collected for up to 365 days back. If no payout has been triggered in over a year, earlier periods are dropped. Accounts with very little activity can accumulate pending rewards without realizing it, and if their node is removed, those rewards cannot be recovered.
Node removals are not always announced in advance, so redeem rewards periodically rather than waiting. If you find out your node has been removed, switch to an active node to start earning again.
A change to this behavior is being tracked in [hiero-consensus-node#25701](https://github.com/hiero-ledger/hiero-consensus-node/issues/25701). Details and timing are still being defined.
### Staking Reward Distribution
With the implementation of [HIP-1259](https://hips.hedera.com/hip/hip-1259), the mechanism for handling fees and distributing rewards has been streamlined to improve network efficiency and simplify transaction records.
Previously, transaction fees were immediately split and distributed to multiple accounts. The new system introduces the **[Fee Collection Account](/support/glossary#fee-collector-account) (`0.0.802`)**, a network-controlled account that consolidates all transaction fees.
**How it Works:**
1. **Collection**: When a transaction is processed, the entire fee is transferred in a single payment to the [Fee Collection Account](/support/glossary#fee-collection-account) (`0.0.802`).
2. **Distribution**: Once per day, at the end of each staking period, a single, large synthetic transaction distributes the accumulated fees from the `0.0.802` account to their appropriate destinations, including the Staking Rewards account (`0.0.800`).
#### **Key Takeaway**
This enhancement **does not change the amount you pay** for transactions. It only optimizes how the network processes the fees behind the scenes, resulting in a cleaner experience for users and a more efficient network for everyone.
### Indirect Staking
Hedera offers a unique feature: **indirect staking**. If account A stakes to account B, and account B stakes to a node, the stake from both A and B increases the node's consensus weight. However, the rewards for both accounts are paid to account B.
An account can also optionally decline to earn rewards, though its balance will still contribute to the node's stake.
**📣 If you're interested in checking out the wallets and exchanges supporting staking HBAR, head to the** [**Stake HBAR**](/learn/core-concepts/staking/stake-hbar) **page.**
# State and History
Source: https://docs.hedera.com/learn/core-concepts/state-and-history
### Understanding State Machines
A "[state machine](/support/glossary#state-machine)" represents a conceptual approach to how a program operates: it maintains a "state" and modifies this state in response to specific "transactions." In a "replicated state machine," the duty and accountability for managing this evolving state are distributed across several computers, offering fault tolerance.
Hedera enables a replicated state machine. Numerous nodes, even potentially opposing ones, can consistently maintain the state of a dataset. For example, the HBAR quantity across a group of accounts. As detailed earlier, transactions are submitted to the network, and subsequently, the [hashgraph](/support/glossary#hashgraph) algorithm assigns them a consensus timestamp and a position in the consensus sequence. Once all nodes reach an agreement on the transaction sequence, they sequentially apply them to the state. This procedure ensures each node's state copy remains consistent. Every node applies (for example, adjusts the payer & recipient balances for an HBAR payment) the transactions to the state following the mutually agreed sequence, thus preserving a uniform state with other nodes at any specific moment.
### State vs. History
The latest state (e.g., the HBAR balances of each account) and the history of the transactions that altered that state are two distinct data structures with different properties. State is mutable by definition, constantly changing as transactions are applied to it. In contrast, the history of transactions is generally considered immutable and irreversible. State and history present very different storage burdens. At the high throughput that Hedera can support, history will grow very quickly, increasing the burden of storing it. State will also grow as new accounts, files, and smart contracts are created, but at a slower pace.
### Roles of Distributed Technology (DLT) Node
There are three mostly independent functions that a [distributed ledger technology (DLT)](/support/glossary#distributed-ledger-technology-dlt) node can perform:
* Contribute to [consensus](/support/glossary#consensus)
* Persist history of transactions
* Persist state
As nodes have limited resources, it is generally the case that a node cannot optimally perform all roles – and choices must be made as to which functions to prioritize.
### Priorities of Hedera Mainnet Nodes
For Hedera Mainnet nodes, the priorities contribute to consensus and persists state. The hashgraph, which contains all the transactions that change the state, is constantly pruned after transactions are assigned a place in consensus order. Mainnet nodes can delete older portions of the hashgraph because the algorithm delivers finality – once a transaction has been assigned a timestamp, ordered, and then applied to the state, there is no chance of reversal. Consequently, there is no need to keep historical transactions around in case they might be necessary to apply them in a different order. To prevent such historical transactions from filling up the node’s storage, mainnet nodes delete historical transactions.
But there is value in the history being persisted, even if not by mainnet nodes. An auditor might want to determine the identities of the parties that sent HBAR to a given account or the times of those transfers, neither of which would be available from the state (e.g., the balances of the accounts) alone.
### Roles of Mirror Nodes
[Mirror nodes](/learn/core-concepts/mirror-nodes) in the Hedera architecture, in addition to maintaining state, can also store transaction history. A particular mirror can choose whether to store all history, no history, or possibly only a fraction of the history, perhaps only for particular transaction types, particular accounts, etc. In addition to the history, mirror nodes store information that allows them to prove that their history is correct, even for some kinds of partial histories. This prevents a malicious mirror node from lying about what it is storing. A [client](/support/glossary#client) seeking a transaction from the past would query an appropriate mirror for the record of that transaction. As the burden of storing history is borne by mirrors and not mainnet nodes, the latter can be optimized for the more fundamental role of consensus and state storage.
## FAQ
The state in the Hedera Network is the current status of all data, like the amount of HBAR in a set of accounts. It is maintained across multiple nodes in a consistent representation, providing fault tolerance. The state constantly changes as transactions are applied to it.
The history of transactions is maintained as a separate data structure from the state. It provides a record of transactions that have changed the state over time. It is usually envisaged as immutable and irreversible. Mirror nodes in the Hedera architecture store the transaction history, while mainnet nodes focus on consensus and state storage.
Mainnet nodes prioritize contributing to consensus and persisting state. They delete historical transactions after they are assigned a place in the consensus order. Mirror nodes, on the other hand, store the transaction history and maintain state, providing a record of past transactions for audit purposes.
# Token Airdrops
Source: https://docs.hedera.com/learn/core-concepts/tokens/airdrops
## Overview
Token airdrops are commonly used to distribute tokens to multiple accounts, often as a promotional or reward mechanism. Hedera introduced "frictionless" airdrops in HIP-904 that simplify token distribution by handling token associations automatically and allowing recipients to accept or reject tokens as they choose. The Hedera SDKs provide dedicated transaction types (e.g., `TokenAirdropTransaction`, `TokenClaimAirdropTransaction`, `TokenRejectTransaction`) to support these flows. Let's break down how exactly token airdrops work on Hedera below.
***
## Token Airdrop
The `TokenAirdropTransaction` allows senders to distribute tokens to multiple recipients in a single transaction, even if the receiving accounts haven't previously associated with the token. This transaction type handles different recipient states:
* **Direct Airdrop**: If the recipient has an available auto-association slot or has pre-associated with the token, the token is transferred immediately.
* **Pending Airdrop**: If the recipient lacks an available association slot, the network creates a pending airdrop instead of failing the transaction.
The sender pays all associated fees, including transfer costs, association fees (if auto-associating a new token), first auto-renewal period rent for any new token associations and airdrop-specific spam deterrent fee. By balancing seamless token distribution with user control, this transaction type is valuable for token issuers, marketers, and airdrop campaigns.
***
## Claim Airdrop
The `TokenClaimAirdropTransaction` enables recipients to claim pending airdrops that were created when a TokenAirdropTransaction couldn’t complete due to a lack of available association slots. This ensures that users have the final say in which tokens enter their accounts.
The claim transaction integrates token association and transfer to avoid additional steps. At the moment of claim, the sender's balance is verified—if insufficient, the claim doesn’t proceed. The transaction requires the recipient's signature, enhancing security. The recipient must actively claim the airdrop, ensuring that token acceptance is consent-based. Key features include streamlined processing and enhanced security through recipient consent
***
## Cancel Airdrop
The TokenCancelAirdropTransaction allows senders to manage unclaimed pending airdrops, enabling them to cancel token distributions that haven't been accepted. This feature is particularly useful when a mistake is made in the airdrop, the sender needs to reallocate tokens elsewhere, or the sender wants to reclaim tokens from inactive recipients. Although the sender incurs a small cancellation fee to prevent misuse, they can effectively remove unclaimed airdrops from the network state. Only the original sender can initiate this process, and airdrops that have already been claimed cannot be canceled
***
## **Reject Token**
The `TokenRejectFlow` streamlines the process of returning unwanted tokens to the token's treasury account and dissociating the account from the token, effectively preventing spam and unwanted tokens. This flow can only be initiated if the token is not frozen and the recipient's account is active (i.e., not paused) for that token. The process allows recipients to reject tokens without requiring treasury consent, and all custom fees and royalties are waived so that users aren’t penalized for rejecting a token.
The process begins when a recipient initiates a `TokenRejectTransaction` for a specific token. The network then verifies the transaction by checking the token's state and the account's eligibility. Once the request is approved, the recipient's balance for that token is updated to zero, and the tokens are transferred back to the treasury and dissociated from the account. This entire sequence is executed seamlessly with a single `execute()` call, ensuring an efficient and user-friendly experience.
***
## Token Associations
When transferring tokens on Hedera, recipients must first link (associate) it to a smart contract or account before any token transfers can occur. This is called **token association**. Without it, token transfers will fail. If a token isn’t pre-associated or lacks an auto-association slot, transfers cannot proceed.
You can associate with a token in the following ways:
* Using the Hedera SDK with a `TokenAssociationTransaction`
* Using the `associateToken()` and `associateTokens()` as described in HIP-206.
**Note:** `Token association` is for HTS tokens only.
### Auto-Associations and Fees
Hedera introduced frictionless airdrops through HIP-904, enabling automatic token associations for recipients who haven't pre-associated, within auto-association limits. Each account has a `maxAutoAssociations` property that specifies the maximum number of allowed auto-associations. The sender covers the `maxAutoAssociations` fee and the rent for the association's first auto-renewal period, on top of the usual transfer fees. This setup ensures recipients can receive tokens without prior association, streamlining the transfer process. The properties are as follows:
Property Value
Description
0
Automatic token associations or token airdrops are not allowed, and the account must be manually associated with a token. This also applies if the value is less than or equal to usedAutoAssociations.
-1
The number of automatic token associations an account can have is unlimited. -1 is the default value for new automatically-created accounts.
> 0
If the value is a positive number (number greater than 0), the number of automatic token associations an account can have is limited to that number.
This enhancement removes friction from token transfers, making it easier to onboard users and distribute tokens efficiently.
**Reference**: [HIP-904](https://hips.hedera.com/hip/hip-904)
***
## Examples
* [Java](https://github.com/hashgraph/hedera-sdk-java/blob/main/examples/src/main/java/com/hedera/hashgraph/sdk/examples/TokenAirdropExample.java)
* [JavaScript](https://github.com/hashgraph/hedera-sdk-js/blob/main/examples/token-airdrop-example.js)
* [Go](https://github.com/hiero-ledger/hiero-sdk-go/blob/main/examples/token_airdrop/main.go)
* [Java](https://github.com/hiero-ledger/hiero-sdk-java/blob/main/examples/src/main/java/com/hedera/hashgraph/sdk/examples/TokenRejectExample.java)
* [JavaScript](https://github.com/hiero-ledger/hiero-sdk-js/blob/main/examples/token-airdrop-example.js)
* [Go](https://github.com/hiero-ledger/hiero-sdk-go/blob/main/examples/token_airdrop/main.go)
[^1]:
# Token Creation
Source: https://docs.hedera.com/learn/core-concepts/tokens/creation
## Overview
Creating tokens with the Hedera Token Service (HTS) is a streamlined process designed to help developers issue both fungible and non-fungible tokens quickly and securely on the Hedera network. Whether you’re launching digital currencies, loyalty points, or unique digital collectibles, HTS offers a robust framework for token creation and management. During the token creation process, you can customize properties like the token name, symbol, supply details, and administrative keys. Once the transaction is processed, a unique token ID is generated, which serves as the reference for all future token operations.
***
## HTS Token Creation Flow
### 1. Define Token Properties
Before initiating the token creation process, clearly define the token’s properties. Depending on the type of token you're creating, the required properties may vary. See examples below.
**Key Properties:**
* **Token Details:**
* **Name & Symbol:** Human-readable identifiers.
* **Decimals:** Defines token divisibility.
* **Initial & Maximum Supply:** Determines the token economy.
* **Key Roles:**
* **Admin Key:** Governs administrative changes.
* **Supply Key:** Controls minting and burning operations.
* **Freeze, KYC, & Pause Keys:** Enhance security and regulatory compliance.
* **Treasury Account:**
* Holds the initial supply and manages distribution.
**Key Properties:**
* **Token Details:**
* **Name & Symbol:** As with FTs, but often without a decimals property.
* **Unique Metadata:**
* Each token holds unique identifiers (serial numbers) and metadata that distinguishes it from others.
* **Key Roles & Treasury:**
* Similar key roles apply, but NFT-specific operations may involve unique signing procedures for each minting event.
***➡** The full list of token properties can be found on the* [*Token Properties*](/learn/core-concepts/tokens/properties) *page.*
### 2. Create the Token Creation Transaction
Use the HTS API to create a token creation transaction that incorporates all the defined properties. Ensure that you:
* Specify the treasury account.
* Include the necessary administrative keys (such as the admin key).
* Confirm that the initiating account has sufficient funds (in tinybars) to cover the transaction fee.
### 3. Sign and Submit the Transaction
Use the required treasury or admin keys to sign the transaction securely before submitting the signed transaction to the Hedera network. The network processes the transaction and, upon successful execution, generates a unique token ID.
### 4. Confirm Token Creation and Get the Token ID
Once the network confirms the transaction, get the transaction receipt, which includes the new token ID generated from the previous step. Use queries such as the `TokenInfoQuery` to confirm the token’s properties and ensure that all configurations are correctly set.
***
## Muteable vs. Immutable Tokens
* **Mutable Tokens:**\
These tokens can be updated after creation (e.g., changes to supply or metadata) and require keys that are set during creation by designating keys (such as the admin key) that authorize changes.
* **Immutable Tokens:**\
Once created, these tokens cannot be changed. No administrative keys are assigned, ensuring that the token’s properties remain fixed by omitting or restricting the relevant keys (e.g., by not assigning an admin key) to prevent modifications.
***
## Transaction Signing Requirements
Token operations require specific keys to ensure that only authorized actions are executed:
Key
Description
Admin Key
Grants full control over token configuration, including updating properties. Used for governance and administrative tasks, such as freezing or pausing.
Supply Key
Allows minting and burning of tokens to adjust the total supply. Ensures control over token issuance and circulation.
Freeze Key
Enables freezing or unfreezing token transfers for specific accounts. Used to enforce restrictions during investigations or compliance checks.
KYC Key
Enforces KYC compliance, allowing only approved accounts to transact with tokens. Ensures regulatory compliance for sensitive use cases.
Wipe Key
Allows removal of tokens from specific accounts. Used for refunding or correcting token allocations.
Pause Key
Allows pausing or unpausing token transactions. Pausing a token prevents the token from participating in all transactions.
Fee Schedule Key
Allows adjustments to the token’s custom fee schedule, providing flexibility in managing transaction costs.
Metadata Key
The key which can update the metadata of an NFT. This key is used to sign and authorize the transaction to update the metadata of NFTs. This value can be null.
***
## Consensus Nodes vs. Mirror Nodes in the Hedera Stack
On the Hedera network, the consensus nodes and mirror nodes perform two distinct operations at various layers of the Hedera stack.
**Consensus nodes** are responsible for executing all state-changing transactions, including HTS token creation, minting, burning, and transfers. They validate and order transactions, ensuring finality and security. Once transactions are processed, they generate transaction receipts containing details like the token ID.
**Mirror nodes** provide a data layer for querying real-time and historical ledger information without impacting network performance. Developers can use them to fetch token details, track NFT ownership history, and view token transaction logs. By leveraging mirror nodes, developers can access detailed insights into their token’s history and state without requiring direct interaction with consensus nodes.
# Custom Fee Schedule
Source: https://docs.hedera.com/learn/core-concepts/tokens/custom-fees
## Overview
The Hedera Token Service (HTS) enables token issuers to set up to 10 automated, protocol-enforced custom fees per token. These fees apply automatically during token transfer transactions, facilitating revenue generation, royalty distribution, or incentive structures without the need for additional coding or smart contracts. This page will walk you through each token and how it operates and interacts with the network.
***
## Types of Custom Fees
HTS supports three types of custom fees, allowing token issuers to design flexible fee structures that align with various use cases and business models:
**Fixed Fee:** Paid by the *sender* of the fungible or non-fungible tokens. A fixed fee transfers a set amount to a fee collector account each time a token is transferred, independent of the transfer size. This fee can be collected in HBAR or another Hedera token but not in NFTs.
**Fractional Fee:** Take a specific portion of the transferred fungible tokens, with optional minimum and maximum limits. The token *receiver* (fee collector account) pays these fees by default. However, if [`net_of_transfers`](/reference/protobuf/token/customfees/fractionalfee) is set to true, the sender pays the fees and the receiver collects the full token transfer amount. If this field is set to false, the receiver pays for the token custom fees and gets the remaining token balance.
**Royalty Fee:** Paid by the *receiver* account that is exchanging the fungible value for the NFT. When the NFT sender does not receive any fungible value, the [fallback fee](/support/glossary#fallback-fee) is charged to the NFT receiver.
📝 **Example Breakdown:**
Fee Type
Who Pays?
Payment Type
Fixed Fee
Sender
HBAR or HTS FT
Fractional Fee
Sender (or recipient if configured)
HTS FT
Royalty Fee
NFT Buyer (if exchanged for FTs)
HBAR or HTS FT
Royalty Fallback
NFT Receiver (if no fungible value received)
Fixed fee in HBAR
#### **Note**
In addition to the custom token fee payment, the sender account must pay for the token transfer transaction fee in HBAR. The "[*Payment of Custom Fees & Transaction Fees in HBAR*](/native/tokens/custom-fees#payment-of-custom-fees-vs.-transaction-fees-in-hbar)*"* section below covers the distinction between custom fees and transaction fees.
### How They Work
### 1️⃣ Fixed Fee
A fixed fee is a predetermined amount collected by a designated fee collector each time a token transfer occurs. This fee is independent of the transfer volume and is always paid by the sender. It can be collected in HBAR or an HTS fungible token (FT), but NFTs cannot be used as a fee payment type. Fixed fees can be applied to both fungible and non-fungible tokens, ensuring consistent fee collection per transaction.
### 2️⃣ Fractional Fee
A fractional fee deducts a percentage of the transferred amount and credits it to a designated fee collector account. This fee is only applicable to HTS fungible tokens (FTs) and must be ≤1 while staying within the fractional range of a 64-bit signed integer. By default, the recipient covers the fee, meaning the deducted amount is taken from what they receive. However, if `net_of_transfers` is set to true, the sender pays the fee, ensuring the recipient gets the full intended amount.
In essence, a fractional fee deducts a percentage of the transferred amount, with configurable payer responsibility (sender or recipient) and optional minimum and maximum limits to enforce boundaries on the deducted amount.
#### Example
* A 1% fractional fee applies to token transfers, with a minimum of 1 token and a maximum of 50 tokens.
* A 1,000-token transfer incurs a 10-token fee (1% of 1000).
* A 20-token transfer incurs the minimum fee of 1 token since 1% of 20 is less than 1.
### 3️⃣ Royalty Fee (NFTs)
A royalty fee applies when an NFT is transferred in exchange for fungible tokens (e.g. when an NFT is sold). The receiver pays the royalty fee, which is deducted as a percentage of the fungible amount exchanged. If no fungible tokens are involved in the transfer (i.e., the NFT is gifted or moved without payment), a fixed [fallback fee](/support/glossary#fallback-fee) is instead charged to the NFT recipient.
Key Points:
* Royalty fees apply only to non-fungible tokens (NFTs).
* Fees are paid in HBAR or an HTS fungible token.
* If the transfer includes no fungible value, a fallback fee is imposed.
#### Example
An NFT with a 5% royalty fee on a 1,000 HBAR sale results in a 50 HBAR fee to the fee collector. If the NFT is transferred without a sale, a fixed fallback fee (e.g., 10 HBAR) is charged to the recipient.
#### 🔔 **Note**
Royalty fees function as a convenience feature, but the network cannot enforce royalties if users split the NFT exchange into separate transactions. To ensure proper application, the NFT sender and receiver must both sign a single `CryptoTransfer` transaction. There is an ongoing HIP discussion about expanding automatic royalty collection.
***
## Payment of Custom Fees vs. Transaction Fees in HBAR
Understanding the difference between custom fees and standard transaction fees in HBAR is crucial for token issuers and developers working with Hedera.
* **Custom fees** are designed to enforce complex fee structures, such as royalties and fractional ownership. These fees can be fixed, fractional, or royalty-based and are usually paid in the token being transferred, although other Hedera tokens or HBAR can also be used. You can configure up to 10 custom fees to be automatically disbursed to designated fee collector accounts.
* **Transaction fees** in HBAR serve a different purpose: they compensate the network for processing transactions. These fees follow a [base fee + extras model](/learn/core-concepts/fee-model) defined in the network's fee schedule (system file `0.0.113`). While the fee structure varies by transaction type (each has its own base fee and applicable extras), the fee schedule is set by the network's governing authority, not by individual users. Transaction fees are paid exclusively in HBAR.
### **Key Differences**
The key differences are that custom fees offer flexibility and can be paid in various tokens to any account, while transaction fees follow a network-defined schedule and go to the network and node operators, paid only in HBAR. The table below summarizes the key differences between custom fees and transaction fees.
Compensate network nodes for transaction processing
Who Collects?
Designated fee collector(s)
Hedera network nodes
Currency
HBAR or HTS fungible tokens
HBAR only
Configurability
Fully configurable by token issuer
Fixed by the network
***
### **Fee Exemptions**
Fee collector accounts can be exempt from paying custom fees. To enable this, you need to set the exemption during the creation of the custom fees ([HIP-573](https://hips.hedera.com/hip/hip-573)). If not enabled, custom fees will only be exempt for an account if that account is set as a fee collector.
### **Limits and Constraints**
When it comes to setting custom fees, there are a few limits and constraints to keep in mind:
* First, fees cannot be set to a negative value.
* Each token can have up to 10 different custom fees.
* Additionally, the treasury account for a given token is automatically exempt from paying these custom transaction fees.
* The system also permits, at most, two "levels" of custom fees. That means a token being transferred might require fees in another token that also has its own fee schedule; however, this can only be nested two layers deep to prevent excessive complexity.
***
## Additional Resources
* [**NFT Royalty Fees: Everything You Need To Know**](https://hedera.com/blog/nft-royalty-fees-hedera-hashgraph)
* [**Hedera Token Service: NFT Token Keys Edge Cases**](https://hedera.com/blog/hedera-token-service-nft-token-keys-edge-cases)
# Hedera Token Service (HTS) Native Tokenization
Source: https://docs.hedera.com/learn/core-concepts/tokens/hts-overview
## Overview
The Hedera Token Service (HTS) provides native support for fungible and non-fungible tokens (NFTs), allowing developers to create and manage tokens without deploying smart contracts. This simplifies tokenization, reduces development complexity, and enhances security. The native tokenization model is optimized for high-speed transactions, supporting up to 10,000 transactions per second (TPS) for token transfers, making it ideal for enterprise-scale applications that require rapid and secure token operations. HTS also provides additional features such as:
* **Built-in compliance controls** – KYC, pause, freeze, and account association
* **Atomic swaps** – Transfer multiple tokens and HBAR in a single transaction
* **Customizable fees** – Supports automatic royalties and revenue sharing
* **Direct API & SDK access** - No Solidity smart contracts required
HTS is accessible through Hedera’s native API (HAPI), which provides comprehensive token management capabilities, including account operations, token transactions, and consensus messaging. Developers can interact with HTS via the Hedera SDKs, enabling seamless token transfers, contract calls, and consensus integrations.
***
## **Built-in Compliance & Security Controls**
HTS ensures secure and compliant tokenization with built-in tools that eliminate the need for Solidity-based enforcement. Token issuers can configure their tokens using the following security features
Feature
Function
Benefit
KYC Enforcement
Restricts transfers to verified accounts
Ensures regulatory compliance and security
Freeze/Pause Functions
Suspends transactions when needed
Protects against fraud and suspicious activity
Custom Fees
Supports royalties & structured payments
Automates revenue collection and token monetization
Supply Control
Enables or disables minting or burning
Maintains controlled token supply
Account Association
Prevents spam token transfers
Enhances security and prevents unwanted token drops
These optional compliance and control mechanisms make HTS flexible to meet regulatory requirements while maintaining token security and flexibility.
***
## Token Management
Managing token operations on Hedera involves defining roles and performing key actions to control and manage the token lifecycle. Below are the primary roles and token management operations supported by HTS.
### Role-Based Access Control
HTS utilizes role-based access control (RBAC) to securely manage token lifecycle operations. Token issuers can assign distinct roles to delegate responsibilities, enhance security, and ensure compliance.
Role
Description
Admin Key
Grants full control over token configuration, including updating properties. Used for governance and administrative tasks, such as freezing or pausing.
Supply Key
Allows minting and burning of tokens to adjust the total supply. Ensures control over token issuance and circulation.
Freeze Key
Enables freezing or unfreezing token transfers for specific accounts. Used to enforce restrictions during investigations or compliance checks.
KYC Key
Enforces KYC compliance, allowing only approved accounts to transact with tokens. Ensures regulatory compliance for sensitive use cases.
Wipe Key
Allows removal of tokens from specific accounts. Used for refunding or correcting token allocations.
Pause Key
Allows pausing or unpausing token transactions. Pausing a token prevents the token from participating in all transactions.
Fee Schedule Key
Allows adjustments to the token’s custom fee schedule, providing flexibility in managing transaction costs.
Metadata Key
The key which can update the metadata of an NFT. This key is used to sign and authorize the transaction to update the metadata of NFTs. This value can be null.
### **Token Operations**
HTS supports various token operations to ensure secure and flexible management of tokenized assets.
Operation
Description
Mint
Increase the total supply of a token by creating new units.
Burn
Decrease the total supply of a token by destroying specific units.
Associate
Enables an account to hold and transact the token.
Dissociate
Remove a token association from an account to prevent further transactions.
Freeze/Unfreeze
Temporarily restrict or allow token transfers for specific accounts.
Wipe
Remove tokens from an account’s balance, effectively burning them.
Enable KYC
Restricts token holding or transfers to accounts meeting compliance requirements.
***
## **Use Cases for HTS**
HTS powers a wide range of applications across finance, gaming, enterprise, and digital assets. It enables high-speed, low-cost transfers for stablecoins, CBDCs (central bank digital currency), and remittances, making digital payments faster and more efficient. Its NFT marketplace solutions support efficient and affordable minting and trading of non-fungible tokens (NFTs). For enterprises and banking, HTS facilitates tokenized securities, corporate treasury operations, and interbank settlements. In gaming and the metaverse, it powers instant asset ownership transfers for in-game items, digital collectibles, and virtual economies.
# Tokenization on Hedera
Source: https://docs.hedera.com/learn/core-concepts/tokens/index
## Overview
Tokens on Hedera represent digital assets that users can create, manage, and transfer through the Hedera Token Service (HTS). HTS supports both fungible tokens (e.g., stablecoins, loyalty points) and non-fungible tokens (NFTs) for collectibles, real-world assets (RWAs), and more. Built on Hedera’s high-performance hashgraph, tokens benefit from low, predictable fees, built-in compliance features, and cross-chain interoperability, making them ideal for enterprise and web3 applications.
## Tokenization Models
Hedera supports three tokenization approaches, allowing developers to choose the best fit for their use case:
### Tokenization Quick Reference Summary
Feature
HTS Native Tokenization
ERC/EVM Tokenization
Hybrid Tokenization
Smart Contracts Required
No
Yes
Optional (HTS + EVM)
Cost Efficiency
Low fees, fixed-cost transactions
Higher gas fees
Lower fees than full EVM, utilizes HTS efficiencies
Compliance & Security
Built-in compliance tools (KYC, freeze, pause)
No built-in compliance, requires smart contract logic
HTS tokens act as ERC-20/ERC-721 (via HIP-218 & HIP-376)
Flexibility
Limited customization
Highly customizable with Solidity
Combines benefits of both models
Use Cases
Enterprise solutions, regulated assets, fast transactions
***
### 1. Hedera Token Service (HTS) - Native Tokenization
HTS provides a high-performance, native tokenization framework that operates directly on Hedera’s core consensus layer. Unlike smart contract-based tokenization, HTS offers faster transactions and reduced costs by eliminating the need for Solidity smart contracts.
**Best for: Stablecoins, loyalty programs, micropayments, and enterprise solutions.**
***
### 2. ERC/EVM Tokenization (EVM-Compatible Standards)
This model allows developers to deploy standard ERC-20, ERC-721, and ERC-1155 tokens using smart contracts within Hedera’s EVM implementation environment. This model is ideal for EVM-based projects migrating to Hedera or developers who prefer Solidity-based token logic.
**Best for DeFi protocols, NFT marketplaces, and EVM-native projects.**
***
### 3. Hybrid Tokenization – Combining HTS with EVM Smart Contracts
The hybrid model combines the speed and cost efficiency of HTS with the programmability of EVM smart contracts. Developers can perform basic token operations (minting, transfers, burning) using HTS while leveraging smart contracts for complex logic, such as governance, interoperability, and multi-signature transactions.
**Best for DeFi, security tokens, and asset tokenization requiring smart contract logic.**
#### **Key Features**
* **HTS handles core operations** – Minting, burning, and transfers at low cost
* **Smart contracts add flexibility** – Custom governance, multi-signature approvals
* **Balances cost & programmability** – Reduces gas fees while maintaining EVM flexibility
* **Interoperability-ready** – Works with existing EVM infrastructure
***
## Tokenomics & Fee Structure
Hedera's tokenization framework prioritizes low costs, predictability, and flexibility, making it ideal for developers and businesses issuing and managing digital assets at scale. The **fixed fee** model ensures that transaction fees remain low, stable, and predictable, as they are denominated in USD and paid in HBAR. The transaction fees are not impacted by network demand and congestion like other chains. This eliminates volatility and makes transaction costs easy to estimate.
#### Built-In Custom Fees
The Hedera Token Service (HTS) extends beyond standard transaction fees by offering a custom fee schedule that enables automated fee distribution for token transactions, revenue sharing, and royalty payments, all without requiring smart contracts. These fees are enforced programmatically at the token level, ensuring predictability, efficiency, and scalability. By integrating fixed network fees with customizable fee structures, Hedera provides a developer-friendly, cost-effective solution for managing digital assets at scale.
***
## Additional Resources
* [**HTS x EVM - Part 1: How to Mint NFTs \[Tutorial\]**](/evm/tutorials/hedera/hts-evm/part1-mint-nfts)
* [**HTS x EVM - Part 2: KYC & Update \[Tutorial\]**](/evm/tutorials/hedera/hts-evm/part2-kyc-update)
* [**HTS x EVM - Part 3: How to Pause, Freeze, Wipe, and Delete NFTs \[Tutorial\]**](/evm/tutorials/hedera/hts-evm/part3-pause-freeze-wipe)
# Token Properties
Source: https://docs.hedera.com/learn/core-concepts/tokens/properties
create a token
Token properties on Hedera define the characteristics, governance, and lifecycle of fungible and non-fungible tokens. These properties are set during token creation and, depending on configuration, can be updated to meet evolving requirements. By specifying token properties, developers gain control over supply, permissions, and other critical aspects of token behavior.
Property
Description
Name
Set the publicly visible name of the token. The token name is specified as a string of UTF-8 characters in Unicode. UTF-8 encoding of this Unicode cannot contain the 0 byte (NUL). The token name is not unique. Maximum of 100 characters.
Token Type
The type of token to create. Either fungible (FUNGIBLE\_COMMON) or non-fungible(NON\_FUNGIBLE\_UNIQUE).
Symbol
The publicly visible token symbol. Set the publicly visible name of the token. The token symbol is specified as a string of UTF-8 characters in Unicode. UTF-8 encoding of this Unicode cannot contain the 0 byte (NUL). The token symbol is not unique. Maximum of 100 characters.
Decimal
The number of decimal places a token is divisible by. This field can never be changed.
Initial Supply
Specifies the initial supply of fungible tokens to be put in circulation. The initial supply is sent to the Treasury Account. The maximum supply of tokens is 9,223,372,036,854,775,807(2^63-1) tokens and is in the lowest denomination possible. For creating an NFT, you must set the initial supply to 0.
Treasury Account
The account which will act as a treasury for the token. This account will receive the specified initial supply and any additional tokens that are minted. If tokens are burned, the supply will decreased from the treasury account.
Fee Schedule Key
The key which can change the token's custom fee schedule. It must sign a TokenFeeScheduleUpdate transaction. A custom fee schedule token without a fee schedule key is immutable.
Custom Fees
Custom fees to charge during a token transfer transaction that transfers units of this token. Custom fees can either be fixed, fractional, or royalty fees. You can set up to a maximum of 10 custom fees.
Max Supply
For tokens of type FUNGIBLE\_COMMON - the maximum number of tokens that can be in circulation. For tokens of type NON\_FUNGIBLE\_UNIQUE - the maximum number of NFTs (serial numbers) that can be minted. This field can never be changed. You must set the token supply type to FINITE if you set this field.
Supply Type
Specifies the token supply type. Defaults to INFINITE.
Freeze Default
The default Freeze status (frozen or unfrozen) of Hedera accounts relative to this token. If true, an account must be unfrozen before it can receive the token.
Expiration Time
The epoch second at which the token should expire; if an auto-renew account and period are specified, this is coerced to the current epoch second plus the autoRenewPeriod. The default expiration time is 7,890,000 seconds (90 days).
Auto Renew Account
An account which will be automatically charged to renew the token's expiration, at autoRenewPeriod interval. This key is required to sign the transaction if present. Currently, rent is not enforced for tokens so auto-renew payments will not be made.
Auto Renew Period
The interval at which the auto-renew account will be charged to extend the token's expiry. The default auto-renew period is 7,890,000 seconds. Currently, rent is not enforced for tokens so auto-renew payments will not be made.
NOTE: The minimum period of time is approximately 30 days (2592000 seconds) and the maximum period of time is approximately 92 days (8000001 seconds). Any other value outside of this range will return the following error: AUTORENEW\_DURATION\_NOT\_IN\_RANGE.
Memo
A short publicly visible memo about the token.
Metadata
The metadata of the token. The admin key or the metadata key can be used to update this property.
***
## Token Metadata Standard
Metadata enhances the utility and interoperability of tokens by providing additional context about their attributes. Token metadata provides valuable context for a token's characteristics, enhancing its usability, interoperability, and discoverability across different apps. This metadata is particularly important for NFTs (non-fungible tokens) but can also apply to fungible tokens in certain use cases.
#### Common Metadata Fields
* **Name**: The display name of the asset.
* **Description**: A detailed description of the asset.
* **Image URL**: A link to the asset's image or multimedia file.
* **Attributes**: Key-value pairs describing properties like rarity, edition, or category.
* **Royalties**: Details about royalty fees (e.g., a 5% royalty with a fallback fee).
#### Metadata Storage and Standards
Metadata is typically attached during the minting process as a base64-encoded JSON payload, allowing seamless integration with wallets, dApps, and marketplaces. Hedera follows established metadata standards to ensure consistency across platforms. [HIP-412](https://hips.hedera.com/hip/hip-412) defines a structured format for NFT metadata. See the example below.
```json theme={null}
{
"format": "HIP412@2.0.0",
"name": "Hello",
"creator": "HGraph Punks",
"description": "HGraph Punks are a collection of 8,192 randomly generated NFTs that exist on the Hedera network. HGraph Punks holders get access to The H digital bar experience, ability to vote on HGraph Punks decisions, exclusive community events and much more. For more information, visit www.hgraphpunks.com",
"image": "ipfs://bafybeibrnfa3dc43ukx6ypt4vb3uanbgvqe5jci7ubcmpgvau5shbmzujm/373.png",
"properties": {
"edition": {
"number": 28,
"set": 1,
"drop": 2,
"pack": 14
},
"supply": "8192",
"catalog": ["classic"],
"extras": [],
"compiler": "Turtle Moon Tools",
"category": "HGraph Punks"
},
"royalties": {
"numerator": 5,
"denominator": 100,
"fallbackFee": 100
},
"attributes": [
{
"trait_type": "background",
"value": "punkyPurple"
},
{
"trait_type": "skin",
"value": "hederaBlue"
},
{
"trait_type": "tattoos",
"value": "none"
},
{
"trait_type": "forehead-h",
"value": "turquoise"
},
{
"trait_type": "earrings",
"value": "stud_deepTurquoise"
},
{
"trait_type": "necklace",
"value": "none"
},
{
"trait_type": "eyes",
"value": "classicEyes_lightBrown"
},
{
"trait_type": "nose",
"value": "nose3"
},
{
"trait_type": "mouth",
"value": "tongueOut"
},
{
"trait_type": "hair",
"value": "braids_pink"
},
{
"trait_type": "extras",
"value": "none"
}
],
"files": [
{
"uri": "ipfs://bafybeibrnfa3dc43ukx6ypt4vb3uanbgvqe5jci7ubcmpgvau5shbmzujm",
"type": "image/png",
"is_default_file": true
}
],
"localization": {
"uri": "ipfs://QmWS1VAdMD353A6SDk9wNyvkT14kyCiZrNDYAad4w1tKqT/{locale}.json",
"default": "en",
"locales": ["es", "fr"]
}
}
```
By adhering to these standards, developers ensure that tokens created on Hedera can be easily indexed, retrieved, and utilized across various dApps and ecosystems.
***
## Additional Resources
* [**How to Structure Token Metadata Using HIP-412 Standard**](/native/tutorials/tokens/metadata-schema)
# Token Types and ID Formats
Source: https://docs.hedera.com/learn/core-concepts/tokens/types-and-ids
## Supported Token Types
The Hedera Token Service (HTS) supports multiple token types, enabling you to represent a wide range of digital assets—whether they’re identical units of value or unique items. This guide covers the supported token types and explains how tokens are identified on Hedera. The Hedera Token Service supports two primary token types:
### **Fungible Tokens**
Fungible tokens are identical, interchangeable, and share the same value and properties. They are best suited for assets where every unit is equal. This is particularly useful for representing divisible assets such as stablecoins, loyalty points, in-game currencies, or any currency-like asset with fractional ownership.
### **Non-Fungible Tokens**
Non-fungible tokens (NFTs) are unique and non-interchangeable, differentiated by a distinct serial number. They can represent various unique and indivisible assets that require individual distinction, such as digital art, music, collectibles, or identity and certifications.
***
## Token ID and Format
The **Token ID** uniquely identifies a token entity on the Hedera network. It consists of the shard number, realm number, and a token number, formatted as:
📌 `..`
This Token ID is used in all transactions and queries involving the token.
#### **Token ID Example**
📌 `0.0.100` → Identifies a **fungible token** with a token number `100`.
### Components of a Token ID
Component
Description
Default
Shard Number (shardNum)
Defines the shard (partition) where the token exists. Currently, Hedera operates in only one shard, so this value remains 0.
0
Realm Number (realmNum)
Identifies the realm within a shard. Today, Hedera has one realm, so this value is also 0.
0
Token Number (tokenNum)
A unique identifier assigned to each token when it is created.
Varies
(assigned at creation)
Serial Number (serialNum)
A unique identifier for individual NFTs within a collection. Not applicable to fungible tokens.
N/A
(NFTs only)
### Non-Fungible Token ID Format
For **non-fungible tokens (NFTs)**, an additional serial number is appended to the Token ID to distinguish each unique instance within a collection:
📌 `../`*``*
#### **NFT Token ID Example**
📌 `0.0.12345/1` → First NFT in collection `0.0.12345`
📌 `0.0.12345/2` → Second NFT in collection `0.0.12345`
This consistent ID format ensures that tokens are efficiently tracked and managed across the Hedera network.
# Transactions and Queries
Source: https://docs.hedera.com/learn/core-concepts/transactions/index
An overview of Hedera API transactions and queries
## Transactions
Transactions are requests sent by a client to a node with the expectation that they are submitted to the network for processing into consensus order and subsequent application to state. Each transaction (e.g. `TokenCreateTransaction()`) has an associated transaction fee compensating the Hedera network for processing and subsequent maintenance in a consensus state.
**Transaction ID**
Each transaction has a unique transaction ID. The transaction ID is used for the following:
* Obtaining receipts, records
* Internally by the network for detecting when duplicate transactions are submitted
The transaction ID is composed by using the transaction's valid start time and the account ID of the account that is paying for the transaction. The transaction's valid start time is the time the transaction begins to be processed on the network. The transaction's valid start time can be set to a future date/time. A transaction ID looks something like `0.0.9401@1598924675.82525000`where `0.0.9401` is the transaction fee payer account ID and `1598924675.82525000` is the timestamp in `seconds.nanoseconds`.
Transactions have a valid duration of up to 180 seconds and begin at the transaction's valid start time. This means that the transaction has up to 180 seconds to be accepted by one of the nodes in the network. If the transaction is not accepted in this timeframe, the transaction will expire. The transaction will have to be created, signed, and submitted again.
A **transaction** generally includes the following:
* **Node Account**: the account ID of the node the transaction is being sent to (e.g. `0.0.3`)
* **Transaction ID**: the identifier for a transaction. It has two components:
* The account ID of the paying account
* The transaction’s valid start time
* **Transaction Fee**: the maximum fee the transaction fee paying account is willing to pay for the transaction
* **Valid Duration**: the number of seconds that the client wishes the transaction to be deemed valid for, starting at the transaction's valid start time
* **Memo**: a string of text up to 100 bytes of data (optional)
* **Transaction**: type of request, for instance, an HBAR transfer or a smart contract call
* **Signatures**: at minimum, the paying account will sign the transaction as authorization. Other signatures may be present as well.
For a detailed breakdown of all transaction properties, please refer to the [Transaction Properties](/learn/core-concepts/transactions/properties) page.
The lifecycle of a transaction in the Hedera ecosystem begins when a client creates a transaction. Once the transaction is created it is cryptographically signed at a minimum by the account paying for the fees associated with the transaction. Additional signatures may be required depending on the properties set for the account, topic, or token. The client can stipulate the maximum fee it is willing to pay for the processing of the transaction and, for a smart contract operation, the maximum amount of gas. Once the required signatures are applied to the transaction the client then submits the transaction to any node on the Hedera network.
The receiving node validates (for instance, confirms the paying account has sufficient balance to pay the fee) the transaction and, if validation is successful, submits the transaction to the Hedera network for consensus by adding the transaction to an event and gossiping that event to another node. Quickly, that event flows out to all the other nodes. The network receives this transaction exponentially fast via the [gossip about gossip protocol](/learn/core-concepts/hashgraph/gossip-about-gossip). The consensus timestamp for an event (and so the transactions within) is calculated by each node independently calculating the median of the times that the network nodes received that event. You may find more information on how the consensus timestamp is calculated [here](https://docs.hedera.com/docs/hashgraph-overview#section-fair-timestamps). The hashgraph algorithm delivers the finality of consensus. Once assigned a consensus timestamp the transaction is then applied to the consensus state in the order determined by each transaction’s consensus timestamp. At that point, the transaction fees are also processed. In this manner, every node in the network maintains a consensus state because they all apply the same transactions in the same order. Each node also creates and temporarily stores receipts/records in support of the client, subsequently querying for the status of a transaction.
## Transaction Fees
Every transaction on the Hedera network has an associated fee to compensate the network for processing and state storage. With the implementation of **[HIP-1259](https://hips.hedera.com/hip/hip-1259)**, the mechanism for handling these fees has been significantly streamlined to improve network efficiency and simplify transaction records for users.
**Variable-rate pricing for high-volume transactions.** Entity creation transactions
(such as `CryptoCreate`, `TokenCreate`, and `TokenMint`) that set the `high_volume`
flag use a separate throttle pool with **variable-rate pricing** — fees scale with
current utilization of the high-volume capacity. See the
[High-Volume Entity Creation](/learn/core-concepts/high-volume-entity-creation)
guide for details on how this affects your costs.
### Fee Collection and Distribution ([HIP-1259](https://hips.hedera.com/hip/hip-1259))
Previously, transaction fees were immediately split and distributed to multiple accounts with every transaction. This immediate distribution created challenges. For a simple crypto transfer between two accounts, the system must read and update up to six accounts: the sender, receiver, submitting node, `0.0.98`, `0.0.800`, and `0.0.801`. This increases processing overhead and slows performance. In the [block stream](/support/glossary#block-stream), every transaction must record balance changes for all these accounts, inflating data size and storage costs. Users viewing transactions on explorers like HashScan see a complex web of transfers, which can be confusing even with visualizations.
The new system introduces the **Fee Collection Account (`0.0.802`)**, a network-controlled account that consolidates all transaction fees.
**How it Works:**
1. **Collection**: When a transaction is processed, the entire fee is transferred in a single payment to the Fee Collection Account (`0.0.802`).
2. **Distribution**: Once per day, at the end of each staking period, a single, large synthetic transaction distributes the accumulated fees from the `0.0.802` account to the appropriate destinations.
#### **Key Takeaway**
This enhancement **does not change the amount you pay** for transactions. It only optimizes how the network processes the fees behind the scenes, resulting in a cleaner experience for users and a more efficient network for everyone.
These destinations include:
* **Node Operator Rewards**: Payments to individual nodes for their services.
* **Staking Rewards (`0.0.800`)**: Funds allocated to accounts participating in staking.
* **Node Rewards (`0.0.801`)**: Rewards distributed to nodes.
* **Network Treasury (`0.0.98`)**: The account that receives network fees.
### Example 1: Fee Collection in Action (Crypto Transfer)
Let's look at a standard `CRYPTO TRANSFER` transaction to see how the fee is collected.
**Transaction Link:** [https://hashscan.io/mainnet/transaction/1771485699.125401461](https://hashscan.io/mainnet/transaction/1771485699.125401461)
In this transaction, account `0.0.10231006` sends a small amount of HBAR to `0.0.37`. The HBAR transfers clearly show the fee consolidation:
| Account | Amount | Description |
| :------------- | :--------------- | :-------------------------- |
| `0.0.10231006` | **-0.00102456ℏ** | Payer (Sent transfer + fee) |
| `0.0.37` | **+0.00000009ℏ** | Receiver (Node 34) |
| **`0.0.802`** | **+0.00102447ℏ** | **Fee Collection Account** |
### Example 2: Fee Collection Across All Transaction Types
The HIP-1259 fee collection mechanism applies to all transaction types, not just crypto transfers. Let's look at a `SUBMIT MESSAGE` transaction to see the same streamlined process.
**Transaction Link:** [https://hashscan.io/mainnet/transaction/1771486013.122401000](https://hashscan.io/mainnet/transaction/1771486013.122401000)
#### Transaction Details
* **ID:** `0.0.85243@1771485974.183471116`
* **Type:** `SUBMIT MESSAGE`
* **Fee:** `0.00902194ℏ`
#### HBAR Transfers Breakdown
This transaction submitted a message to the Hedera Consensus Service. Notice how simple the HBAR transfers are:
| Account | Amount | Description |
| :------------ | :--------------- | :------------------------------- |
| `0.0.85243` | **-0.00902194ℏ** | Payer (Paid the transaction fee) |
| **`0.0.802`** | **+0.00902194ℏ** | **Fee Collection Account** |
That's it. The entire fee is cleanly transferred to account `0.0.802`. There are no other transfers related to the fee in this transaction, making the record simple and easy to understand.
### Before vs. After HIP-1259
| Aspect | Before HIP-1259 | After HIP-1259 |
| :--------------------- | :------------------------------------------------------- | :-------------------------------------------------------------------- |
| **Fee Distribution** | Immediate, per-transaction splits to multiple accounts. | Consolidated into a single account (`0.0.802`) and distributed daily. |
| **Transaction Record** | Shows multiple fee-related transfers. | Shows a single, clear fee transfer to `0.0.802`. |
| **Network Overhead** | Higher, due to multiple balance updates per transaction. | Lower, improving overall network throughput. |
| **Block Stream** | Larger and more complex. | Smaller and more efficient, reducing costs for mirror nodes. |
## Transaction Types
### Standard Transactions
Standard transactions are individual operations submitted to the network, such as token transfers, account creation, or smart contract calls. Each transaction contains a specific operation type that determines its behavior and the changes it makes to the network state.
* **Standard Transaction ID Format**
* The transaction ID uniquely identifies a transaction on the Hedera network. It consists of the payer’s account ID and the transaction’s valid start time, formatted as:
`accountID@validStartTime`\
This ID is used for obtaining receipts and records and for detecting duplicate transactions within the network.
**Transaction ID Example**
`0.0.9401@1598924675.82525000` → A transaction paid for by account `0.0.9401` with a valid start time of `1598924675.82525000`.
### Batch Transactions ([HIP-551](https://hips.hedera.com/hip/hip-551))
#### **Note**
Jumbo EthereumTransaction ([HIP-1086](https://hips.hedera.com/hip/hip-1086)) supports large `callData` directly in `ethereumData` but can’t be included in batch transactions. For limits and details, see the [EthereumTransaction SDK documentation](/native/smart-contracts/ethereum-transaction#handling-large-calldata-payloads).
📣 For detailed gas cost calculation of jumbo Ethereum transactions, refer to the [Gas and Fees page](/evm/development/gas-fees#gas-schedule-and-fee-calculation).
Batch transactions allow multiple operations (HAPI calls) to be executed atomically as a single network transaction, ensuring that all operations either succeed together or fail together (upholding ACID properties).
#### **Outer Batch Transaction ID**
* This is the container transaction that follows the standard transaction ID format (`accountID@validStartTime`).
* It uniquely identifies the entire batch and is used for deduplication of the batch as a whole.
* The fee for the batch is paid by the account that submits the outer transaction.
***Example:*** **`0.0.9401@1598924675.82525000`**
#### **Inner Transaction IDs**
* Each inner transaction has its own transaction ID, following the same format as standard transactions.
* These IDs are associated with the specific operations within the batch.
* Upon processing, each inner transaction record includes a `parentConsensusTimestamp` field, which links it to the consensus timestamp of the outer batch transaction. This linkage preserves the atomicity of the batch by ensuring all inner transactions are tied to the same consensus event.
* Methods such as `getInnerTransactionIds()` can be used to retrieve the inner transaction IDs after execution.
#### **Batch Key**
To prevent tampering—such as reordering, removing, or adding transactions within the batch a Batch Key is used.
* **Purpose:**
* The Batch Key signals the trusted signer who is authorized to finalize the batch.
* It ensures that the inner transactions are submitted as a complete, unaltered set.
* **Mechanism:**
* Each inner transaction must include the Batch Key in its signature map.
* During consensus, the network verifies that every inner transaction carries a valid and consistent Batch Key.
* If any inner transaction is missing a valid Batch Key signature or if inconsistencies are detected, the entire batch is rejected.
#### **Note:**
The outer batch transaction does not include the Batch Key; its role is solely to encapsulate the inner transactions and manage deduplication.
#### **Overall Batch Transaction Processing**
* The batch transaction is processed as a single atomic unit with a consolidated response and receipt.
* Despite the atomic processing, each inner transaction is recorded individually, allowing for detailed auditing and troubleshooting if necessary.
* The design of batch transactions minimizes network overhead and ensures that all related operations are executed in lockstep, thereby maintaining the integrity and consistency of the network state.
**Reference**: [HIP-551](https://hips.hedera.com/hip/hip-551), [HIP-1086](https://hips.hedera.com/hip/hip-1086)
### Nested Transactions
A **nested transaction** triggers subsequent transactions after executing a top-level transaction. The top-level transaction that a user submits is a **parent transaction**. For each subsequent transaction, the parent transaction triggers a **child transaction** as a result of the execution of the parent transaction. An example of a nested transaction is when a user submits the top-level transfer transaction to an account alias that triggers an account creation transaction behind the scenes. This parent/child transaction relationship is also observed with Hedera contracts interacting with HTS precompile. A parent transaction supports up to 999 child transactions since the platform reserves 1000 nanoseconds per user-submitted transaction.
**Transaction IDs**
Parent and child transactions share the payer account ID and transaction valid start timestamp. The child transaction IDs have an additional **nonce** value representing the order in which the child transactions were executed. The parent transaction has a nonce value of 0. The nonce value of child transactions increments by 1 for each child transaction executed due to the parent transaction.
Parent Transaction ID: payerAccountId\@transactionValidStart
Child Transaction ID: payerAccountId\@transactionValidStart/nonce
Example:
* Parent Transaction ID: 0.0.2252\@1640119571.329880313
* Child 1 Transaction ID: 0.0.2252\@1640119571.329880313/1
* Child 2 Transaction ID: 0.0.2252\@1640119571.329880313/2
**Transaction Records**
Nested transaction records are returned by requesting the record for the parent transaction and setting the `setIncludeChildren()` to true. This returns records for all child transactions associated with the parent transaction. Child transaction records include the parent consensus timestamp and the child transaction ID.
The parent consensus timestamp field in a child transaction record is not populated when the child transaction was triggered **before** the parent transaction. An example of this case is creating an account using an account alias. The user submits the transfer transaction to create and fund the new account using the account alias. The transfer transaction (parent) triggers the account create transaction (child). However, the child transaction occurs before the parent transaction, so the new account is created before completing the transfer. The parent consensus timestamp is not populated in this case.
**Transaction Receipts**
Nested transaction receipts can be returned by requesting the parent transaction receipt and setting the boolean value equal to true to return all child transaction receipts.
**Child Transaction Fees**
The transaction fee for the child transaction is included in the record of the parent transaction. The transaction fee will return zero in the child transaction.
## Queries
**Queries** are processed only by the single node to which they are sent. Clients send queries to retrieve some aspect of the current consensus state, like an account balance. Certain queries are free, but generally, they are subject to fees. The full list of queries can be found [here](/native/queries).
Under the [Fee Model](/learn/core-concepts/fee-model), queries can have node, network, and service fee components. However, many common queries (e.g., `TransactionGetReceipt`, `CryptoGetAccountBalance`) are marked as **free** in the fee schedule. For non-free queries, the SDK creates a payment transaction to cover the fees.
A query includes a header that includes a normal HBAR transfer transaction that will serve as how the client pays the node the appropriate fee. There is no way to give partial payment to a node for processing the query, meaning if a user overpaid for the query, the user will not receive a refund. The node processing the query will submit that payment transaction to the network for processing into a consensus statement to receive its fee.
A client can determine the appropriate fee for a query by asking a node for the cost, not the actual data. Such a `COST_ANSWER` query is free to the client.
For more information about query fees, please visit Hedera API fees [overview](https://www.hedera.com/fees).
#### Recall
Hedera does not have **miners** or a special group of nodes responsible for adding transactions to the ledger like alternative distributed ledger solutions. Each node's influence on determining the consensus timestamp for an event is proportional to its stake in HBAR.
Once a transaction has been submitted to the network, clients may seek confirmation that it was successfully processed. Multiple confirmation methods are available, varying in the level of information provided, the duration for which the confirmation is available, the degree of trust, and the corresponding cost.
### Confirmations
* **Receipts:** Receipts provide minimal information - simply whether or not the transaction was successfully processed into a consensus state. Receipts are generated by default and are persisted for 3 minutes. Receipts are free.
* **Records:** Records provide greater detail about the transaction than do receipts — such as the consensus timestamp it received or the results of a smart contract function call. Records are generated by default but are persisted for 3 minutes.
* **State proofs (coming soon):** When querying for a record, a client can optionally indicate that it desires the network to return a state proof in addition to the record. A state-proof documents network consensus on the contents of that record in the consensus state — this collective assertion includes signatures of most of the network nodes. Because state proofs are cryptographically signed by a supermajority of the network, they are secure and potentially admissible in a court of law.
For a more detailed review of the confirmation methods, please check out this [blog post](https://www.hedera.com/blog/transaction-confirmation-methods-in-hedera).
## FAQs
You can refer to the fees page on Hedera's website for a detailed breakdown of transaction and query costs. If you're looking for an estimation tool, you can use the [Hedera fee estimator](https://hedera.com/fees).
Transactions are requests sent by a client to a node with the expectation that they are submitted to the network for processing into consensus order and subsequent application to state. Each transaction has a unique transaction ID composed of the transaction's valid start time and the account ID of the account that is paying for the transaction. This ID is used for obtaining receipts, records, and state proofs and for detecting when duplicate transactions are submitted.
Queries are requests processed only by the single node to which they are sent. [Clients](/support/glossary#client) send queries to retrieve some aspect of the current consensus state, like the balance of an account. Certain queries are free, but generally, queries are subject to fees.
Receipts provide minimal information - whether or not the transaction was successfully processed into a consensus state. Records provide greater detail about the transaction than receipts, such as the consensus timestamp it received or the results of a smart contract function call.
The batch transaction size limits are:
* **Number of transactions**: The maximum number of transactions in a batch is limited to 50 inner transactions.
* **Total size**: The maximum size of the batch transaction must not exceed 6KB, including all inner transactions.
* **Time constraint**: All inner transactions must execute within the standard transaction valid duration (typically 3 minutes).
These limits are designed to ensure that batch transactions can be processed efficiently by the network while still providing enough capacity for complex transaction flows. The 50-transaction limit and 6KB size limit help prevent network congestion, while the time constraint ensures that all operations complete within a reasonable timeframe.
Batch transactions allow multiple operations to be executed atomically in a single network transaction. All operations either succeed together or fail together, providing ACID properties (Atomicity, Consistency, Isolation, and Durability).
You should use batch transactions when:
* You need to ensure multiple operations succeed or fail as a unit
* You want to reduce the complexity of managing multiple separate transactions
* You need to perform operations that logically belong together (like unfreezing an account, transferring tokens, and freezing it again)
* You want to reduce overall transaction fees compared to submitting multiple individual transactions
Batch transactions have a specific fee structure:
* The outer batch transaction has its own fee (node + network), paid by the batch transaction's payer
* Each inner transaction pays its own fee (node + network + service), paid by each inner transaction's payer
* Inner transactions are charged even if the batch fails
* The total cost will typically be less than submitting each transaction individually
This means different accounts can pay for different parts of the batch, allowing for flexible payment arrangements.
A `BatchKey` is a key that must sign the outer batch transaction and is set on each inner transaction. It serves several critical purposes:
* **Security**: Ensures that batch transactions can only be submitted as a whole and prevents tampering with the batch
* **Authorization**: Signals the trusted entity who can finalize the batch
* **Integrity**: Guarantees that the inner transactions haven't been modified after being prepared for the batch
Every inner transaction must have a `BatchKey` set, and the outer batch transaction must be signed by all BatchKeys specified in the inner transactions.
The **recommended way** to prepare transactions for a batch is to use the `batchify()` method:
```javascript theme={null}
// JavaScript example
const tx = new TransferTransaction()
.addHbarTransfer(sender, -10)
.addHbarTransfer(recipient, 10)
.batchify(client, batchPublicKey);
```
This method automatically:
1. Sets the batch key on the transaction
2. Sets the node account ID to 0.0.0 (required for inner transactions)
3. Freezes the transaction with the provided client
4. Signs the transaction with the client's operator key
The **manual approach** requires multiple steps:
```javascript theme={null}
// Manual approach - requires multiple steps
const transferTx = new TransferTransaction()
.addHbarTransfer(sender, -10)
.addHbarTransfer(recipient, 10)
.setBatchKey(batchPublicKey) // Step 1: Set batch key
.freezeWith(client) // Step 2: Freeze with client (sets nodeAccountId to 0.0.0)
.sign(operatorKey); // Step 3: Sign with operator key
```
Learn more [here](/native/transactions/batch).
Error
Code
Cause
Solution
BATCH\_LIST\_EMPTY
388
Submitting a batch with no inner transactions
Add at least one inner transaction to the batch
BATCH\_LIST\_CONTAINS\_DUPLICATES
389
The batch contains duplicate inner transactions
Ensure each inner transaction in the batch is unique
BATCH\_TRANSACTION\_IN\_BLACKLIST
390
An inner transaction is of a type that's not allowed in batches
Only use allowed transaction types in batches
INNER\_TRANSACTION\_FAILED
391
One or more inner transactions failed during execution
Check the specific error for the inner transaction and fix the issue
BATCH\_KEY\_SET\_ON\_NON\_INNER\_TRANSACTION
393
`BatchKey` is set on the outer transaction
Only set BatchKey on inner transactions, not on the outer batch transaction
INVALID\_BATCH\_KEY
394
The `BatchKey` is missing or invalid
Ensure all inner transactions have a valid `BatchKey` set
INVALID\_NODE\_ACCOUNT\_ID
341
Inner transaction has a nodeAccountID other than 0.0.0
Use the batchify() method which automatically sets nodeAccountID to 0.0.0
> #### HIP-1259 FAQs
>
>
>
> This is part of a network enhancement called HIP-1259, which introduced the **Fee Collection Account (0.0.802)**. Instead of splitting fees across multiple accounts with every transaction, all fees are now sent to this single, network-controlled account. This simplifies transaction records and improves network performance.
>
>
>
> Staking rewards are distributed to individual stakers at the end of each 24-hour staking period. The funds for these rewards are moved into the staking rewards account (`0.0.800`) once per day at the **beginning** of the staking period from the Fee Collection Account (`0.0.802`).
>
>
>
> No. The Fee Collection Account (`0.0.802`) is a special, network-controlled account that **does not accept HBAR deposits** from users. It has no keys and is designed to only receive network fees. Any direct transfer attempts will be rejected.
>
>
>
> No. This enhancement **does not change the amount you pay** for transactions. It only optimizes how the network processes fees behind the scenes. The cost of transactions remains the same.
>
>
>
> The daily synthetic transaction is a single, large, network-generated transaction that occurs once per day at the start of each staking period. It distributes all the fees accumulated in the Fee Collection Account (`0.0.802`) to their final destinations, including the staking rewards account (`0.0.800`), node reward account (`0.0.801`), and the network treasury (`0.0.98`).
>
>
# Transaction Properties
Source: https://docs.hedera.com/learn/core-concepts/transactions/properties
Transaction properties on Hedera define the characteristics, behavior, and content of transactions on the network. These properties are set during transaction creation and determine how transactions are processed, validated, and applied to the network state.
Property
Description
Transaction ID
The unique identifier for a transaction on the Hedera network. The transaction ID is composed of the account ID of the account paying for the transaction and the transaction's valid start time.
For child transactions in nested transactions, a nonce value is added: Format: accountID\@validStartTime/nonce Example: 0.0.2252\@1640119571.329880313/1
Node Account ID
The account of the node the transaction is being sent to.
Format: \.\.\ Example: 0.0.3
For batch transactions, the node account ID of inner transactions must be set to 0.0.0 to indicate they are part of a batch.
Transaction Fee
The maximum fee the paying account is willing to pay for the transaction. This is an upper bound, and the actual fee charged may be less.
The transaction fee consists of: - Node fee: Paid to the node processing the transaction - Network fee: Paid to the network for processing the transaction - Service fee: Paid for the specific service being used
For batch transactions, the fee structure is: - The outer batch transaction has its own fee (node + network) - Each inner transaction pays its own fee (node + network + service) - Inner transactions are charged even if the batch fails
Valid Duration
The number of seconds that the client wishes the transaction to be deemed valid for, starting at the transaction's valid start time.
Transactions have a valid duration of up to 180 seconds. If the transaction is not accepted within this timeframe, it will expire and must be recreated.
Memo
A string of text that can be included with a transaction.
Limited to 100 bytes of UTF-8 encoded data. This field is optional and visible to all participants in the network.
Transaction Body
The specific operation to be performed. Only one operation type can be specified in a given transaction.
The transaction body can contain one of the following operation types: - contractCall: Call a smart contract function - contractCreateInstance: Create a new smart contract instance - cryptoCreateAccount: Create a new cryptocurrency account - cryptoTransfer: Transfer cryptocurrency between accounts - fileCreate: Create a new file - consensusSubmitMessage: Submit a message to a topic - tokenCreation: Create a new token - atomicBatch: Execute multiple transactions atomically - And many others (over 40 different transaction types)
Batch Key
A property introduced in HIP-551 that must be set on inner transactions within a batch to ensure transaction integrity and prevent tampering.
The batch key: - Signals the trusted signer who can finalize the batch - Ensures that batch transactions can only be submitted as a whole - Prevents malicious actors from tampering with a batch
Required for all inner transactions in a batch. Must not be set on the outer batch transaction.
Signatures
Cryptographic proofs that authorize the transaction.
Different transaction types may require different signatures: - The account paying for the transaction must always sign - For token operations, the token's admin key may need to sign - For account updates, the account's key must sign - For batch transactions, all batch keys must sign the outer transaction
Inner Transactions
For batch transactions, the list of transactions to be executed atomically.
Inner transactions: - Are signed individually - Each pays for itself (node + network + service fees) - Have their own payer - Are deduplicated individually - Have scheduled = false and nonce = 0 - Cannot be batch transactions themselves - Must have nodeAccountID set to 0.0.0 - Must set batchKey
Transaction Valid Start
The timestamp at which the transaction begins to be valid.
Format: Seconds.nanoseconds since the epoch Example: 1598924675.82525000
This timestamp is used as part of the transaction ID and can be set to a future date/time.
Generated Transaction ID
For child transactions in nested transactions, the transaction ID is generated with an additional nonce value.
Generated by the network once consensus is reached; it reflects when the majority of nodes first received the transaction and is computed as the median of those times. This information is critical for record-keeping and audit trails.
Transaction Receipt
Produced after processing; it details the outcome (including status codes and new entity ids, if applicable) and confirms that consensus has been reached.
Transaction Hash
A unique digest (typically computed using sha-384) representing the finalized transaction content; useful for integrity verification.
# Schedule Transaction
Source: https://docs.hedera.com/learn/core-concepts/transactions/scheduled
## Overview
A scheduled transaction is a type of transaction that allows you to publicly collect all the required signatures on the network. For example, Transaction A requires signatures from Alice, Bob, and Carol. Alice can schedule and sign Transaction A using the schedule transaction. Alice also specifies an expiry time for Transaction A during creation. Once the schedule transaction is successfully executed and posted on the network Alice can call Bob and Carol to sign the transaction. Bob and Carol can sign the schedule transaction by submitting a schedule sign transaction.
Transaction A will automatically execute once it receives the minimum required signatures. However, if the required signatures are not received by the specified expiry time, Transaction A will not execute. Alice can optionally set the transaction to execute automatically at its expiry time. In this case, even if all required signatures are applied to the transaction, the transaction will wait until the expiry time to execute.
Unlike other Hedera transactions, this one allows you to queue a transaction for future execution (up to two months into the future). This feature is ideal for transactions that require multiple signatures and would benefit from being submitted on-chain.
The transaction types that can be scheduled in a schedule transaction as of Consensus Node Release 0.57 are the following:
* `TransferTransaction`
* `TokenMintTransaction`
* `TokenBurnTransaction`
* `AccountCreateTransaction`
* `AccountUpdateTransaction`
* `FileUpdateTransaction`
* `SystemDeleteTransaction`
* `SystemUndeleteTransaction`
* `FreezeTransactions`
* `ContractExecuteTransaction`
* `ContractCreateTransaction`
* `ContractUpdateTransaction`
* `ContractDeleteTransaction`
**Transaction Throttles**
Schedule transactions are throttled based on the transaction they contain. For example, a scheduled transaction containing the transaction type of “CryptoTransfer” would be throttled as defined [here](/networks/mainnet).
\[[Reference: HIP-423](https://hips.hedera.com/hip/hip-423)]
***
## **Creating a Schedule Transaction**
When a schedule transaction is created, the following information will need to be specified in the `ScheduleCreateTransaction`.
#### Scheduled Transaction ID
The Transaction ID of the transaction that needs to be scheduled. You will need to create the transaction that you would like to schedule prior to creating the schedule transaction. Once you have created the transaction you want to schedule, you will need to specify that transaction ID in this field.
**Admin Key**
Setting an admin key on a schedule transaction allows the user to cancel or delete the schedule transaction, if needed. This key is optional to set. If this key is not set upon creation, the transaction cannot be deleted.
**Expiration Time**
The expiration time is a timestamp for specifying when the transaction should be evaluated for execution and then expire. The maximum allowed value is 62 days ([5356800 seconds](https://github.com/hashgraph/hedera-services/blob/develop/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/SchedulingConfig.java#L35)).
* Scheduled Transactions will execute at the earliest available consensus time after their expiration time on a best-effort basis.
**Wait for Expiry**
The default behavior for a scheduled transaction is to automatically execute when the required number signatures for the transaction are received . If the transaction should wait for the specified expiry time to send the transaction to the network, you can optionally enable the wait\_for\_expiry flag.
* When set to true, the transaction will be evaluated for execution at expiration\_time instead of when all required signatures are received.
* When this flag is set to false, the transaction will execute immediately after sufficient signatures are received
**Payer Account ID**
The account ID of the account responsible for paying the transaction fees of the scheduled transaction. This field is optional. If not set, the transaction fee payer for the schedule transaction defaults to the transaction fee payer account of the scheduled transaction.
**Schedule Memo**
Publicly visible text that is stored with the schedule transaction and can be viewed in a network explorer up to 100 bytes and does not include the zero byte.
***
## **Signing and Submitting a Schedule Transaction**
Before submitting your scheduled transaction, you must sign it with the key of the account responsible for paying the schedule transaction fees and, optionally, the key of the transaction fee payer account for the scheduled transaction, if specified. Additionally, if your signature is required for the scheduled transaction, you can sign the `ScheduleCreateTransaction` using that key.
After a `ScheduleCreateTransaction` successfully executes, the receipt will include the schedule ID and the scheduled transaction ID. The **schedule ID** is a unique identifier used to reference the created schedule transaction. The **scheduled transaction ID** represents the transaction scheduled by the schedule transaction. Scheduled transaction IDs include a `?scheduled` flag at the end (e.g., `0.0.1234@1615422161.673238162?scheduled`), indicating it is a scheduled transaction. This ID inherits the valid start time and the account ID from the original schedule transaction.
You can request the current state of a schedule transaction by querying the network for `ScheduleGetInfoQuery` using the schedule ID. The request will return the following information:
* Schedule ID
* Account ID that created the schedule transaction
* Account ID that paid for the creation of the scheduled transaction
* Transaction body of the transaction that was scheduled
* Transaction ID for the transaction that was scheduled
* Current list of signatures
* Admin key (if any)
* Expiration time
* The timestamp of when the transaction was deleted, if true
***
## **Signing the Scheduled Transaction**
After the schedule transaction is submitted, the scheduled transaction becomes available for on-chain signing. Required parties can use a `ScheduleSignTransaction` to add their signatures to the scheduled transaction. Once the minimum number of signatures is collected, the transaction will automatically execute, unless it is configured to wait until the expiry timestamp.
**Scheduled Transaction Record**
Once the schedule transaction successfully executes, the transaction record is made available. To get the transaction record for the scheduled transaction after successful execution, you can do the following:
1. Poll the network for the specified scheduled transaction ID. Once the schedule transaction executes the scheduled transaction successfully, request the record for the scheduled transaction using the scheduled transaction ID.
2. Query a Hedera mirror node for the scheduled transaction ID.
3. Run your own mirror node and query for the scheduled transaction ID.
***
## **Deleting a schedule transaction**
A schedule transaction can be deleted if an admin key was set during its creation. If no admin key was set, the schedule transaction cannot be deleted.
***
## Tutorial/Examples
***
## FAQ
A **schedule transaction** is a transaction that can schedule any Hedera transaction with the ability to collect the required signatures on the Hedera network in preparation for its execution.
A **scheduled transaction** is a transaction that was scheduled by the schedule transaction.
Yes, the entity ID is referred to as the **schedule ID** which is returned in the receipt of the `ScheduleCreateTransaction`.
Refer to the Overview section of this page.
* The creator of the schedule transaction can provide you a schedule ID which you specify in the `ScheduleSignTransaction` to submit your signature.
If the fee payer account for the scheduled transaction (e.g., a transfer transaction) does not have a sufficient balance, the scheduled transaction will fail. However, the schedule transaction itself will still be considered successful.
No, you cannot delay or modify a scheduled transaction once it's been submitted to a network. You would need to delete the scheduled transaction and create a new one with the modifications. If the transaction cannot be deleted, the transaction will need to expire.
* The first transaction to reach consensus will create the schedule transaction and provide the schedule entity ID
* The other users will get the schedule ID in the receipt of the transaction that was submitted. The receipt status will result in `IDENTICAL_SCHEDULE_ALREADY_CREATED`. These users would need to submit a `ScheduleSignTransaction` to append their signatures to the schedule transaction.
The scheduled transaction executes when the last signature is received. Unless, the wait for expiry flag was enabled.
Every time the scheduled transaction is signed.
You can submit a [schedule info query](/native/scheduled/get-info) request to the network.
A scheduled transaction expires at the specified expiration date/time.
The transaction receipt for a schedule that was created contains the new schedule entity ID and the scheduled transaction ID.
# Choose Your Path
Source: https://docs.hedera.com/learn/getting-started/choose-your-path
Two ways to build on Hedera — find the one that fits your background.
Hedera supports two development approaches. Pick the one that matches your background and goals.
## Quick Decision Guide
You're an Ethereum or web3 developer familiar with Solidity, Hardhat, Foundry, or Remix. Hedera is fully EVM-compatible — deploy your existing contracts with minimal changes.
**What you'll use:** MetaMask, Hardhat, Foundry, Remix, Ethers.js, Viem
**What you'll build:** Solidity smart contracts, ERC-20/ERC-721 tokens, dApps with wallet integrations
You're a backend or enterprise developer, or new to web3. The Hiero SDKs give you direct access to all Hedera services without needing Solidity.
**What you'll use:** Hiero JavaScript, Java, Go, or Python SDK
**What you'll build:** HBAR transfers, native tokens via HTS, verifiable event streams via HCS
***
## Path 1: EVM Developer
**For:** Ethereum and Solidity developers, web3 veterans
Deploy Solidity contracts to Hedera using the tools you already know. Hedera's JSON-RPC Relay makes it seamless — MetaMask, Hardhat, and Foundry all work out of the box.
Add Hedera Testnet to MetaMask and fund your wallet.
[Set Up MetaMask →](/evm/quickstart/setup-metamask)
Fund your account from the testnet faucet to pay for transactions.
[Get Test HBAR →](/learn/getting-started/testnet-faucet)
Deploy a Solidity contract using Hardhat or Foundry.
[Deploy with Hardhat →](/evm/quickstart/deploy-with-hardhat)
Want to develop locally without testnet rate limits or faucet dependencies? [Solo](https://solo.hiero.org/docs/simple-solo-setup/quickstart/) runs a full Hedera network on your machine and works with MetaMask, Hardhat, and Foundry out of the box.
***
## Path 2: Native SDK Developer
**For:** Backend and enterprise developers, developers new to web3
Use the Hiero SDK in your preferred language to interact with Hedera services directly. No Solidity required — create accounts, transfer HBAR, mint tokens, and publish to HCS topics using familiar programming patterns.
Set up your Hedera developer portal account and testnet credentials.
[Create Testnet Account →](/learn/getting-started/create-portal-account)
Fund your testnet account from the faucet.
[Get Test HBAR →](/learn/getting-started/testnet-faucet)
Follow the quickstart for your language: JavaScript, Java, or Go.
[Native SDK Quickstart →](/native/quickstart/javascript)
Want to develop locally without testnet rate limits or faucet dependencies? [Solo](https://solo.hiero.org/docs/simple-solo-setup/quickstart/) runs a full Hedera network on your machine and is compatible with all Hiero SDKs.
***
## Not Sure Which Path?
Both paths work. Use the **EVM path** if you want ERC-20/ERC-721 token standards and compatibility with existing Ethereum tooling. Use the **Native SDK path** if you want the full feature set of Hedera Token Service (HTS), including native fractional fees, royalty schedules, and token-level KYC.
Use the **EVM path**. MetaMask and WalletConnect work natively with Hedera via the JSON-RPC Relay. Your existing frontend code (Ethers.js, Viem, Wagmi) requires minimal changes.
Use the **Native SDK path**. Hedera Consensus Service (HCS) lets you publish ordered, timestamped, and verifiable messages to a topic — ideal for audit logs, supply chain events, and AI data provenance.
Start with the **Native SDK path**. The SDK abstracts away the complexity of key management and transactions, letting you focus on learning Hedera concepts with familiar language patterns (JS, Java, Go).
Yes. Many production applications combine both: smart contracts deployed via the EVM path, with HCS or HTS operations performed via the Native SDK. They share the same account system and HBAR balance.
# Create an Account
Source: https://docs.hedera.com/learn/getting-started/create-portal-account
Learn how to create a new Hedera **account** on *testnet* using the JavaScript, Java, Go, SDK, or Python. A [`Hedera account`](/learn/core-concepts/accounts) is your identity on‑chain. It holds your HBAR (the network’s currency) and lets you sign transactions.
***
## Prerequisites
* A Hedera testnet **operator account ID** and **ECDSA** **DER-encoded private key** (from the [Quickstart](/native/quickstart/javascript)).
* A small amount of testnet **HBAR (ℏ)** to pay the `$0.05` account‑creation fee.
***Note***
*You can always check the "*✅ [*Code Check*](#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*](http://hedera.com/discord)*.*
***
## Install the SDK
Open your terminal and create a directory `hedera-examples` directory. Then change into the newly created directory:
```bash theme={null}
mkdir hedera-examples && cd hedera-examples
```
Initialize a `node.js` project in this new directory:
```bash theme={null}
npm init -y
```
Ensure you have [**Node.js**](https://nodejs.org/en/download) `v18` or later installed on your machine. Then, install the[ JavaScript SDK](https://github.com/hiero-ledger/hiero-sdk-js).
```bash theme={null}
npm install --save @hashgraph/sdk
```
Update your `package.json` file to enable ES6 modules and configure the project:
```json highlight={4} theme={null}
{
"name": "hedera-examples",
"version": "1.0.0",
"type": "module",
"main": "createAccountDemo.js",
"scripts": {
"start": "node createAccountDemo.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@hashgraph/sdk": "^2.69.0"
}
}
```
Create a `createAccountDemo.js` file and add the following imports:
```javascript theme={null}
import {
Client,
PrivateKey,
AccountCreateTransaction,
Hbar,
} from "@hashgraph/sdk";
```
Add the[ Java SDK](https://github.com/hiero-ledger/hiero-sdk-java) dependency to your Maven project's `pom.xml` and create your source file:
Create a new **Maven** project and name it `HederaExamples`. Add the following dependencies to your `pom.xml` file:
```xml theme={null}
com.hedera.hashgraphsdk2.61.0com.google.code.gsongson2.10.1io.grpcgrpc-netty-shaded1.73.0
```
Or for **Gradle** projects using the Groovy DSL, add these dependencies to your `build.gradle` file and install the dependencies using `./gradlew build`
```gradle theme={null}
plugins {
id 'java'
id 'application'
}
repositories {
mavenCentral()
}
dependencies {
implementation 'com.hedera.hashgraph:sdk:2.60.0'
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'io.grpc:grpc-netty-shaded:1.61.0'
}
application {
mainClass = 'CreateAccountDemo'
// or 'com.example.CreateAccountDemo' if it's in a package
}
```
Create a `CreateAccountDemo.java` class in `src/main/java/` with the following imports:
```java theme={null}
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 CreateAccountDemo {
public static void main(String[] args) throws Exception {
// Your account creation code will go here
}
}
```
Create a new file `create_account_demo.go` and import the following packages to your file:
```go theme={null}
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
hedera "github.com/hiero-ledger/hiero-sdk-go/v2/sdk"
)
```
In your project's root directory, initialize modules and pull in the [Go SDK](https://github.com/hiero-ledger/hiero-sdk-go):
```wrap theme={null}
```
```go-module theme={null}
go mod init create_account_demo
go get github.com/hiero-ledger/hiero-sdk-go/v2@latest
go mod tidy
```
**Before you start:** Ensure you have Python 3.10+ installed on your machine. Run this command to verify.
```bash theme={null}
python --version
```
If the `python --version` command is not found or shows a version lower than 3.10, install or upgrade Python from [Python Install](https://www.python.org/downloads/).
**Note:** On some systems, you may need to use `python3` instead of `python` for initial setup commands. If `python --version` doesn't work, try `python3 --version` and use `python3` for the virtual environment creation. After activating the virtual environment, always use `python` for all commands.
Open your terminal and create a working directory for your Hedera project. Then navigate into the new directory:
```bash theme={null}
mkdir hedera-examples && cd hedera-examples
```
**Verify Python and pip:** Ensure you have Python 3.10+ and pip installed on your machine. Run these commands to check:
```bash theme={null}
python --version
```
```bash theme={null}
python -m pip --version
```
Create a virtual environment to isolate your project dependencies (Python best practice):
```bash theme={null}
python -m venv .venv
```
Activate the virtual environment to use the isolated Python installation:
```bash theme={null}
source .venv/bin/activate
```
```bash theme={null}
.venv\Scripts\activate
```
Upgrade pip to ensure you have the latest package installer (recommended):
```bash theme={null}
python -m pip install --upgrade pip
```
Install the [Python SDK](https://github.com/hiero-ledger/hiero-sdk-python):
```bash theme={null}
python -m pip install hiero_sdk_python
```
Create a file named `CreateAccountDemo.py` and add the following imports:
```python theme={null}
import os
import time
import requests
from hiero_sdk_python import (
Client,
AccountId,
PrivateKey,
AccountCreateTransaction,
Hbar,
)
# Used to print the EVM address for the new ECDSA public key
from hiero_sdk_python.utils.crypto_utils import keccak256
```
***
## Environment Variables
Set your testnet 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.
```bash theme={null}
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.
```js JavaScript theme={null}
// 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);
```
```java Java theme={null}
// Load your operator credentials
AccountId operatorId = AccountId.fromString(System.getenv("OPERATOR_ID"));
PrivateKey operatorKey = PrivateKey.fromString(System.getenv("OPERATOR_KEY"));
// Initialize your testnet client and set operator
Client client = Client.forTestnet().setOperator(operatorId, operatorKey);
```
```go Go highlight={1} theme={null}
// 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)
```
```python Python theme={null}
# Load your operator credentials
operatorId = AccountId.from_string(os.getenv("OPERATOR_ID", ""))
operatorKey = PrivateKey.from_string(os.getenv("OPERATOR_KEY", ""))
# Initialize your testnet client and set operator
client = Client()
client.set_operator(operatorId, operatorKey)
```
***
## Step 2: Generate a New Key Pair
Generate a new ECDSA private/public key pair for the account you'll create.
### Why keys?
On the Hedera network, a **private key** allows you to sign transactions, ensuring only you control your assets, while a **public key**, shared on-chain, verifies your identity. This key pair is essential for account security.
```js JavaScript theme={null}
// generates a new ECDSA key pair in memory
const newPrivateKey = PrivateKey.generateECDSA();
const newPublicKey = newPrivateKey.publicKey;
```
```java Java theme={null}
// generate an ECDSA key pair in memory
PrivateKey newPrivateKey = PrivateKey.generateECDSA();
PublicKey newPublicKey = newPrivateKey.getPublicKey();
```
```go Go theme={null}
// generate a new key pair
newPrivateKey, _ := hedera.PrivateKeyGenerateEcdsa()
newPublicKey := newPrivateKey.PublicKey()
```
```python Python theme={null}
# generate a new ECDSA key pair in memory
newPrivateKey = PrivateKey.generate_ecdsa()
newPublicKey = newPrivateKey.public_key()
```
**‼️ Security reminder**: Keep your private keys secure - anyone with access
can control your account and funds.
***
## Step 3: Create Your First Account on Hedera
Build an `AccountCreateTransaction` with the *new public key* and initial balance, then execute it. Specify the public key , an optional initial HBAR balance, and once you execute it, the network creates the account and returns the new `AccountId` in the receipt.
```js JavaScript theme={null}
// Build & execute the account creation transaction
const transaction = new AccountCreateTransaction()
.setECDSAKeyWithAlias(newPublicKey) // set the account key
.setInitialBalance(new Hbar(20)); // fund with 20 HBAR
const txResponse = await transaction.execute(client);
const receipt = await txResponse.getReceipt(client);
const newAccountId = receipt.accountId;
console.log(`\nHedera Account created: ${newAccountId}`);
console.log(`EVM Address: 0x${newPublicKey.toEvmAddress()}`);
```
```java Java theme={null}
// Build & execute the account creation transaction
AccountCreateTransaction transaction = new AccountCreateTransaction()
.setKeyWithAlias(newPublicKey) // set the account key
.setInitialBalance(new Hbar(20)); // fund with 20 HBAR
TransactionResponse txResponse = transaction.execute(client);
TransactionReceipt receipt = txResponse.getReceipt(client);
AccountId newAccountId = receipt.accountId;
System.out.println("\nHedera Account created: " + newAccountId);
System.out.println("EVM Address: 0x" + newPublicKey.toEvmAddress());
```
```go Go theme={null}
// build & execute the account creation transaction
transaction := hedera.NewAccountCreateTransaction().
SetECDSAKeyWithAlias(newPublicKey). // set the account key
SetInitialBalance(hedera.NewHbar(20)) // fund with 20 HBAR
// execute the transaction and get response
txResponse, err := transaction.Execute(client)
if err != nil {
panic(err)
}
// get the receipt to extract the new account ID
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
newAccountId := *receipt.AccountID
fmt.Printf("Hedera Account created: %s\n", newAccountId.String())
fmt.Printf("EVM Address: 0x%s\n", newPublicKey.ToEvmAddress())
```
```python Python theme={null}
# Build & execute the account creation transaction
transaction = (
AccountCreateTransaction()
.set_key(newPublicKey) # set the account key
.set_initial_balance(Hbar(20)) # fund with 20 HBAR
)
# Get the receipt to extract the new account ID
receipt = transaction.execute(client)
newAccountId = receipt.account_id
evm_address = keccak256(newPublicKey.to_bytes_ecdsa(compressed=False)[1:])[-20:].hex()
print(f"\nHedera account created: {newAccountId}")
print(f"EVM Address: 0x{evm_address}")
```
***
## Step 4: Query the Account Balance Using Mirror Node API
Use the Mirror Node REST API to check your new account's HBAR balance. Mirror nodes provide free access to network data without transaction fees.
**API endpoint:**
```
/api/v1/balances?account.id={accountId}
```
**Replace the placeholder:**
* **`{accountId}`** - Your new account ID from the creation transaction
> ***Why this endpoint?***
>
> *This endpoint queries account balances directly by account ID. It returns detailed information including HBAR balance in tinybars, making it ideal for verifying the new account was funded with the expected initial balance.*
**Example URLs:**
```javascript JavaScript theme={null}
const mirrorNodeUrl = `https://testnet.mirrornode.hedera.com/api/v1/balances?account.id=${newAccountId}`;
```
```java Java theme={null}
String mirrorNodeUrl = "https://testnet.mirrornode.hedera.com/api/v1/balances?account.id=" + newAccountId;
```
```go Go theme={null}
mirrorNodeUrl := "https://testnet.mirrornode.hedera.com/api/v1/balances?account.id=" + newAccountId.String( )
```
```python Python theme={null}
mirror_node_url = f"https://testnet.mirrornode.hedera.com/api/v1/balances?account.id={newAccountId}"
```
**Complete Implementation:**
```js JavaScript theme={null}
// Wait for Mirror Node to populate data
console.log("\nWaiting for Mirror Node to update...");
await new Promise((resolve) => setTimeout(resolve, 6000));
// Query balance using Mirror Node
const mirrorNodeUrl = `https://testnet.mirrornode.hedera.com/api/v1/balances?account.id=${newAccountId}`;
const response = await fetch(mirrorNodeUrl);
const data = await response.json();
if (data.balances && data.balances.length > 0) {
const balanceInTinybars = data.balances[0].balance;
const balanceInHbar = balanceInTinybars / 100000000;
console.log(`\nAccount balance: ${balanceInHbar} ℏ\n`);
} else {
console.log("Account balance not yet available in Mirror Node");
}
client.close();
```
```java Java theme={null}
// Wait for Mirror Node to populate data
System.out.println("\nWaiting for Mirror Node to update...");
Thread.sleep(6000);
// Query balance using Mirror Node
String mirrorNodeUrl = "https://testnet.mirrornode.hedera.com/api/v1/balances?account.id=" + newAccountId;
HttpClient httpClient = HttpClient.newHttpClient( );
HttpRequest request = HttpRequest.newBuilder().uri(URI.create(mirrorNodeUrl)).build();
HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString( ));
JsonObject data = new Gson().fromJson(response.body(), JsonObject.class);
JsonArray balances = data.getAsJsonArray("balances");
if (balances.size() > 0) {
long balanceInTinybars = balances.get(0).getAsJsonObject().get("balance").getAsLong();
double balanceInHbar = balanceInTinybars / 100000000.0;
System.out.println("\nAccount balance: " + balanceInHbar + " ℏ\n");
} else {
System.out.println("Account balance not yet available in Mirror Node");
}
client.close();
```
```go Go theme={null}
// wait for Mirror Node to populate data
fmt.Println("\nWaiting for Mirror Node to update...")
time.Sleep(6 * time.Second)
// query balance using Mirror Node
mirrorNodeUrl := "https://testnet.mirrornode.hedera.com/api/v1/balances?account.id=" + newAccountId.String()
resp, _ := http.Get(mirrorNodeUrl)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data struct {
Balances []struct {
Balance int64 `json:"balance"`
} `json:"balances"`
}
json.Unmarshal(body, &data)
if len(data.Balances) > 0 {
balanceInTinybars := data.Balances[0].Balance
balanceInHbar := float64(balanceInTinybars) / 100000000.0
fmt.Printf("\nAccount balance: %g ℏ\n\n", balanceInHbar)
} else {
fmt.Println("\nAccount balance not yet available in Mirror Node")
}
client.Close()
```
```python Python theme={null}
# Wait for Mirror Node to populate data
print("\nWaiting for Mirror Node to update...\n")
time.sleep(6)
# Query balance using Mirror Node
mirrorNodeUrl = f"https://testnet.mirrornode.hedera.com/api/v1/balances?account.id={newAccountId}"
response = requests.get(mirrorNodeUrl, timeout=10)
response.raise_for_status()
data = response.json()
balances = data.get("balances", [])
if balances:
balanceInTinybars = balances[0].get("balance", 0)
balanceInHbar = balanceInTinybars / 100_000_000
print(f"Account balance: {balanceInHbar:g} ℏ\n")
else:
print("Account balance not yet available in Mirror Node")
```
***
## ✅ Code check
Before running your project, verify your code matches the complete example:
```javascript wrap theme={null}
import {
Client,
PrivateKey,
AccountCreateTransaction,
Hbar
} from "@hashgraph/sdk";
async function createAccountDemo() {
// 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 a new key pair
const newPrivateKey = PrivateKey.generateECDSA();
const newPublicKey = newPrivateKey.publicKey;
// build & execute the account creation transaction
const transaction = new AccountCreateTransaction()
.setECDSAKeyWithAlias(newPublicKey) // set the account key with alias
.setInitialBalance(new Hbar(20)); // fund with 20 HBAR
const txResponse = await transaction.execute(client);
const receipt = await txResponse.getReceipt(client);
const newAccountId = receipt.accountId;
console.log(`\nHedera account created: ${newAccountId}`);
console.log(`EVM Address: 0x${newPublicKey.toEvmAddress()}`);
// Wait for Mirror Node to populate data
console.log("\nWaiting for Mirror Node to update...");
await new Promise(resolve => setTimeout(resolve, 6000));
// query balance using Mirror Node
const mirrorNodeUrl = `https://testnet.mirrornode.hedera.com/api/v1/balances?account.id=${newAccountId}`;
const response = await fetch(mirrorNodeUrl);
const data = await response.json();
if (data.balances && data.balances.length > 0) {
const balanceInTinybars = data.balances[0].balance;
const balanceInHbar = balanceInTinybars / 100000000;
console.log(`\nAccount balance: ${balanceInHbar} ℏ\n`);
} else {
console.log("Account balance not yet available in Mirror Node");
}
client.close();
}
createAccountDemo().catch(console.error);
```
```java wrap theme={null}
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 CreateAccountDemo {
public static void main(String[] args) throws Exception {
// load your operator credentials
String operatorId = System.getenv("OPERATOR_ID");
String operatorKey = System.getenv("OPERATOR_KEY");
// initialize the client for testnet
Client client = Client.forTestnet()
.setOperator(AccountId.fromString(operatorId), PrivateKey.fromString(operatorKey));
// generate a new key pair
PrivateKey newPrivateKey = PrivateKey.generateECDSA();
PublicKey newPublicKey = newPrivateKey.getPublicKey();
// build & execute the account creation transaction
AccountCreateTransaction transaction = new AccountCreateTransaction()
// set the account key with alias
.setKeyWithAlias(newPublicKey)
.setInitialBalance(new Hbar(20)); // fund with 20 HBAR
TransactionResponse txResponse = transaction.execute(client);
TransactionReceipt receipt = txResponse.getReceipt(client);
AccountId newAccountId = receipt.accountId;
System.out.println("\nHedera account created: " + newAccountId);
System.out.println("EVM Address: 0x" + newPublicKey.toEvmAddress() + "\n");
// Wait for Mirror Node to populate data
System.out.println("\nWaiting for Mirror Node to update...\n");
Thread.sleep(6000);
// query balance using Mirror Node
String mirrorNodeUrl = "https://testnet.mirrornode.hedera.com/api/v1/balances?account.id=" + newAccountId;
HttpClient httpClient = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(mirrorNodeUrl))
.build();
HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());
Gson gson = new Gson();
JsonObject data = gson.fromJson(response.body(), JsonObject.class);
if (data.has("balances") && data.getAsJsonArray("balances").size() > 0) {
JsonArray balances = data.getAsJsonArray("balances");
JsonObject accountBalance = balances.get(0).getAsJsonObject();
long balanceInTinybars = accountBalance.get("balance").getAsLong();
double balanceInHbar = balanceInTinybars / 100000000.0;
System.out.println("Account balance: " + balanceInHbar + " ℏ\n");
} else {
System.out.println("Account balance not yet available in Mirror Node");
}
client.close();
}
}
```
```go wrap theme={null}
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 a new key pair
newPrivateKey, _ := hedera.PrivateKeyGenerateEcdsa()
newPublicKey := newPrivateKey.PublicKey()
// build & execute the account creation transaction
transaction := hedera.NewAccountCreateTransaction().
SetECDSAKeyWithAlias(newPublicKey). // set the account key with alias
SetInitialBalance(hedera.NewHbar(20)) // fund with 20 HBAR
txResponse, _ := transaction.Execute(client)
receipt, _ := txResponse.GetReceipt(client)
newAccountId := *receipt.AccountID
fmt.Printf("\nHedera account created: %s\n", newAccountId.String())
fmt.Printf("EVM Address: 0x%s\n", newPublicKey.ToEvmAddress())
// wait for Mirror Node to populate data
fmt.Println("\nWaiting for Mirror Node to update...")
time.Sleep(6 * time.Second)
// query balance using Mirror Node
mirrorNodeUrl := "https://testnet.mirrornode.hedera.com/api/v1/balances?account.id=" + newAccountId.String()
resp, _ := http.Get(mirrorNodeUrl)
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
var data struct {
Balances []struct {
Balance int64 `json:"balance"`
} `json:"balances"`
}
json.Unmarshal(body, &data)
if len(data.Balances) > 0 {
balanceInTinybars := data.Balances[0].Balance
balanceInHbar := float64(balanceInTinybars) / 100000000.0
fmt.Printf("\nAccount balance: %g ℏ\n\n", balanceInHbar)
} else {
fmt.Println("\nAccount balance not yet available in Mirror Node")
}
client.Close()
}
```
```python wrap theme={null}
import os
import time
import requests
from hiero_sdk_python import (
Client, AccountId, PrivateKey, AccountCreateTransaction, Hbar
)
from hiero_sdk_python.utils.crypto_utils import keccak256
# load your operator credentials
operatorId = AccountId.from_string(os.getenv("OPERATOR_ID", ""))
operatorKey = PrivateKey.from_string(os.getenv("OPERATOR_KEY", ""))
# initialize the client for testnet
client = Client()
client.set_operator(operatorId, operatorKey)
# generate a new key pair
newPrivateKey = PrivateKey.generate_ecdsa()
newPublicKey = newPrivateKey.public_key()
# build & execute the account creation transaction
transaction = (
AccountCreateTransaction()
.set_key(newPublicKey) # set the account key
.set_initial_balance(Hbar(20)) # fund with 20 HBAR
)
receipt = transaction.execute(client)
newAccountId = receipt.account_id
evm_address = keccak256(newPublicKey.to_bytes_ecdsa(compressed=False)[1:])[-20:].hex()
print(f"\nHedera account created: {newAccountId}")
print(f"EVM Address: 0x{evm_address}")
# wait for Mirror Node to populate data
print("\nWaiting for Mirror Node to update...\n")
time.sleep(6)
# query balance using Mirror Node
mirrorNodeUrl = f"https://testnet.mirrornode.hedera.com/api/v1/balances?account.id={newAccountId}"
response = requests.get(mirrorNodeUrl, timeout=10)
response.raise_for_status()
data = response.json()
balances = data.get("balances", [])
if balances:
balanceInTinybars = balances[0].get("balance", 0)
balanceInHbar = balanceInTinybars / 100_000_000
print(f"Account balance: {balanceInHbar:g} ℏ\n")
else:
print("Account balance not yet available in Mirror Node")
client.close()
```
***
## Run Your Project
Ensure your environment variables are set:
```bash theme={null}
export OPERATOR_ID="0.0.1234"
export OPERATOR_KEY="3030020100300506032b657004220420..."
```
```bash theme={null}
node createAccountDemo.js
```
```bash theme={null}
mvn compile exec:java -Dexec.mainClass="com.example.CreateAccountDemo"
```
```gradle theme={null}
./gradlew run
```
```bash theme={null}
go run create_account_demo.go
```
```bash theme={null}
python CreateAccountDemo.py
```
**When finished, deactivate the virtual environment:**
```bash theme={null}
deactivate
```
#### **Expected sample output:**
```
Hedera account created: 0.0.12345
EVM Address: 0xabcdef0123456789abcdef0123456789abcdef01
Waiting for Mirror Node to update...
Account balance: 20 ℏ
```
### ‼️ Troubleshooting
Verify OPERATOR\_KEY is a valid DER-encoded private key string
KEY\_REQUIRED
Missing key in AccountCreateTransaction
Ensure you call .setECDSAKeyWithAlias(newPublicKey)
OPERATOR\_ID and OPERATOR\_KEY must be set
Environment variables not accessible
Check environment variables are set and accessible to your application
Cannot read properties of undefined
Missing imports or undefined variables
Verify all imports are included and variables are defined
***
## What just happened?
1. The SDK built an **`AccountCreateTransaction`** and signed it with your operator key.
2. A consensus node validated the signature and charged the account creation fee.
3. After network consensus, a unique **account ID** and **EVM address** were assigned and returned in the receipt.
4. The account was funded with **20 HBAR** from your operator account.
5. The Mirror Node API confirmed your new account exists with the expected balance.
***
## Next steps
* [Learn more about accounts](/learn/core-concepts/accounts)
* [Create a Token](/native/tutorials/tokens/create-first-token)
* Explore more examples in the SDK repos ([JavaScript](https://github.com/hiero-ledger/hiero-sdk-js), [Java](https://github.com/hiero-ledger/hiero-sdk-java), [Go](https://github.com/hiero-ledger/hiero-sdk-go))
***
🎉 **Great work!** You now control a brand new Hedera account secured by your fresh key pair. Keep the private key safe and never commit it to source control.
## Additional resources
# Faucet API for Testnet HBAR
Source: https://docs.hedera.com/learn/getting-started/faucet-api
Use the Hedera Portal HTTP faucet endpoint to fund testnet and previewnet accounts with HBAR programmatically from scripts, CI pipelines, or SDKs.
The Hedera Portal exposes an HTTP faucet endpoint so you can fund testnet and previewnet accounts programmatically from the terminal, scripts, CI/CD pipelines, SDKs, or agentic workflows, with no browser, no reCAPTCHA, and no copy-pasting.
For a one-off, the [web faucet](/learn/getting-started/testnet-faucet) at [portal.hedera.com](https://portal.hedera.com/faucet) is the simplest
path. Use the Faucet API when you want to fund accounts programmatically or in bulk.
**Need mainnet HBAR?** This Portal Faucet API serves **testnet and previewnet** only. For mainnet, the ecosystem project HashPort runs a
community-operated [Faucet API](https://faucet.hashport.network/) that distributes HBAR to onboard new users.
***
## Before you start
The Faucet API requires a Hedera Portal account and a Personal Access Token (the web faucet does not):
1. **A Hedera Portal account.** [Sign up](https://portal.hedera.com/register) if you don't have one.
2. **A Personal Access Token (PAT).** Generate one from the Portal UI, following [Create an API Key](/native/tutorials/getting-started/create-api-key).
In your terminal, set your token as an environment variable so the `curl` examples below can read it. The `export` command runs in a macOS or Linux shell (bash or zsh) and lasts for the current terminal session:
```bash theme={null}
export HEDERA_PAT=""
```
On Windows PowerShell, use:
```powershell theme={null}
$env:HEDERA_PAT = ""
```
***
## Make a request
Send a `POST` to `/api/disbursement/cli` with your PAT as a Bearer token.
**About the destination:** A Hedera account ID (`0.0.x`) must already exist on the network. An EVM address that has no account yet gets one created
on the spot through [auto account creation](/learn/core-concepts/accounts/auto-account-creation) when the HBAR lands.
```bash wrap theme={null}
curl -X POST https://portal.hedera.com/api/disbursement/cli \
-H "Authorization: Bearer $HEDERA_PAT" \
-H "Content-Type: application/json" \
-d '{
"address": "0.0.12345",
"amount": 25,
"network": "testnet"
}'
```
### Request fields
| Field | Required | Description |
| ------------ | -------- | ------------------------------------------------------------------------------------------- |
| `address` | Yes | The destination: a Hedera account ID (`0.0.12345`) or an EVM address (`0x…`, 40 hex chars). |
| `amount` | Yes | A whole number of HBAR to send, from `1` to `100`. |
| `network` | No | `testnet` (default) or `previewnet`. |
| `sdkVersion` | No | For tool authors: the SDK version, so usage can be attributed. |
| `cliVersion` | No | For tool authors: the CLI version. |
***
## Response
A successful request returns the amount sent, the on-chain transaction ID, and your remaining daily allowance:
```json theme={null}
{
"amount": 25,
"transactionId": "0.0.2@1715600000.123456789",
"dailyQuota": {
"used": 25,
"remaining": 75
}
}
```
* **`amount`**: how much HBAR was actually sent.
* **`transactionId`**: the on-chain transaction ID. Use it to look up the transfer on a [mirror node](/reference/rest-api) or [HashScan](https://hashscan.io).
* **`dailyQuota`**: `used` and `remaining` HBAR in your rolling 24-hour window.
***
## Limits
Three limits apply to every request, and a call must satisfy all three:
* **Up to 100 HBAR per call.**
* **Up to 100 HBAR per 24 hours**, as a rolling window tied to your Hedera Portal account.
* **One funding per destination account per 24 hours.** This cooldown is shared with the web faucet, so if an account was funded on the web today, the API will refuse it for the next 24 hours, and vice versa.
A few things to know:
* You can spread your daily 100 HBAR across multiple accounts (for example, 10 HBAR to each of 10 accounts).
* You can hit your daily cap while fresh destinations are still available, in which case you'll need to wait for the window to roll off.
* You can hit a destination's cooldown while you still have HBAR left, so switch to another account.
***
## Troubleshooting
The faucet uses standard HTTP status codes:
| Status | What it means | How to fix it |
| --------------------------- | --------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| `400 Bad Request` | Your request body didn't pass validation, for example `amount` is missing or outside `1` to `100`, or `network` is invalid. | Check the request body against the [request fields](#request-fields). |
| `403 Forbidden` | Your token is missing, malformed, or has been revoked. | Verify the `Authorization` header reads `Bearer `, and that the token still exists in the Portal. |
| `422 Unprocessable Entity` | The destination can't be funded: the account ID does not exist, address is invalid, or it was funded in the last 24 hours. | Confirm the account exists or the address is well-formed, and off cooldown, or fund a different account. |
| `429 Too Many Requests` | You've used your full 100 HBAR for the last 24 hours. | Wait for your 24-hour window to roll off, then try again. |
| `500 Internal Server Error` | The transfer failed on the server side. | Retry after a minute. If it persists, check the transaction ID on a mirror node and reach out to support. |
***
## Next steps
* [Create an API Key (PAT)](/native/tutorials/getting-started/create-api-key): required to authenticate Faucet API requests.
* [Web Testnet Faucet](/learn/getting-started/testnet-faucet): create and fund an account from the browser.
* [Learn more about accounts](/learn/core-concepts/accounts)
# Start Here
Source: https://docs.hedera.com/learn/getting-started/index
Your guide to understanding and building on Hedera.
Welcome to Hedera. This section walks you through everything you need to understand the network, choose the right development approach, and make your first transaction. Whether you're an Ethereum developer or new to web3 entirely, you'll find a path here.
## Your Journey
Understand what makes Hedera different — Hashgraph consensus, the Governing Council, and the services you can build on.
[Read: What is Hedera? →](/learn/getting-started/what-is-hedera)
See how Hedera compares to Ethereum and Solana on finality, fees, and governance — and what kinds of applications it's built for.
[Read: Why Build on Hedera? →](/learn/getting-started/why-hedera)
Pick the development approach that fits your background: EVM/Solidity or Native SDK (JS/Java/Go).
[Read: Choose Your Path →](/learn/getting-started/choose-your-path)
Create your testnet account, fund it, and run your first transaction.
[Create Testnet Account →](/learn/getting-started/create-portal-account)
# Hedera Docs MCP Server Setup Guide
Source: https://docs.hedera.com/learn/getting-started/mcp-setup
Connect your AI tools to the official Hedera documentation for real-time, accurate answers to your Hedera-related questions — directly from the source.
***
## What Is the Hedera MCP Server?
The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard that creates direct connections between AI applications and external data sources. Instead of relying on general web search results, MCP lets AI tools search your documentation directly for more accurate, up-to-date information.
Hedera's MCP server exposes a **`SearchHedera`** tool that gives any MCP-compatible AI application the ability to search across the entire Hedera knowledge base, including:
* API references and SDK documentation
* Code examples and tutorials
* How-to guides and quickstarts
* Network concepts and architecture docs
### MCP Server URL
```
https://docs.hedera.com/mcp
```
Use this URL to connect any supported AI tool to the Hedera documentation.
### Why MCP Over Web Search?
| Feature | Web Search | MCP |
| :-------------- | :------------------------------------------ | :----------------------------------------------------------------------------------------------------------- |
| **Source** | Search engine index (may be stale) | Live documentation content |
| **Noise** | Affected by SEO and ranking algorithms | Goes straight to official docs |
| **Integration** | Separate step from response generation | Searches during response generation |
| **Accuracy** | May surface outdated or third-party content | Always returns the most up to date Hedera documentation. No stale indexes or outdated training data training |
***
## Installation
The Hedera MCP server is a remote `HTTP` server hosted by Mintlify. No code is downloaded or executed on your machine. Your AI tool connects directly to [https://docs.hedera.com/mcp](https://docs.hedera.com/mcp) over `HTTPS` and queries the documentation through the `SearchHedera` tool.
To connect, you manually add the server URL to your AI tool's configuration using the setup instructions below. This is the recommended approach because it gives you full visibility into what's being configured, and no third-party packages are installed or executed on your system.
***
## Setup by Tool
#### Note
If you run into issues, refer to the [Additional Resources](/learn/getting-started/mcp-setup#additional-resources) section below as setup instructions can change and that's where you'll find the most current guidance.
### Claude (Web & Mobile)
Claude supports MCP servers as custom connectors. Available on free, Pro, Max, Team, and Enterprise plans (free users are limited to one custom connector).
**Steps:**
1. Navigate to **Customize → Connectors** (or go directly to [claude.ai/settings/connectors](https://claude.ai/settings/connectors)).
2. Click the **"+"** button next to Connectors, then select **"Add custom connector"**.
3. Enter the details:
* **Name:** `Hedera Docs`
* **URL:** `https://docs.hedera.com/mcp`
4. Click **Add**.
5. In any chat, click the **"+"** button in the lower left of the chat interface, then hover over **"Connectors"** to enable **Hedera Docs** for that conversation.
Claude will automatically use the `SearchHedera` tool when your questions relate to Hedera development.
***
### Claude Desktop
Claude Desktop supports remote MCP servers through custom connectors and local servers through Desktop Extensions.
**For remote servers (like Hedera's):**
1. Click the **"+"** button at the bottom of the chat box, then select **"Connectors"**.
2. If you haven't added the Hedera connector yet, navigate to **Settings → Developer → Connectors** and click **"Add custom connector"**.
3. Enter the details:
* **Name:** `Hedera Docs`
* **URL:** `https://docs.hedera.com/mcp`
4. Click **Add** and restart Claude Desktop if prompted.
> **Note:** Custom connectors added on Claude web (claude.ai) are also available in Claude Desktop and Claude mobile when signed in with the same account.
***
### Claude Code
Claude Code connects to MCP servers via CLI commands.
**Steps:**
Run the following command in your terminal:
```bash theme={null}
claude mcp add --transport http hedera-docs https://docs.hedera.com/mcp
```
**Verify the connection:**
```bash theme={null}
claude mcp list
```
You should see `hedera-docs` listed as a connected server.
***
### Cursor IDE
Cursor supports MCP servers through its `mcp.json` configuration file.
**Option A: Command Palette (Recommended)**
1. Press `Cmd + Shift + P` (macOS) or `Ctrl + Shift + P` (Windows/Linux).
2. Search for **"Open MCP settings"**.
3. Select **Add custom MCP**.
4. Add the following to `mcp.json`:
```json theme={null}
{
"mcpServers": {
"hedera-docs": {
"url": "https://docs.hedera.com/mcp"
}
}
}
```
**Option B: Manual Configuration**
Create or edit the `mcp.json` file at one of these locations:
| Scope | Path |
| :------------------------ | :----------------------------------- |
| **Global** (all projects) | `~/.cursor/mcp.json` |
| **Project-specific** | `.cursor/mcp.json` (in project root) |
**Verify the connection:** In Cursor's chat, ask *"What tools do you have available?"* and confirm the Hedera Docs server appears.
***
### VS Code (GitHub Copilot)
VS Code supports MCP servers via the `.vscode/mcp.json` file, available when using GitHub Copilot in Agent mode.
**Steps:**
1. Create the file `.vscode/mcp.json` in your project root.
2. Add the following configuration:
```json theme={null}
{
"servers": {
"hedera-docs": {
"type": "http",
"url": "https://docs.hedera.com/mcp"
}
}
}
```
3. In the Copilot chat panel, switch to **Agent mode** and restart the MCP server if prompted.
> **Note:** VS Code uses a `"servers"` key (not `"mcpServers"`) and requires a `"type"` field.
***
### Windsurf IDE
Windsurf supports MCP servers through its Cascade AI agent. It supports `stdio`, `Streamable HTTP`, and `SSE` transport types.
**Option A: MCP Marketplace**
1. Click the **MCPs icon** in the Cascade panel (top-right).
2. Browse or search for the Hedera MCP server and click **Install**.
**Option B: Manual Configuration**
1. Press `Cmd + Shift + P` (macOS) or `Ctrl + Shift + P` (Windows/Linux).
2. Search for **"Open Windsurf Settings"**.
3. Navigate to **Cascade → MCP Servers** or directly edit:
```
~/.codeium/windsurf/mcp_config.json
```
4. Add the following:
```json theme={null}
{
"mcpServers": {
"hedera-docs": {
"serverUrl": "https://docs.hedera.com/mcp"
}
}
}
```
> **Note:** Windsurf has a limit of **100 total tools** across all connected MCP servers. You can toggle individual tools on/off per server.
***
### ChatGPT
ChatGPT supports remote MCP servers through its Apps feature (formerly called "Connectors"). Developer Mode is required for full MCP tool support and is available for Plus, Pro, Business, Enterprise, and Edu plans.
**Steps:**
1. Open **Settings → Apps** (or **Settings → Connectors** in some UI versions).
2. Navigate to **Advanced settings** and toggle on **Developer Mode**.
3. Click **Create** to add a new app/connector.
4. Enter the details:
* **Name:** `Hedera Docs`
* **Description:** `Search the official Hedera documentation.`
* **URL:** `https://docs.hedera.com/mcp`
5. Click **Create**.
6. In a new chat, click the **"+"** button in the composer area, enable Developer Mode for the session, and select **Hedera Docs**.
> **Important:** Developer Mode is required because without it, ChatGPT only accepts MCP servers that implement a specific `search` + `fetch` tool pattern. The Hedera MCP server exposes a `SearchHedera` tool, which requires Developer Mode to be accessible in chat. See the [ChatGPT Developer Mode guide](https://platform.openai.com/docs/guides/developer-mode) for details.
***
### Gemini CLI
Google's Gemini CLI supports MCP servers through a `settings.json` configuration file. It supports `stdio`, `SSE`, and `Streamable HTTP` transports.
**Steps:**
1. Open or create the Gemini CLI settings file:
```
~/.gemini/settings.json
```
2. Add the Hedera MCP server:
```json theme={null}
{
"mcpServers": {
"hedera-docs": {
"url": "https://docs.hedera.com/mcp"
}
}
}
```
3. Launch Gemini CLI and verify with:
```
/mcp
```
You should see the `hedera-docs` server listed with its available tools.
> **Prerequisite:** Install Gemini CLI with `npm install -g @google/gemini-cli@latest`.
***
***
## Using Multiple MCP Servers
You can connect the Hedera MCP server alongside other documentation MCP servers. Here's an example Cursor configuration with multiple servers:
```json theme={null}
{
"mcpServers": {
"hedera-docs": {
"url": "https://docs.hedera.com/mcp"
},
"another-docs": {
"url": "https://example.com/docs/mcp"
}
}
}
```
**Best practices for multiple servers:**
* Connect only the servers relevant to your current work to keep context focused.
* Be specific in your prompts so the AI searches the most relevant server.
* MCP servers don't consume context until the AI actively calls a search tool.
* Disconnect servers you're not actively using to reduce context usage.
***
## Configuration Reference
A quick-reference table of config file paths and JSON formats for each tool:
| Tool | Config File Path | Server Key | URL Key |
| :------------------- | :------------------------------------------------- | :----------- | :----------------------- |
| **Cursor** (global) | `~/.cursor/mcp.json` | `mcpServers` | `url` |
| **Cursor** (project) | `.cursor/mcp.json` | `mcpServers` | `url` |
| **VS Code** | `.vscode/mcp.json` | `servers` | `url` (+ `type: "http"`) |
| **Windsurf** | `~/.codeium/windsurf/mcp_config.json` | `mcpServers` | `serverUrl` |
| **Claude (Web)** | Customize → Connectors | UI-based | URL field |
| **Claude Desktop** | Settings → Developer → Connectors | UI-based | URL field |
| **Claude Code** | CLI: `claude mcp add` | — | — |
| **Gemini CLI** | `~/.gemini/settings.json` | `mcpServers` | `url` |
| **ChatGPT** | Settings → Apps → Create (Developer Mode required) | UI-based | URL field |
***
## Troubleshooting
**Server not connecting?**
* Verify the URL is exactly `https://docs.hedera.com/mcp` (no trailing slash).
* Ensure you have an active internet connection.
* Restart your AI tool after adding the configuration.
**Tools not appearing?**
* In Cursor/VS Code, click the refresh button next to the server entry.
* In Claude Code, run `claude mcp list` to verify the server is registered.
* In Windsurf, check the MCPs icon for server status — a red indicator means the connection failed.
**Search returning no results?**
* Try rephrasing your query with more specific Hedera terminology.
* Ensure the MCP server is enabled/active for your current chat session.
**Rate limits:**
Mintlify-hosted MCP servers enforce rate limits to protect availability: 200 requests/hour per user (IP) and 1,000 requests/hour per documentation site.
***
## Additional Resources
* [Model Context Protocol Specification](https://modelcontextprotocol.io/)
* [Mintlify MCP Documentation](https://www.mintlify.com/docs/ai/model-context-protocol)
* [Claude MCP Connectors](https://claude.ai/settings/connectors)
* [Cursor MCP Docs](https://docs.cursor.com/en/context/mcp)
* [VS Code MCP Servers](https://code.visualstudio.com/docs/copilot/chat/mcp-servers)
* [Windsurf MCP Integration](https://docs.windsurf.com/windsurf/cascade/mcp)
* [ChatGPT Developer Mode](https://platform.openai.com/docs/guides/developer-mode)
* [Gemini CLI MCP Setup](https://google-gemini.github.io/gemini-cli/docs/tools/mcp-server.html)
* [Claude Code MCP Docs](https://docs.anthropic.com/en/docs/claude-code/mcp)
***
[GitHub](https://github.com/theekrystallee) |
[X](https://X.com/theekrystallee)
[LinkedIn](https://www.linkedin.com/in/ty-patches-smith)
# Developer Playground
Source: https://docs.hedera.com/learn/getting-started/portal-playground
Try Hedera transactions directly in your browser with this interactive playground. Create accounts, transfer HBAR, mint tokens, and exercise core services on testnet with no SDK install or keys to manage. Built in collaboration with Kabila and open source.
# Hedera Testnet Faucet
Source: https://docs.hedera.com/learn/getting-started/testnet-faucet
Get free testnet HBAR by entering your EVM wallet address or Hedera account ID in the Hedera web faucet. No developer portal account required to fund a testnet account.
The Hedera faucet allows you to quickly create and fund a testnet account without creating a developer portal account. The faucet flow auto-creates an account when you enter an EVM wallet address to receive testnet HBAR.
Prefer the terminal? The [Faucet API](/learn/getting-started/faucet-api) funds testnet and previewnet accounts programmatically, ideal for scripts, CI pipelines, and agentic workflows.
To use the faucet, head to the [faucet](https://portal.hedera.com/faucet) landing page.
* Enter your EVM wallet address in the **Enter Wallet Address** field and
* Click the **RECEIVE 100 TESTNET HBAR** button to initiate an [auto account creation](/learn/core-concepts/accounts/auto-account-creation) flow that creates and funds a new testnet account
#### ⚠️ **Important**
When you use an EVM wallet address for the first time, **Auto Account Creation** kicks in to establish a new Hedera account linked to your EVM address.
This process creates a **hollow account**, an account with an ID and alias but no key. Hollow accounts can receive HBAR and tokens, but it cannot transfer tokens from the account or modify any account properties until the account key has been added and the account is complete.
To complete the account, use it as the **fee payer** in a transaction and sign with the **ECDSA private key** tied to the EVM address. Once completed, the account works like any regular Hedera account.
## Environment Variable Setup (Optional)
This section is for developers who want to set up their environment for production use. If you plan to use Hardhat, Foundry, or other development frameworks, complete this step to configure your environment variable. Skip if you're just getting started.
If you plan to use Hardhat, Foundry, or other development frameworks, you'll want to set up environment variables:
1. **Export your private key from MetaMask:**
1. Click the **three dots menu** → **Account details** → **Show private key**
2. Copy the private key (64-character hex string)
For detailed instructions on exporting your private key, refer to [this how-to
guide](https://support.metamask.io/managing-my-wallet/secret-recovery-phrase-and-private-keys/how-to-export-an-accounts-private-key/).
Keep your private keys secure. Anyone with access to them can control your
wallet and any funds.
2. **Create a `.env` file** in your project directory with your account credentials
```
# private key exported from MetaMask
OPERATOR_KEY=0xc89f760d43832...
# new testnet account ID
OPERATOR_ID=0.0.1234
# Hedera testnet RPC endpoint
RPC_URL=https://testnet.hashio.io/api
```
#### Warning
Storing private keys in a `.env` file is not considered best practice. There is always a risk of accidentally committing and pushing to a public GitHub repo and exposing your keys. Make it a habit to add `.env` to your `.gitignore` file as a precautionary measure.
We **highly advise against** using a private key with mainnet funds.
## Next Step
* [Deploy a Smart Contract Using Remix](/evm/quickstart/deploy-with-remix)
* [Deploy your First Contract with Contract Builder](/evm/quickstart/deploy-with-contract-builder)
# What is Hedera?
Source: https://docs.hedera.com/learn/getting-started/what-is-hedera
Hedera is a public, open-source, proof-of-stake distributed ledger built on Hashgraph consensus — not a blockchain.
Hedera is a public distributed ledger network designed for production-grade decentralized applications. It is used by enterprises, startups, and developers around the world to build tokenization platforms, supply chain systems, AI data pipelines, DeFi protocols, and more. Unlike traditional blockchain networks, Hedera is built on the Hashgraph consensus algorithm — delivering faster finality, predictable fees, and enterprise-grade governance.
## The Hedera Network
Hedera is a **public, permissionless network** — anyone can submit transactions, deploy smart contracts, and build applications on it. Key characteristics:
* **Not a blockchain.** Hedera uses a directed acyclic graph (DAG) data structure with Hashgraph consensus instead of a chain of blocks.
* **Governed by the Hedera Governing Council** — a body of up to 39 global enterprises (including Google, IBM, Boeing, LG, and Deutsche Telekom) that oversee network operations and roadmap decisions. No single entity controls the network.
* **\~10,000 TPS** on the public network with 3–5 second absolute finality.
* **Carbon-negative certified** — Hedera offsets more carbon than it produces, verified annually.
* **Open-source via Hiero** — All Hedera SDKs and node software are governed by the Hiero project under the Linux Foundation.
A free **testnet** is available for development. No real HBAR required — fund your testnet account from the [faucet](/learn/getting-started/testnet-faucet).
## Hashgraph Consensus
Hedera's consensus mechanism is **Hashgraph**, invented by Dr. Leemon Baird. It uses two core techniques:
**Gossip about gossip** — Nodes spread transaction information to random peers, and each message carries a history of who told whom, forming a graph of events rather than a linear chain.
**Virtual voting** — Instead of sending vote messages across the network, nodes calculate what other nodes *would* vote based on the shared event graph. This eliminates communication overhead and enables high throughput.
Key properties:
* **Fair** — Transaction ordering is mathematically fair; no miner or validator can manipulate the order.
* **Fast** — Consensus is reached in seconds, not minutes.
* **Byzantine fault-tolerant** — The network is secure as long as fewer than 1/3 of nodes are malicious.
* **ABFT** — Asynchronous Byzantine Fault Tolerant, the highest security grade in distributed systems theory.
For a deeper technical dive, see [Hashgraph Consensus](/learn/core-concepts/hashgraph/index).
## What Can You Build on Hedera?
Hedera offers four native network services, each accessible via the SDK or smart contracts:
Deploy Solidity contracts using Hardhat, Foundry, or Remix. Full EVM compatibility with Ethereum tooling.
Create and manage fungible tokens and NFTs natively on the network: no smart contract required.
Publish ordered, timestamped, verifiable messages to a topic. Ideal for audit logs, supply chain, and AI data provenance.
Store immutable files on the network. Used for smart contract bytecode and configuration data.
## Key Properties at a Glance
| Property | Hedera |
| -------------- | ------------------------------------------------ |
| Throughput | \~10,000 TPS |
| Finality | 3–5 seconds (absolute) |
| Average Fee | \~\$0.0001 per transaction |
| Governance | Hedera Governing Council (39 enterprises) |
| Consensus | Hashgraph (ABFT) |
| EVM Compatible | Yes — full Solidity and Ethereum tooling support |
| Carbon | Negative (certified) |
## Frequently Asked Questions
No. Hedera uses a directed acyclic graph (DAG) with Hashgraph consensus, not a chain of blocks. The result is faster finality and higher throughput than traditional blockchains, while maintaining the same decentralized, tamper-proof properties.
HBAR is the native cryptocurrency of the Hedera network. It is used to pay transaction fees and to stake to nodes for network security. Transaction fees on Hedera are denominated in USD and paid in HBAR at the current exchange rate, making costs predictable.
The Hedera Governing Council — up to 39 global enterprises serving rotating, term-limited seats. Council members include Google, IBM, Boeing, LG, Deutsche Telekom, Ubisoft, and others. No single member controls the network, and all node software is open-source via the Hiero project under the Linux Foundation.
Yes. Hedera supports the Ethereum Virtual Machine (EVM). You can deploy Solidity smart contracts and use Ethereum tooling (Hardhat, Foundry, MetaMask, Ethers.js, Viem) via Hedera's JSON-RPC Relay. In addition to EVM contracts, Hedera offers native services (HTS, HCS, HFS) accessible directly from smart contracts via precompile interfaces.
Hedera has three networks: **Mainnet** (production, real HBAR), **Testnet** (free development environment, test HBAR from faucet), and **Previewnet** (early access to upcoming features). Start with Testnet.
# Why Build on Hedera?
Source: https://docs.hedera.com/learn/getting-started/why-hedera
Performance, predictability, and sustainability — built for production.
Hedera is built for applications where correctness, speed, and cost matter. If you've been frustrated by slow finality, unpredictable gas fees, or lack of enterprise governance on other networks, Hedera was designed to solve exactly those problems.
## Performance and Finality
Hedera delivers **3–5 second absolute finality** — not probabilistic. Once a transaction is consensus-stamped, it cannot be reversed or reorganized.
* **\~10,000 TPS** on the public mainnet
* **No block reorgs** — Hashgraph consensus is deterministic
* Consistent performance under load — no throughput degradation during high-traffic periods
Unlike probabilistic finality on Ethereum (where you typically wait for 12+ confirmations), Hedera's finality is absolute. A transaction confirmed at second 4 stays confirmed.
## Predictable, Low Fees
Hedera fees are **fixed in USD** and paid in HBAR at the current exchange rate. There are no gas auctions.
* Average transaction fee: **\~\$0.0001**
* Fees are set by the Hedera Governing Council and change infrequently
* No fee spikes during network congestion — fees don't fluctuate with demand
* Smart contract execution costs are predictable and published in the [fee schedule](/networks/fees)
This makes Hedera practical for high-volume applications (millions of transactions per month) where gas costs would be prohibitive on other networks.
## Enterprise-Grade Governance
Hedera is governed by the **Hedera Governing Council** — up to 39 global enterprises serving rotating, term-limited seats:
* **Current members include:** Google, IBM, Boeing, LG Electronics, Deutsche Telekom, Ubisoft, Avery Dennison, ServiceNow, and others
* No single member controls more than one seat — no single entity can dictate network direction
* All node software is open-source via **Hiero**, a Linux Foundation project
* Published roadmap and transparent governance decisions
This governance model makes Hedera suitable for regulated industries and enterprise procurement requirements where vendor lock-in and single-point-of-control are deal-breakers.
## Sustainability
* **Carbon-negative certified** — Hedera offsets more carbon than it produces, verified by independent auditors annually
* **Proof-of-stake consensus** — no energy-intensive mining
* One of the most energy-efficient public networks in operation
## How Hedera Compares
| | Hedera | Ethereum | Solana |
| ------------------ | ------------------------ | ------------------------ | ---------------------- |
| **Finality** | 3–5s (absolute) | \~13 min (probabilistic) | \~0.4s (probabilistic) |
| **TPS** | \~10,000 | \~15–30 | \~65,000 |
| **Avg Fee** | \~\$0.0001 | Variable (gas auction) | \~\$0.00025 |
| **Governance** | Council (39 enterprises) | Decentralized | Solana Foundation |
| **EVM Compatible** | Yes (+ native services) | Native | No |
| **Carbon** | Negative | Neutral (post-Merge) | Not certified |
| **Finality Type** | Absolute (ABFT) | Probabilistic | Probabilistic |
## What Gets Built on Hedera
Real-world asset tokenization, stablecoins, loyalty points, and carbon credits using Hedera Token Service.
Verifiable provenance tracking and audit trails using Hedera Consensus Service.
Timestamped, tamper-proof logs of AI model inputs and outputs for auditability.
AMMs, lending protocols, and yield strategies using EVM-compatible smart contracts.
NFT minting and marketplaces with native royalty enforcement via HTS.
Ordered, verifiable event streams for compliance, audit, and cross-org coordination.
## Ready to Build?
## Frequently Asked Questions
Fees on Hedera are set by the Governing Council and denominated in USD. Unlike gas auctions (where fees spike when the network is busy), Hedera fees are fixed per transaction type regardless of network load. The HBAR amount you pay fluctuates with the HBAR/USD exchange rate, but the USD cost stays stable.
It depends on your use case. For financial applications, tokenization, or any scenario where transaction reversal is unacceptable, absolute finality matters enormously. For applications that already handle eventual consistency (like most web2 apps), probabilistic finality is often acceptable. If you need settlement guarantees, Hedera's ABFT finality is the right choice.
Hiero is a Linux Foundation project that maintains all Hedera SDKs and the node software (Hedera Services). The source code is publicly available, and contributions are welcome from the community. This means Hedera is not proprietary — it cannot be shut down or held hostage by a single company.
Yes. The Governing Council model, KYC/KYB-capable token features (via HTS token management keys), and enterprise-grade SLAs make Hedera a preferred network for financial services, healthcare data, and government applications. Several central bank digital currency (CBDC) pilots have been built on Hedera.
# Learn Hedera
Source: https://docs.hedera.com/learn/index
Get started with Hedera: what it is, how it works, and the core concepts behind accounts, tokens, consensus, and smart contracts.
# Delete an allowance
Source: https://docs.hedera.com/native/accounts/adjust-allowance
A transaction that deletes one or more non-fungible approved allowances from an owner's account. This operation will remove the allowances granted to one or more specific non-fungible token serial numbers. Each owner account listed as wiping an allowance must sign the transaction.
The total number of NFT serial number deletions within the transaction body cannot exceed 20.
#### **Fungible and HBAR allowance deletion:**
HBAR and fungible token allowances can be removed by setting the amount to zero in `CryptoApproveAllowance`.
#### **Transaction Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for the base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your transaction fee cost
#### **Transaction Signing Requirements**
* The transaction must be signed by the owner's account
* The transaction must be signed by the transaction fee-paying account if different than the owner's account
* If the owner's account and transaction fee-paying account are the same, only one signature is required
**Reference:** [HIP-336](https://github.com/hashgraph/hedera-improvement-proposal/blob/master/HIP/hip-336.md)
### Methods
| **Method** | **Type** | **Description** |
| -------------------------------------------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `deleteAllTokenNftAllowances(, )` |
| Removes the NFT allowance from the spender account. |
```java Java theme={null}
//Create the transaction
AccountAllowanceDeleteTransaction transaction = new AccountAllowanceDeleteTransaction()
.deleteAllTokenNftAllowances(nftId , ownerAccountId);
//Sign the transaction with the owner account key
TransactionResponse txResponse = transaction.freezeWith(client).sign(ownerAccountKey).execute(client);
//Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
//v2.12.0+
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = new AccountAllowanceDeleteTransaction()
.deleteAllTokenNftAllowances(nftId , ownerAccountId);
//Sign the transaction with the owner account key
const signTx = await transaction.sign(ownerAccountKey);
//Sign the transaction with the client operator private key and submit to a Hedera network
const txResponse = await signTx.execute(client);
//Request the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the transaction consensus status
const transactionStatus = receipt.status;
console.log("The transaction consensus status is " +transactionStatus.toString());
//v2.13.0+
```
```go Go theme={null}
//Create the transaction
transaction := hedera.NewAccountAllowanceDeleteTransaction().
DeleteAllTokenNftAllowances(nftId , ownerAccountId)
if err != nil {
panic(err)
}
//Sign the transaction with the owner account private key and submit to the network
txResponse, err := transaction.Sign(ownerAccountKey).Execute(client)
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction consensus status
transactionStatus := receipt.Status
println("The transaction consensus status is ", transactionStatus)
//v2.13.1+
```
```rust Rust theme={null}
// Create the transaction
let transaction = AccountAllowanceDeleteTransaction::new()
.delete_all_token_nft_allowances(nft_id, owner_account_id);
// Sign the transaction with the owner account key
let tx_response = transaction
.freeze_with(&client)?
.sign(owner_account_key)
.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
# Approve an allowance
Source: https://docs.hedera.com/native/accounts/approve-allowance
A transaction that allows a token owner to delegate a token spender to spend the specified token amount on behalf of the token owner. A Hedera account owner can provide an allowance for HBAR, non-fungible, and fungible tokens.
The **owner** is the Hedera account that owns the tokens and grants the token allowance to the spender. The **spender** is the account that spends tokens, authorized by the owner, from the owner's account. The spender pays for the transaction fees when transferring tokens from the owner's account to another recipient. This means that the transaction fee payer for the `TransferTransaction` is required to set the spender account ID as the transaction fee payer. If the spender account ID is not set as the transaction fee payer, the system will error with `SPENDER_DOES_NOT_HAVE_ALLOWANCE`.
The maximum number of token approvals for the `AccountAllowanceApproveTransaction` cannot exceed 20. Note that each NFT serial number counts as a single approval. An `AccountAllowanceApproveTransaction` granting 20 NFT serial numbers to a spender will use all of the approvals permitted for the transaction.
A single NFT serial number can only be granted to one spender at a time. If an approval assigns a previously approved NFT serial number to a new user, the old user will have their approval removed.
Each owner account is limited to granting 100 allowances. This limit spans HBAR, fungible token allowances, and non-fungible token `approved_for_all` grants. No limit exists on the number of NFT serial number approvals an owner may grant.
The number of allowances set on an account will increase the auto-renewal fee for the account. Conversely, removing allowances will decrease the auto-renewal fee for the account.
To decrease the allowance for a given spender, you must set the amount to the value you would like to authorize the account for. If the spender account was authorized to spend 25 HBAR and the owner wants to modify their allowance to 5 HBAR, the owner would submit the `AccountAllowanceApproveTransaction` for 5 HBAR.
Only when a spender is set on an explicit NFT ID of a token, do we return the spender ID in `TokenNftInfoQuery` for the respective NFT. If `approveTokenNftAllowanceAllSerials` is used to approve all NFTs for a given token class, and no NFT ID is specified; we will not return a spender ID for all the serial numbers of that token.
**Transaction Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for the base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your transaction fee cost
**Transaction Signing Requirements**
* Must be signed by the owner's account
* Must be signed by the transaction fee payer if different then the owner account
* If the owner and transaction fee payer key are the same only one signature is required
**Reference:** [HIP-336](https://github.com/hashgraph/hedera-improvement-proposal/blob/master/HIP/hip-336.md)
## Constructor
| Constructor | **Description** |
| ------------------------------------------ | --------------------------------------------------------- |
| `new AccountAllowanceApproveTransaction()` | Initializes the AccountAllowanceApproveTransaction object |
## Transaction Properties
| **Method** | **Type** | Requirement |
| --------------------------------------------------------------------------------- | ----------------------------------- | ----------- |
| `approveHbarAllowance()` | AccountId, AccountId, Hbar | Optional |
| `approveTokenAllowance()` | TokenId, AccountId, AccountId, long | Optional |
| `approveTokenNftAllowance()` | NftId, AccountId, AccountId | Optional |
| `approveTokenNftAllowanceAllSerials()` | TokenId, AccountId, AccountId | Optional |
| `setHighVolume()` | boolean | Optional |
## Get Transaction Values
| **Method** | **Type** | **Description** |
| ------------------------ | ------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `getHbarApprovals()` | List\ | Returns the HBAR allowances |
| `getTokenApprovals()` | List\ | Returns the fungible token allowances |
| `getTokenNftApprovals()` | List\ | Returns the NFT allowances |
| `getHighVolume()` | boolean | Returns whether this transaction uses [high-volume throttles](/learn/core-concepts/high-volume-entity-creation) |
This transaction supports [high-volume entity creation](/learn/core-concepts/high-volume-entity-creation)
(HIP-1313). Setting `setHighVolume(true)` routes the transaction through dedicated
high-volume throttle capacity with variable-rate pricing. Always pair this with
`setMaxTransactionFee()` to cap your costs.
```java Java theme={null}
//Create the transaction
AccountAllowanceApproveTransaction transaction = new AccountAllowanceApproveTransaction()
.approveHbarAllowance(ownerAccount, spenderAccountId, Hbar.from(1));
//Sign the transaction with the owner account key and the transaction fee payer key (client)
TransactionResponse txResponse = transaction.freezeWith(client).sign(ownerAccountKey).execute(client);
//Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
//v2.12.0+
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = new AccountAllowanceApproveTransaction()
.approveHbarAllowance(ownerAccount, spenderAccountId, Hbar.from(1));
//Sign the transaction with the owner account key
const signTx = await transaction.sign(ownerAccountKey);
//Sign the transaction with the client operator private key and submit to a Hedera network
const txResponse = await signTx.execute(client);
//Request the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the transaction consensus status
const transactionStatus = receipt.status;
console.log("The transaction consensus status is " +transactionStatus.toString());
//v2.13.0
```
```go Go theme={null}
//Create the transaction
transaction := hedera.NewAccountAllowanceApproveTransaction().
ApproveHbarAllowance(ownerAccount, spenderAccountId, Hbar.fromTinybars(1))
FreezeWith(client)
if err != nil {
panic(err)
}
//Sign the transaction with the owner account private key
txResponse, err := transaction.Sign(ownerAccountKey).Execute(client)
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction consensus status
transactionStatus := receipt.Status
println("The transaction consensus status is ", transactionStatus)
//v2.13.1+
```
```rust Rust theme={null}
// Create the transaction
let transaction = AccountAllowanceApproveTransaction::new()
.approve_hbar_allowance(owner_account, spender_account_id, Hbar::from(1));
// Sign the transaction with the owner account key and the transaction fee payer key (client)
let tx_response = transaction
.freeze_with(&client)?
.sign(owner_account_key)
.execute(&client)
.await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
# Create an account
Source: https://docs.hedera.com/native/accounts/create
Programmatically create a Hedera account with AccountCreateTransaction, set an ECDSA key with EVM Address from Public Key, and fund the new account with HBAR.
A Hedera account is required to interact with any network service, since every transaction and query fee is paid from an account. You can create a previewnet or testnet account on the [Hedera Developer Portal](https://portal.hedera.com/), or use a third-party wallet to generate a free [mainnet account](/networks/mainnet/access).
This page covers programmatic account creation with `AccountCreateTransaction()`. The transaction must be signed and paid for by an existing account. To obtain the new account ID, request the [receipt](/native/transactions/receipt) of the transaction.
For a complete list of account properties, see the [accounts overview](/native/accounts).
**Recommended default for EVM compatibility:** Create accounts with an ECDSA key and set the **EVM Address from Public Key** at creation. This enables native compatibility with EVM wallets, JSON-RPC tooling, and smart-contract interactions. Use `setECDSAKeyWithAlias()` (or `setKeyWithAlias()` with an ECDSA key) to do this in a single call.
***
## Transaction fees and signing
* The account paying for the transaction fee is required to sign the transaction.
* The sender also pays the `maxAutoAssociations` fee and the rent for the first auto-renewal period.
* See the transaction and query [fees](/networks/fees) table for the base transaction fee.
* Use the [Hedera fee estimator](https://hedera.com/fees) to estimate cost.
***
## Constructor
| Constructor | Description |
| -------------------------------- | ------------------------------------------------- |
| `new AccountCreateTransaction()` | Initializes the `AccountCreateTransaction` object |
## Methods
Exactly one of `setKey`, `setKeyWithAlias`, `setECDSAKeyWithAlias`, or `setKeyWithoutAlias` is required. See [Setting the key and alias](#setting-the-key-and-alias) for which to use.
| Method | Type | Key type accepted | Requirement |
| ------------------------------------------------- | ----------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `setKey()` | Key | PublicKey or PrivateKey | Sets the account key with no EVM Address from Public Key. Accepts ED25519 or ECDSA. |
| `setKeyWithAlias()` | Key (ECDSA) | PublicKey or PrivateKey | Sets the key and the EVM Address from Public Key. Java's one-argument recommended form; in other SDKs this is the two-argument overload `setKeyWithAlias(key, aliasKey)`. |
| `setECDSAKeyWithAlias()` | Key (ECDSA) | PrivateKey only in Rust, C++, Swift; PublicKey or PrivateKey elsewhere | **Recommended for EVM use cases.** Sets the key and the EVM Address from Public Key in one call. Not available in Java, which uses the one-argument `setKeyWithAlias` instead. |
| `setKeyWithoutAlias()` | Key | PublicKey or PrivateKey | Sets the key with no EVM Address from Public Key (the account falls back to its EVM Address from Account ID). Use if you plan to rotate keys later. |
| `setAlias()` | EvmAddress | — | Explicitly sets the `alias` bytes. Pair with `publicKey.toEvmAddress()` to set the EVM Address from Public Key. |
| `setInitialBalance()` | Hbar | — | Optional |
| `setReceiverSignatureRequired()` | boolean | — | Optional |
| `setMaxAutomaticTokenAssociations()` | int | — | Optional |
| `setStakedAccountId()` | AccountId | — | Optional |
| `setStakedNodeId()` | long | — | Optional |
| `setDeclineStakingReward()` | boolean | — | Optional |
| `setAccountMemo()` | String | — | Optional |
| `setHighVolume()` | boolean | — | Optional |
| `setAutoRenewPeriod()` | Duration | — | Disabled |
### EVM address from public key
Setting an ECDSA-derived EVM address at creation makes the account natively addressable from EVM wallets, JSON-RPC, and Solidity (`msg.sender`). The address is the rightmost 20 bytes of the Keccak-256 hash of the ECDSA public key. To enable this behavior, use the one-argument key-with-alias method for your SDK (`setECDSAKeyWithAlias()` in most SDKs, `setKeyWithAlias()` in Java). See [Setting the key and alias](#setting-the-key-and-alias) for the full breakdown.
**Immutability:** The EVM address is bound to the original ECDSA public key and does **not** change if you later rotate keys via `CryptoUpdateTransaction`. Integrations keyed to that EVM address (smart-contract permissions, address-based access lists) will continue to reference the original address.
**If key rotation is required:** Use `setKeyWithoutAlias()` instead. The account will fall back to its EVM Address from Account ID (the long-zero form).
**Recovery model:** If keys are compromised or must be replaced, create a new account with a new ECDSA key, then migrate assets and state. Do not rely on key rotation to preserve the same EVM identity.
#### High-volume entity creation
This transaction supports [high-volume entity creation](/learn/core-concepts/high-volume-entity-creation) (HIP-1313). Setting `setHighVolume(true)` routes the transaction through dedicated high-volume throttle capacity with variable-rate pricing. Always pair this with `setMaxTransactionFee()` to cap your costs.
### Maximum auto-associations
The `maxAutoAssociations` property determines how many automatic token associations an account allows.
| Value | Behavior |
| :---: | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `0` | Automatic token associations and token airdrops are not allowed. Tokens must be manually associated. This also applies when the value is less than or equal to `usedAutoAssociations`. |
| `-1` | Unlimited automatic token associations. This is the default for accounts created via [auto account creation](/learn/core-concepts/accounts/auto-account-creation) and for hollow accounts that have been completed. The sender still pays the association fee and initial rent for each new token. |
| `> 0` | Automatic token associations are limited to the specified number. |
Reference: [HIP-904](https://hips.hedera.com/hip/hip-904).
***
## Setting the key and alias
`AccountCreateTransaction` provides three ways to set the account key and the EVM address. Which one you choose depends on whether you want EVM compatibility and whether you plan to rotate the account key later. The method names below use the JavaScript SDK; see the [per-SDK quick reference](#per-sdk-quick-reference) for the equivalent in each language.
| Pattern | Method | What it does | Use when |
| ----------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Key with matching EVM address** (recommended) | `setECDSAKeyWithAlias(key)` | Sets the account key and derives the EVM Address from Public Key from the same ECDSA key. Accepts a private or public ECDSA key. | You want a standard EVM-compatible account. The signing key and the EVM address are backed by the same ECDSA key, so `msg.sender`, EVM wallets, and JSON-RPC all resolve correctly. |
| **Key with a separate alias key** | `setKeyWithAlias(key, aliasKey)` | Sets the account (signing) key to one key and derives the EVM address from a different ECDSA key. Both keys must sign the transaction. | The account is controlled by one key (for example an Ed25519 key, or a different ECDSA key) but you want the EVM address derived from a separate ECDSA key. |
| **Key without an alias** | `setKeyWithoutAlias(key)` | Sets only the account key, with no EVM Address from Public Key. The account falls back to its EVM Address from Account ID (long-zero form). | You plan to rotate or update the account key later. The EVM Address from Public Key is immutable once set, so omit it if the key is not final. |
To assign a specific, pre-computed EVM address instead of deriving one from the key, combine `setKeyWithoutAlias(key)` with `setAlias(evmAddress)`. This is the [HIP-583](https://hips.hedera.com/hip/hip-583) pattern, useful when you already hold the target EVM address (for example, when migrating an existing EVM address).
### Private or public key input
Whether the one-shot method accepts a **public** key or requires a **private** key depends on the SDK (see the [per-SDK quick reference](#per-sdk-quick-reference)):
* **JavaScript, Java, Python, and Go** accept either a public or a private ECDSA key. The EVM address is derived from the public component, so the choice is only about what key material you hand the builder. Pass a public key when the private key is held externally (a hardware wallet, HSM, or another party); the create transaction must still be signed by the corresponding private key.
* **Rust, C++, and Swift** accept a **private key only**. If you hold just a public key (HSM or external-signer flows), use the manual path instead: set the key without an alias and set the alias explicitly from the derived address, for example `setKeyWithoutAlias(publicKey)` with `setAlias(publicKey.toEvmAddress())`. See the [HSM-signing tutorials](/native/tutorials/advanced/hsm-signing/aws-kms).
In the **JavaScript SDK**, the four derive variants are:
| Variant | Call |
| -------------------------------------------------- | ---------------------------------------------- |
| Derived EVM address from a **private account key** | `setECDSAKeyWithAlias(privateKey)` |
| Derived EVM address from a **public account key** | `setECDSAKeyWithAlias(publicKey)` |
| Derived EVM address from a **private alias key** | `setKeyWithAlias(accountKey, privateAliasKey)` |
| Derived EVM address from a **public alias key** | `setKeyWithAlias(accountKey, publicAliasKey)` |
(In Java, the one-argument forms use `setKeyWithAlias(key)` rather than `setECDSAKeyWithAlias`.) When you use a separate alias key, both the account key and the alias key must sign the transaction.
***
## Per-SDK quick reference
| SDK | One-shot method | Accepts `PublicKey`? | `PublicKey.toEvmAddress()` returns | `AccountId.toEvmAddress()` |
| ---------- | ---------------------------------- | --------------------- | ------------------------------------------- | --------------------------- |
| JavaScript | `setECDSAKeyWithAlias(key)` | Yes | `string` (hex, no `0x`) | Yes |
| Java | `setKeyWithAlias(key)` (1-arg) | Yes | `EvmAddress` | Yes |
| Python | `set_key_with_alias(key)` | Yes | `EvmAddress` | Yes |
| Rust | `set_ecdsa_key_with_alias(key)` | No, `PrivateKey` only | `Option` | Use `to_solidity_address()` |
| C++ | `setECDSAKeyWithAlias(key)` | No, `PrivateKey` only | `EvmAddress` (on `ECDSAsecp256k1PublicKey`) | Use `toSolidityAddress()` |
| Swift | `keyWithAlias(_ privateKeyECDSA:)` | No, `PrivateKey` only | `EvmAddress?` | Yes (`throws`) |
| Go | `SetECDSAKeyWithAlias(key)` | Yes | `string` (hex, no `0x`) | Yes |
**Java naming:** `setKeyWithAlias(key)` with one argument is Java's equivalent of `setECDSAKeyWithAlias(key)` in other SDKs. The two-argument overload `setKeyWithAlias(key, ecdsaKey)` is used when the account key and the alias-derivation key differ.
**Long-zero address parallel:** Rust and C++ do not expose `toEvmAddress()` on `AccountId`. They expose `to_solidity_address()` / `toSolidityAddress()` instead, which returns the same long-zero form (the **EVM Address from Account ID**). Functionally equivalent, just a naming difference.
**Swift `throws`:** Swift's `AccountId.toEvmAddress()` is declared `throws`, so call sites need `try` (e.g., `try accountId.toEvmAddress()`).
***
## Example
```javascript wrap JavaScript theme={null}
// Create new ECDSA key
const ecdsaPublicKey = PrivateKey.generateECDSA().publicKey;
// Create the transaction
const transaction = new AccountCreateTransaction()
// Sets the EVM Address from Public Key (recommended for EVM compatibility)
.setECDSAKeyWithAlias(ecdsaPublicKey)
// Use .setKeyWithoutAlias(ecdsaPublicKey) if you plan to rotate keys soon after creation
.setInitialBalance(new Hbar(1));
// Sign the transaction with the client operator private key and submit to a Hedera network
const txResponse = await transaction.execute(client);
//Request the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the account ID
const newAccountId = receipt.accountId;
console.log("The new account ID is " + newAccountId);
// v2.84.0
```
```java wrap Java theme={null}
// Create new ECDSA key
PublicKey ecdsaPublicKey = PrivateKey.generateECDSA().getPublicKey();
// Create the transaction
AccountCreateTransaction transaction = new AccountCreateTransaction()
// Sets the EVM Address from Public Key (recommended for EVM compatibility)
.setKeyWithAlias(ecdsaPublicKey)
// Use .setKeyWithoutAlias(ecdsaPublicKey) if you plan to rotate keys soon after creation
.setInitialBalance(new Hbar(1));
// Sign the transaction with the client operator private key and submit to a Hedera network
TransactionResponse txResponse = transaction.execute(client);
//Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the account ID
AccountId newAccountId = receipt.accountId;
System.out.println("The new account ID is " + newAccountId);
// v2.72.0
```
```go wrap Go theme={null}
// Create new ECDSA key
ecdsaPrivateKey, _ := hedera.PrivateKeyGenerateEcdsa()
ecdsaPublicKey := ecdsaPrivateKey.PublicKey()
// Create the transaction
transaction := hedera.NewAccountCreateTransaction().
// Sets the EVM Address from Public Key (recommended for EVM compatibility)
SetECDSAKeyWithAlias(ecdsaPublicKey).
// Use SetKeyWithoutAlias(ecdsaPublicKey) if you plan to rotate keys soon after creation
SetInitialBalance(hedera.NewHbar(1))
// Sign the transaction with the client operator private key and submit to a Hedera network
txResponse, err := transaction.Execute(client)
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
//Get the account ID
newAccountId := *receipt.AccountID
fmt.Printf("The new account ID is %v\n", newAccountId)
// v2.80.0
```
```rust wrap Rust theme={null}
// Create new ECDSA key
let ecdsa_private_key = PrivateKey::generate_ecdsa();
// Create the transaction
let transaction = AccountCreateTransaction::new()
// Sets the EVM Address from Public Key (recommended for EVM compatibility).
// In Rust this method takes the ECDSA PrivateKey; a PublicKey is not accepted.
.set_ecdsa_key_with_alias(ecdsa_private_key)
// Use .set_key_without_alias(key) if you plan to rotate keys soon after creation
.initial_balance(Hbar::new(1));
// Sign the transaction with the client operator private key and submit to a Hedera network
let tx_response = transaction.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the account ID
let new_account_id = receipt.account_id.unwrap();
println!("The new account ID is {}", new_account_id);
// v0.45.0
```
```python Python theme={null}
# Create new ECDSA key
ecdsa_public_key = PrivateKey.generate_ecdsa().public_key()
# Create the transaction
transaction = (
AccountCreateTransaction()
# Sets the EVM Address from Public Key (recommended for EVM compatibility)
.set_key_with_alias(ecdsa_public_key)
# Use .set_key_without_alias(ecdsa_public_key) if you plan to rotate keys soon after creation
.set_initial_balance(Hbar(1))
)
# Sign the transaction with the client operator private key and submit to a Hedera network
tx_response = transaction.execute(client)
# Request the receipt of the transaction
receipt = tx_response.get_receipt(client)
# Get the account ID
new_account_id = receipt.account_id
print(f"The new account ID is {new_account_id}")
# v0.2.7
```
***
## Deriving the EVM address (`toEvmAddress` helper)
If you call `setAlias(...)` directly instead of a one-shot key-with-alias method, derive the EVM address from the ECDSA public key with the SDK helper:
```javascript JavaScript theme={null}
// publicKey.toEvmAddress() returns the EVM Address from Public Key (hex, no 0x prefix)
const evmAddress = ecdsaPublicKey.toEvmAddress();
const transaction = new AccountCreateTransaction()
.setKeyWithoutAlias(ecdsaPublicKey)
.setAlias(evmAddress)
.setInitialBalance(new Hbar(1));
```
```java Java theme={null}
String evmAddress = ecdsaPublicKey.toEvmAddress();
AccountCreateTransaction transaction = new AccountCreateTransaction()
.setKeyWithoutAlias(ecdsaPublicKey)
.setAlias(evmAddress)
.setInitialBalance(new Hbar(1));
```
```go Go theme={null}
evmAddress := ecdsaPublicKey.ToEvmAddress()
transaction := hedera.NewAccountCreateTransaction().
SetKeyWithoutAlias(ecdsaPublicKey).
SetAlias(evmAddress).
SetInitialBalance(hedera.NewHbar(1))
```
```rust Rust theme={null}
// to_evm_address() returns Option for an ECDSA public key
let evm_address = ecdsa_public_key.to_evm_address().expect("ECDSA key has an EVM address");
let transaction = AccountCreateTransaction::new()
.set_key_without_alias(ecdsa_public_key)
.alias(evm_address)
.initial_balance(Hbar::new(1));
```
```python Python theme={null}
# public_key.to_evm_address() returns the EVM Address from Public Key
evm_address = ecdsa_public_key.to_evm_address()
transaction = (
AccountCreateTransaction()
.set_key_without_alias(ecdsa_public_key)
.set_alias(evm_address)
.set_initial_balance(Hbar(1))
)
```
In most cases, prefer `setECDSAKeyWithAlias(publicKey)`, which sets the key and derives the alias in one call. Use the `toEvmAddress` helper when you need the value separately (for logging, validation, or pairing with `setAlias`).
**Note on overloading:** `toEvmAddress` is also exposed on `AccountId` in some SDKs. `AccountId.toEvmAddress()` returns the **EVM Address from Account ID** (the long-zero form), not the **EVM Address from Public Key**. For EVM compatibility, you want the `PublicKey` variant. `AccountId.toEvmAddress()` is not implemented in Rust and is named `toSolidityAddress()` in C++.
***
## Verifying the EVM address
After creating an account, confirm the EVM Address from Public Key was set correctly using either the SDK or the mirror node.
### Using the SDK
```javascript JavaScript theme={null}
const info = await new AccountInfoQuery()
.setAccountId(newAccountId)
.execute(client);
console.log(`EVM address: 0x${info.contractAccountId}`);
```
```java Java theme={null}
AccountInfo info = new AccountInfoQuery()
.setAccountId(newAccountId)
.execute(client);
System.out.println("EVM address: 0x" + info.contractAccountId);
```
```python Python theme={null}
info = AccountInfoQuery().set_account_id(new_account_id).execute(client)
print(f"EVM address: 0x{info.contract_account_id}")
```
```go Go theme={null}
info, _ := hedera.NewAccountInfoQuery().
SetAccountID(newAccountId).
Execute(client)
fmt.Printf("EVM address: 0x%s\n", info.ContractAccountID)
```
If the account was created with `setKeyWithoutAlias(...)`, `contractAccountId` returns the **EVM Address from Account ID** (long-zero form, starting with 24 zero hex characters). If it was created with `setECDSAKeyWithAlias(...)` or equivalent, it returns the **EVM Address from Public Key**.
### Using the mirror node REST API
```bash theme={null}
# Look up by account ID
curl https://mainnet.mirrornode.hedera.com/api/v1/accounts/0.0.1234
# Look up by EVM address
curl https://mainnet.mirrornode.hedera.com/api/v1/accounts/0xab...cd
```
The `evm_address` field in the response is the canonical EVM address for the account.
***
## Common pitfalls
**Creating an ECDSA account with `setKey()` only.** The account is valid and can hold HBAR, but it has no **EVM Address from Public Key**. It falls back to its **EVM Address from Account ID** (long-zero form), which means:
* It is not natively addressable from MetaMask or other EVM wallets keyed to the public-key EVM address.
* Solidity contracts that compare `msg.sender` against the expected EVM address will not match.
* JSON-RPC tooling expecting an ECDSA-derived address sees an unexpected long-zero address.
Use `setECDSAKeyWithAlias(publicKey)` (or `setKeyWithAlias(publicKey)` with an ECDSA key) instead.
**Using deprecated `PrivateKey.generate()`.** This helper silently returns an ED25519 key, which cannot be used to derive an EVM address. Use `PrivateKey.generateECDSA()` for new accounts.
### Common consensus errors
| Error code | Cause | Resolution |
| --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `INVALID_ALIAS_KEY` | Alias is not derivable from the supplied key (typically an ED25519 key was used in an ECDSA alias flow). | Use `PrivateKey.generateECDSA()` (or the equivalent in your SDK). |
| `INVALID_SIGNATURE` | Two-key form (`setKeyWithAlias(key, ecdsaKey)`) was not signed by both the account key and the alias key. | Sign the transaction with both keys before submitting. |
| `ALIAS_ALREADY_ASSIGNED` | The EVM address is already in use by another account (for example, a hollow account previously received funds at this address). | Generate a fresh ECDSA key, or complete the existing hollow account by signing a transaction from it. |
| `ACCOUNT_ID_DOES_NOT_EXIST` | A query referenced an EVM address with no associated account. | Confirm the account was created and the receipt returned `SUCCESS`. |
***
## Get transaction values
| Method | Type | Description |
| -------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------- |
| `getKey()` | Key | Returns the public key on the account |
| `getInitialBalance()` | Hbar | Returns the initial balance of the account |
| `getAutoRenewPeriod()` | Duration | Returns the auto-renew period on the account |
| `getDeclineStakingReward()` | boolean | Returns whether the account declined staking rewards |
| `getStakedNodeId()` | long | Returns the staked node ID |
| `getStakedAccountId()` | AccountId | Returns the staked account ID |
| `getReceiverSignatureRequired()` | boolean | Returns whether the receiver signature is required |
| `getHighVolume()` | boolean | Returns whether this transaction uses [high-volume throttles](/learn/core-concepts/high-volume-entity-creation) |
# Delete an account
Source: https://docs.hedera.com/native/accounts/delete
A transaction that deletes an existing account from the Hedera network. Before deleting an account, the existing HBAR must be transferred to another account. Submitting a transaction to delete an account without assigning a beneficiary via `setTransferAccountId()` will result in a `ACCOUNT_ID_DOES_NOT_EXIST` error. Transfers cannot be made into a deleted account. A record of the deleted account will remain in the ledger until it expires. The expiration of a deleted account can be extended. The account that is being deleted is required to sign the transaction.
**Note**: The `setTransferAccountId()` method is required, regardless of whether the account has a zero balance.
**Transaction Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for the base transaction fee.
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your transaction fee cost.
**Transaction Signing Requirements**
* The account that is being deleted is required to sign the transaction.
### Methods
Method
Type
Description
Requirement
setAccountId(\)
AccountId
The ID of the account to delete.
Required
setTransferAccountId(\)
AccountId
The ID of the account to transfer the remaining funds to.
Required
```java Java theme={null}
//Create the transaction to delete an account
AccountDeleteTransaction transaction = new AccountDeleteTransaction()
.setAccountId(accountId)
.setTransferAccountId(OPERATOR_ID);
//Freeze the transaction for signing, sign with the private key of the account that will be deleted, sign with the operator key and submit to a Hedera network
TransactionResponse txResponse = transaction.freezeWith(client).sign(newKey).execute(client);
//Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
```
```javascript JavaScript theme={null}
//Create the transaction to delete an account
const transaction = await new AccountDeleteTransaction()
.setAccountId(accountId)
.setTransferAccountId(OPERATOR_ID)
.freezeWith(client);
//Sign the transaction with the account key
const signTx = await transaction.sign(accountKey);
//Sign with the client operator private key and submit to a Hedera network
const txResponse = await signTx.execute(client);
//Request the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the transaction consensus status
const transactionStatus = receipt.status;
console.log("The transaction consensus status is " +transactionStatus);
//2.0.5
```
```java Go theme={null}
//Create the transaction to delete an account, freeze the transaction for signing
transaction, err := hedera.NewAccountDeleteTransaction().
SetAccountID(newAccountID).
SetTransferAccountID(operatorAccountID).
FreezeWith(client)
if err != nil {
panic(err)
}
//Sign with the private key of the account that will be deleted, sign with the operator key and submit to a Hedera network
txResponse, err := transaction.Sign(accountKey).Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction consensus status
transactionStatus := receipt.Status
fmt.Printf("The transaction consensus status is %v\n", transactionStatus)
//v2.0.0
```
```rust Rust theme={null}
// Create the transaction to delete an account
let transaction = AccountDeleteTransaction::new()
.account_id(account_id)
.transfer_account_id(operator_id);
// Freeze the transaction for signing, sign with the private key of the account that will be deleted
let tx_response = transaction
.freeze_with(&client)?
.sign(account_key)
.execute(&client)
.await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
## Get transaction values
Method
Type
Description
getAccountId(\)
AccountId
The account to delete
getTransferAccountId(\)
AccountId
The account to transfer the remaining funds to
```java Java theme={null}
//Create the transaction to delete an account
AccountDeleteTransaction transaction = new AccountDeleteTransaction()
.setAccountId(newAccountId)
.setTransferAccountId(OPERATOR_ID);
//Get the account ID from the transaction
AccountId transactionAccountId = transaction.getAccountId()
System.out.println("The account to be deleted in this transaction is " +transactionAccountId)
//v2.0.0
```
```java JavaScript theme={null}
//Create the transaction to delete an account
const transaction = new AccountDeleteTransaction()
.setAccountId(newAccountId)
.setTransferAccountId(OPERATOR_ID);
//Get the account ID from the transaction
const transactionAccountId = transaction.getAccountId()
console.log("The account to be deleted in this transaction is " +transactionAccountId)
```
```java JavaScript theme={null}
//Create the transaction to delete an account
transaction, err := hedera.NewAccountDeleteTransaction().
SetAccountID(newAccountID).
SetTransferAccountID(operatorAccountID)
//Get the account ID from the transaction
transactionAccountId := transaction.GetAccountID()
//v2.0.0
```
# Network Response Messages
Source: https://docs.hedera.com/native/accounts/errors
Network response messages and their descriptions.
| **Errors** | **Description** |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `ACCOUNT_ID_DOES_NOT_EXIST` | The account id passed has not yet been created. |
| `ACCOUNT_UPDATE_FAILED` | The update of the account failed |
| `ACCOUNT_DELETED` | The account has been marked as deleted |
| `INVALID_ACCOUNT_AMOUNTS` | The crypto transfer credit and debit do not sum equal to 0 |
| `INVALID_INITIAL_BALANCE` | Attempt to set the negative initial balance |
| `INVALID_RECEIVE_RECORD_THRESHOLD` | Attempt to set negative receive record threshold |
| `INVALID_SEND_RECORD_THRESHOLD` | Attempt to set negative send record threshold |
| `SETTING_NEGATIVE_ACCOUNT_BALANCE` | Attempting to set negative balance value for the crypto account |
| `TRANSFER_LIST_SIZE_LIMIT_EXCEEDED` | Exceeded the number of accounts (both from and to) allowed for crypto transfer list |
| `TRANSFER_ACCOUNT_SAME_AS_DELETE_ACCOUNT` | Transfer Account should not be same as Account to be deleted |
| `NO_REMAINING_AUTOMATIC_ASSOCIATIONS` | The account has reached the limit on the automatic associations count. |
| `EXISTING_AUTOMATIC_ASSOCIATIONS_EXCEED_GIVEN_LIMIT` | Already existing automatic associations are more than the new maximum automatic associations. |
| `REQUESTED_NUM_AUTOMATIC_ASSOCIATIONS_EXCEEDS_ASSOCIATION_LIMIT` | Cannot set the number of automatic associations for an account more than the maximum allowed token associations tokens.maxPerAccount |
# Get account balance
Source: https://docs.hedera.com/native/accounts/get-balance
A query that returns the account balance for the specified account. Requesting an account balance is currently free of charge. Queries do not change the state of the account or require network consensus. The information is returned from a single node processing the query.
In Services release 0.50, returning token balance from the consensus node was deprecated with HIP-367. This query returns token information by requesting the information from the Hedera Mirror Node APIs via [/api/v1/accounts/\{id}/tokens](https://mainnet.mirrornode.hedera.com/api/v1/docs/#/accounts/listTokenRelationshipByAccountId). Token symbol is not returned in the response.
#### **DEPRECATION NOTICE: `AccountBalanceQuery`**
The `AccountBalanceQuery` is deprecated and will be completely removed in **July 2026**. This is the only SDK method presented on this page and it will no longer function after this date.
A gradual throttle reduction begins in **May 2026**. To avoid rate limiting and future service disruptions, you must migrate to the Mirror Node REST API.
📚 **For the full migration guide, read:** [Migrating from AccountBalanceQuery: What You Need to Know](https://hedera.com/blog/migrating-from-accountbalancequery-what-you-need-to-know)
#### **Recommend Using Mirror Node REST API**
For obtaining account balance and historical balance information, consider using the Mirror Node REST API endpoint [**List Account Balances**](https://docs.hedera.com/api-reference/balances/list-account-balances) which offers several advantages:
* **Cost-effective and scalable:** [Mirror node providers](/operators/mirror-node#mainnet) offer paid plans with a large number of queries included. The Hedera-hosted mirror node offers free queries with specific throttles for testing. While account balance queries via SDK are currently free, this is subject to change in the future.
* **Performance:** Mirror nodes don't burden consensus nodes, allowing them to focus on processing transactions and providing efficient access to historical data without impacting network performance.
* **Historical data:** Mirror nodes store complete transaction history and balance snapshots - ideal for analytics, auditing, and monitoring past activity.
📚 **For more details on querying data, read:** [Querying Data on Hedera: SDK vs Mirror Node REST API](https://hedera.com/blog/querying-data-on-hedera-sdk-vs-mirror-node-rest-api/)
**Query Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for the base transaction fee.
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your query fee cost.
**Query Signing Requirements**
* The client operator private key is required to sign the query request.
### Methods
Method
Type
Description
setAccountId(\)
AccountID
The account ID to return the current balance for.
setContractId(\)
ContractID
The contract ID to return the current balance for.
```java Java theme={null}
//Create the account balance query
AccountBalanceQuery query = new AccountBalanceQuery()
.setAccountId(accountId);
//Sign with client operator private key and submit the query to a Hedera network
AccountBalance accountBalance = query.execute(client);
//Print the balance of hbars
System.out.println("The hbar account balance for this account is " +accountBalance.hbars);
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the account balance query
const query = new AccountBalanceQuery()
.setAccountId(accountId);
//Submit the query to a Hedera network
const accountBalance = await query.execute(client);
//Print the balance of hbars
console.log("The hbar account balance for this account is " +accountBalance.hbars);
//v2.0.7
```
```go Go theme={null}
//Create the account balance query
query := hedera.NewAccountBalanceQuery().
SetAccountID(newAccountId)
//Sign with client operator private key and submit the query to a Hedera network
accountBalance, err := query.Execute(client)
if err != nil {
panic(err)
}
//Print the balance of hbars
fmt.Println("The hbar account balance for this account is ", accountBalance.Hbars.String())
//v2.0.0
```
```rust Rust theme={null}
// Create the query
let query = AccountBalanceQuery::new()
.account_id(account_id);
// Sign with client operator private key and submit to a Hedera network
let account_balance = query.execute(&client).await?;
println!("The account balance is {:?}", account_balance.hbars);
// v0.34.0
```
# Get account info
Source: https://docs.hedera.com/native/accounts/get-info
A query that returns the current state of the account. This query **does not** include the list of records associated with the account. Anyone on the network can request account info for a given account. Queries do not change the state of the account or require network consensus. The information is returned from a single node processing the query.
In Services release 0.50, returning token balance information from the consensus node was deprecated with HIP-367. This query now returns token information by requesting the information from the Hedera Mirror Node APIs via [/api/v1/accounts/\{id}/tokens](https://mainnet.mirrornode.hedera.com/api/v1/docs/#/accounts/listTokenRelationshipByAccountId). Token symbol is not returned in the response.
**Account Properties**
#### **Recommend Using Mirror Node REST API**
For obtaining account information and historical data, consider using the Mirror Node REST API endpoint [**Get Account by Alias, ID, or EVM Address**](https://docs.hedera.com/api-reference/accounts/get-account-by-alias-id-or-evm-address) which offers several advantages:
* **Cost-effective and scalable:** [Mirror node providers](/operators/mirror-node#mainnet) offer paid plans with a large number of queries included. The Hedera-hosted mirror node offers free queries with specific throttles for testing. While some SDK queries are currently free, these are subject to change in the future.
* **Performance:** Mirror nodes don't burden consensus nodes, allowing them to focus on processing transactions and providing efficient access to historical data without impacting network performance.
* **Historical data:** Mirror nodes store complete transaction history and balance snapshots - ideal for analytics, auditing, and monitoring past activity.
📚 ***For more details, please read our [blog post on querying data](https://hedera.com/blog/querying-data-on-hedera-sdk-vs-mirror-node-rest-api).***
**Query Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for the base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your query fee cost
**Query Signing Requirements**
* The client operator private key is required to sign the query request.
### Methods
Method
Type
Requirement
setAccountId(\)
AccountId
Required
\.accountId
AccountId
Optional
\.contractAccountId
String
Optional
\.isDeleted
boolean
Optional
\.key
Key
Optional
\.balance
HBAR
Optional
\.isReceiverSignatureRequired
boolean
Optional
\.ownedNfts
long
Optional
\.maxAutomaticTokenAssociations
int
Optional
\.accountMemo
String
Optional
\.expirationTime
Instant
Optional
\.autoRenewPeriod
Duration
Optional
\.ledgerId
LedgerId
Optional
\.ethereumNonce
long
Optional
\.stakingInfo
StakingInfo
Optional
\.tokenRelationships
Map\
Optional
```java Java theme={null}
//Create the account info query
AccountInfoQuery query = new AccountInfoQuery()
.setAccountId(newAccountId);
//Submit the query to a Hedera network
AccountInfo accountInfo = query.execute(client);
//Print the account key to the console
System.out.println(accountInfo);
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the account info query
const query = new AccountInfoQuery()
.setAccountId(newAccountId);
//Sign with client operator private key and submit the query to a Hedera network
const accountInfo = await query.execute(client);
//Print the account info to the console
console.log(accountInfo);
//v2.0.0
```
```go Go theme={null}
//Create the account info query
query := hedera.NewAccountInfoQuery().
SetAccountID(newAccountId)
//Sign with client operator private key and submit the query to a Hedera network
accountInfo, err := query.Execute(client)
if err != nil {
panic(err)
}
//Print the account info to the console
fmt.Println(accountInfo)
//v2.0.0
```
```rust Rust theme={null}
// Create the account info query
let query = AccountInfoQuery::new()
.account_id(new_account_id);
// Submit the query to a Hedera network
let account_info = query.execute(&client).await?;
// Print the account info to the console
println!("{:?}", account_info);
// v0.34.0
```
# Transfer cryptocurrency
Source: https://docs.hedera.com/native/accounts/transfer
A transaction that transfers HBAR and tokens between Hedera accounts. You can enter multiple transfers in a single transaction. The net value of HBAR between the sending accounts and receiving accounts must equal zero.
For a CryptoTransferTransactionBody:
* Max of 10 balance adjustments in its HBAR transfer list.
* Max of 10 fungible token balance adjustments across all its token transfer list.
* Max of 10 NFT ownership changes across all its token transfer list.
* Max of 20 balance adjustments or NFT ownership changes implied by a transaction (including custom fees).
* If you are transferring a token with custom fees, only two levels of nesting fees are allowed.
* The sending account is responsible to pay for the custom token fees.
**Transaction Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for the base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your transaction fee cost
**Spender Account Allowances**
An account can have [another account](/native/accounts/approve-allowance) spend tokens on its behalf. If the delegated spender account is transacting tokens from the owner account that authorized the allowance, the owner account needs to be specified in the transfer transaction by calling one of the following:
* `addApprovedHbarTransfer()`
* `addApprovedTokenTransfer()`
* `addApprovedNftTransfer()`
* `addApprovedTokenTransferWithDecimals()`
The debiting account is the owner's account when using this feature.
**Note**: The allowance spender must pay the fee for the transaction.
**Transaction Signing Requirements**
* The accounts the tokens are being debited from are required to sign the transaction
* If an authorized spender account is spending on behalf of the account that owns the tokens then the spending account is required to sign
* The transaction fee-paying account is required to sign the transaction
## Constructor
| **Constructor** | **Description** |
| --------------------------- | ------------------------------------------ |
| `new TransferTransaction()` | Initializes the TransferTransaction object |
## Transaction Properties
| **Method** | **Type** | **Requirement** |
| ----------------------------------------------------------------------------------- | ----------------------------- | --------------- |
| `addHbarTransfer()` | AccountId, Hbar | Required |
| `addTokenTransfer()` | TokenId, AccountId, long | Optional |
| `addNftTransfer()` | NftId, AccountId, AccountId | Optional |
| `addTokenTransferWithDecimals()` | TokenId, AccountId, long, int | Optional |
| `addApprovedHbarTransfer()` | AccountId, Hbar | Optional |
| `addApprovedTokenTransfer()` | TokenId, AccountId, long | Optional |
| `addApprovedNftTransfer()` | NftId, AccountId, AccountId | Optional |
| `addApprovedTokenTransferWithDecimals()` | TokenId, AccountId, long, int | Optional |
| `setHighVolume()` | boolean | Optional |
This transaction supports [high-volume entity creation](/learn/core-concepts/high-volume-entity-creation)
(HIP-1313) when the transfer **auto-creates new accounts**. Setting `setHighVolume(true)`
routes the account-creation portion of the transfer through dedicated high-volume throttle
capacity with variable-rate pricing. The transfer portion itself uses standard throttles
regardless of this flag. Always pair this with `setMaxTransactionFee()` to cap your costs.
```java Java theme={null}
// Create a transaction to transfer 1 HBAR
TransferTransaction transaction = new TransferTransaction()
.addHbarTransfer(OPERATOR_ID, new Hbar(-1))
.addHbarTransfer(newAccountId, evmAddress, new Hbar(1));
//Submit the transaction to a Hedera network
TransactionResponse txResponse = transaction.execute(client);
//Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
//Version 2.0.0
```
```javascript JavaScript theme={null}
// Create a transaction to transfer 1 HBAR
const transaction = new TransferTransaction()
.addHbarTransfer(OPERATOR_ID, new Hbar(-1))
.addHbarTransfer(newAccountId, evmAddress, new Hbar(1));
//Submit the transaction to a Hedera network
const txResponse = await transaction.execute(client);
//Request the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the transaction consensus status
const transactionStatus = receipt.status;
console.log("The transaction consensus status is " +transactionStatus.toString());
//v2.0.0
```
```go Go theme={null}
// Create a transaction to transfer 1 HBAR
transaction := hedera.NewTransferTransaction().
AddHbarTransfer(client.GetOperatorAccountID(), hedera.NewHbar(-1)).
AddHbarTransfer(hedera.AccountID{Account: 3}, hedera.NewHbar(1))
//Submit the transaction to a Hedera network
txResponse, err := transaction.Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction consensus status
transactionStatus := receipt.Status
fmt.Printf("The transaction consensus status is %v\n", transactionReceipt.Status)
//Version 2.0.0
```
```rust Rust theme={null}
// Create the transfer transaction
let transaction = TransferTransaction::new()
.hbar_transfer(account_id, Hbar::from(-10))
.hbar_transfer(recipient_id, Hbar::from(10));
// Freeze the transaction for signing, sign with the private key of the account that is sending hbars
let tx_response = transaction
.freeze_with(&client)?
.sign(account_key)
.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
## Get Transaction Values
| **Method** | **Type** | **Description** |
| ------------------------ | -------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| `getHbarTransfers()` | Map\ | Returns the HBAR transfers |
| `getTokenTransfers()` | Map\> | Returns the token transfers |
| `getTokenNftTransfers()` | Map\> | Returns the NFT transfers |
| `getHighVolume()` | boolean | Returns whether this transaction uses [high-volume throttles](/learn/core-concepts/high-volume-entity-creation) |
```java Java theme={null}
// Create a transaction
CryptoTransferTransaction transaction = new CryptoTransferTransaction()
.addSender(OPERATOR_ID, new Hbar(1))
.addRecipient(newAccountId, new Hbar(1));
//Get transfers
List transfers = transaction.getTransfers();
//v2.0.0
```
```javascript JavaScript theme={null}
// Create a transaction
const transaction = new CryptoTransferTransaction()
.addSender(OPERATOR_ID, new Hbar(1))
.addRecipient(newAccountId, new Hbar(1));
//Get transfers
const transfers = transaction.getTransfers();
//v2.0.0
```
```go Go theme={null}
// Create a transaction
transaction := hedera.NewTransferTransaction().
AddHbarTransfer(client.GetOperatorAccountID(), hedera.NewHbar(-1)).
AddHbarTransfer(hedera.AccountID{Account: 3}, hedera.NewHbar(1))
//Get transfers
transfers := transaction.GetTransfers()
//v2.0.0
```
# Update an account
Source: https://docs.hedera.com/native/accounts/update
Modify a Hedera account with AccountUpdateTransaction: change keys, memo, auto-renew period, staking, and max automatic token associations on existing accounts.
A transaction that updates the properties of an existing account. The network will store the latest updates on the account. If you would like to retrieve the state of an account in the past, you can query a mirror node.
**You cannot add an EVM Address from Public Key to an existing account.** The `alias` is set only at account creation via `setECDSAKeyWithAlias()` / `setKeyWithAlias()` (or the equivalent in your SDK). `AccountUpdateTransaction` has no functional alias setter, so it cannot add or change an account's EVM Address from Public Key. (Some SDKs, such as Java, expose a deprecated `setAliasKey` that has no on-wire effect.)
If your existing account does not have an EVM Address from Public Key and you need EVM compatibility, the recommended path is to **create a new ECDSA account with the EVM address set at creation** and migrate assets and state. See [Create an Account](/native/accounts/create) for the recommended pattern.
Rotating keys with `CryptoUpdateTransaction` does **not** update the EVM address. The original EVM Address from Public Key remains tied to the original ECDSA public key.
**Account Properties**
**Transaction Fees**
* The sender pays for the token association fee and the rent for the first auto-renewal period.
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for the base transaction fee.
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate the cost of your transaction fee.
**Transaction Signing Requirements**
* The account key(s) are required to sign the transaction.
* If you are updating the keys on the account, the OLD KEY and NEW KEY must sign.
* If either is a key list, the key list keys are all required to sign.
* If either is a threshold key, the threshold value is required to sign.
* If you do not have the required signatures, the network will throw an `INVALID_SIGNATURE` error.
#### Maximum Auto-Associations and Fees
Accounts have a property, `maxAutoAssociations`, and the property's value determines the maximum number of automatic token associations allowed.
| Property Value | Description |
| :------------: | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `0` | Automatic token associations or token airdrops are not allowed, and the account must be manually associated with a token. This also applies if the value is less than or equal to `usedAutoAssociations`. |
| `-1` | Unlimited automatic token associations are allowed, and this is the default for accounts created via [auto account creation](/learn/core-concepts/accounts/auto-account-creation) and for accounts that began as hollow accounts and are now complete. Accounts with `-1` can receive new tokens without manually associating them. The sender still pays the `maxAutoAssociations` fee and initial rent for each association. |
| `> 0` | If the value is a positive number (number greater than 0), the number of automatic token associations an account can have is limited to that number. |
The sender pays the `maxAutoAssociations` fee and the rent for the first auto-renewal period for the association. This is in addition to the typical transfer fees. This ensures the receiver can receive tokens without association and makes it a smoother transfer process.
Reference: [HIP-904](https://hips.hedera.com/hip/hip-904)
### Methods
| Method | Type | Requirement |
| ------------------------------------------------- | --------- | ----------- |
| `setAccountId()` | AccountId | Required |
| `setKey()` | Key | Optional |
| `setReceiverSignatureRequired()` | Boolean | Optional |
| `setMaxAutomaticTokenAssociations()` | int | Optional |
| `setAccountMemo()` | String | Optional |
| `setAutoRenewPeriod()` | Duration | Optional |
| `setStakedAccountId()` | AccountId | Optional |
| `setStakedNodeId()` | long | Optional |
| `setDeclineStakingReward()` | boolean | Optional |
| `setExpirationTime()` | Instant | Disabled |
```java Java theme={null}
//Create the transaction to update the key on the account
AccountUpdateTransaction transaction = new AccountUpdateTransaction()
.setAccountId(accountId)
.setKey(updateKey);
//Sign the transaction with the old key and new key, submit to a Hedera network
TransactionResponse txResponse = transaction.freezeWith(client).sign(oldKey).sign(newKey).execute(client);
//Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
//Version 2.0.0
```
```javascript JavaScript theme={null}
//Create the transaction to update the key on the account
const transaction = await new AccountUpdateTransaction()
.setAccountId(accountId)
.setKey(updateKey)
.freezeWith(client);
//Sign the transaction with the old key and new key
const signTx = await (await transaction.sign(oldKey)).sign(newKey);
//SIgn the transaction with the client operator private key and submit to a Hedera network
const txResponse = await signTx.execute(client);
//Request the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the transaction consensus status
const transactionStatus = receipt.status;
console.log("The transaction consensus status is " +transactionStatus.toString());
//v2.0.5
```
```go Go theme={null}
//Create the transaction to update the key on the account
transaction, err := hedera.NewAccountUpdateTransaction().
SetAccountID(newAccountId).
SetKey(updateKey.PublicKey()).
FreezeWith(client)
if err != nil {
panic(err)
}
//Sign the transaction with the old key and new key, submit to a Hedera network
txResponse, err := transaction.Sign(newKey).Sign(updateKey).Execute(client)
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction consensus status
transactionStatus := receipt.Status
println("The transaction consensus status is ", transactionStatus)
//Version 2.0.0
```
```rust Rust theme={null}
// Create the transaction to update the key on the account
let transaction = AccountUpdateTransaction::new()
.account_id(account_id)
.key(update_key);
// Sign the transaction with the old key and new key
let tx_response = transaction
.freeze_with(&client)?
.sign(old_key)
.sign(new_key)
.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
## Get transaction values
Return the properties of an account create transaction.
| Method | Type | Description |
| -------------------------------- | --------- | --------------------------------------------------------- |
| `getKey()` | Key | Returns the public key on the account |
| `getInitialBalance()` | Hbar | Returns the initial balance of the account |
| `getReceiverSignatureRequired()` | boolean | Returns whether the receiver signature is required or not |
| `getExpirationTime()` | Instant | Returns the expiration time |
| `getAccountMemo()` | String | Returns the account memo |
| `getDeclineStakingReward()` | boolean | Returns whether or not the account is declining rewards |
| `getStakedNodeId()` | long | Returns the node ID the account is staked to |
| `getStakedAccountId()` | AccountId | Returns the account ID the node is staked to |
| `getAutoRenewPeriod()` | Duration | Returns the auto renew period on the account |
```java Java theme={null}
//Create a transaction
AccountUpdateTransaction transaction = new AccountUpdateTransaction()
.setAccountId(accountId)
.setKey(newKeyUpdate);
//Get the key on the account
Key accountKey = transaction.getKey();
//v2.0.0
```
```javascript JavaScript theme={null}
//Create a transaction
const transaction = new AccountUpdateTransaction()
.setAccountId(accountId)
.setKey(newKeyUpdate);
//Get the key of an account
const accountKey = transaction.getKey();
//v2.0.0
```
```go Go theme={null}
//Create the transaction
transaction, err := hedera.NewAccountUpdateTransaction().
SetAccountID(newAccountId).
SetKey(updateKey.PublicKey())
//Get the key of an account
accountKey := transaction.GetKey()
//v2.0.0
```
# Create a topic
Source: https://docs.hedera.com/native/consensus/create-topic
A transaction that creates a new topic recognized by the Hedera network. The newly generated topic can be referenced by its `topicId`. The `topicId` is used to identify a unique topic to submit messages to. You can obtain the new topic ID by requesting the receipt of the transaction. All messages within a topic are sequenced with respect to one another and are provided a unique sequence number.
**Note**
With the Consensus Node Release [v0.60](/networks/release-notes/services#release-v0.60), you can set an auto renew account ID without the requirement of setting an admin key on the topic.
#### Private topic
You can also create a private topic where only authorized parties can submit messages to that topic. To create a private topic you would need to set the `submitKey` property of the transaction. The `submitKey` value is then shared with the authorized parties and is required to successfully submit messages to the private topic.
#### Topic Properties
| Field | Description |
| ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Admin Key** | Access control for updateTopic/deleteTopic. If no adminKey is specified, anyone can increase the topic's expirationTime with updateTopic, but they cannot use deleteTopic. However, if an adminKey is specified, both updateTopic and deleteTopic can be used. |
| **Submit Key** | Access control for submitMessage. No access control will be performed specified, allowing all message submissions. |
| **Topic Memo** | Store the new topic with a short publicly visible memo. (100 bytes) |
| **Auto Renew Account** |
At the topic's expirationTime, the optional account can be used to extend the lifetime up to a maximum of the autoRenewPeriod or duration/amount that all funds on the account can extend (whichever is the smaller).
Currently, rent is not enforced for topics so no auto-renew payments will be made.
Note: If the developer does not explicitly set autoRenewAccount, the SDK will automatically default to using the transaction fee payer account ID for the auto renew account. This is beneficial in the event an admin key is not set.
|
| **Auto Renew Period** |
The initial lifetime of the topic and the amount of time to attempt to extend the topic's lifetime by automatically at the topic's expirationTime. Currently, rent is not enforced for topics so auto-renew payments will not be made.
NOTE: The minimum period of time is approximately 30 days (2592000 seconds) and the maximum period of time is approximately 92 days (8000001 seconds). Any other value outside of this range will return the following error: AUTORENEW\_DURATION\_NOT\_IN\_RANGE.
|
| **Fee Schedule Key** | (Optional) A key that controls updates and deletions of topic fees. **Must be set at creation; cannot be added later via `updateTopic`**. |
| **Fee Exempt Keys** | (Optional) A list of keys that, if used to sign a message submission, allow the sender to bypass fees. **Can be updated later via `updateTopic`**. |
| **Custom Fees** |
(Optional) A fee structure applied to message submissions for revenue generation. Can be updated later via updateTopic, but must be signed by the Fee Schedule Key. Defines a fixed fee required for each message submission to the topic. This fee can be set in HBAR or HTS fungible tokens and applies when messages are submitted.
|
**Transaction Signing Requirements:**
* If an **Admin Key** is specified, the Admin Key must sign the transaction.
* If no **Admin Key** is specified, the topic is immutable.
* If an **Auto Renew Account** is specified, that account must also sign this transaction.
* If a **Fee Schedule Key** is specified, the Fee Schedule Key must sign the transaction.
* If a Fee Exempt Key List is specified, it contains a list of public keys that are exempt from paying fees when submitting messages to the topic. These keys do not need to sign the transaction.
**Transaction Fees**
* Each **transaction** incurs a **standard Hedera network fee** based on network resource usage.
* If a **custom fee** is set for a topic, users submitting messages must pay this fee in **HBAR or HTS tokens**.
* The **Fee Schedule Key** allows authorized users to update fee structures. If set, it must sign transactions modifying fees.
* Fee exemptions can be granted using the **Fee Exempt Key List**.
* Use the [Hedera Fee Estimator](https://hedera.com/fees) to estimate standard network fees.
#### Methods
| **Method** | **Type** | **Requirements** |
| --------------------------------------- | --------------------- | ---------------- |
| `setAdminKey()` | Key | Optional |
| `setSubmitKey()` | Key | Optional |
| `setTopicMemo()` | String | Optional |
| `setAutoRenewAccountId()` | AccountId | Optional |
| `setAutoRenewPeriod()` | Duration | Optional |
| `setFeeScheduleKey()` | Key | Optional |
| `setFeeExemptKeys()` | List\ | Optional |
| `setCustomFees()` | List\ | Optional |
| `addCustomFee()` | CustomFixedFee | Optional |
| `addFeeExemptKey()` | Key | Optional |
| `setHighVolume()` | boolean | Optional |
```java Java theme={null}
//Create the transaction
TopicCreateTransaction transaction = new TopicCreateTransaction()
.setFeeScheduleKey(feeScheduleKey)
.setFeeExemptKeys(feeExemptKeys)
.setCustomFees(customFees);
//Sign with the client operator private key and submit the transaction to a Hedera network
TransactionResponse txResponse = transaction.execute(client);
//Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the topic ID
TopicId newTopicId = receipt.topicId;
System.out.println("The new topic ID is " + newTopicId);
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = new TopicCreateTransaction()
.setFeeScheduleKey(feeScheduleKey)
.setFeeExemptKeys(feeExemptKeys)
.setCustomFees(customFees);
//Sign with the client operator private key and submit the transaction to a Hedera network
const txResponse = await transaction.execute(client);
//Request the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the topic ID
const newTopicId = receipt.topicId;
console.log("The new topic ID is " + newTopicId);
//v2.0.0
```
```go Go theme={null}
//Create the transaction
transaction := hedera.NewTopicCreateTransaction().
.setFeeScheduleKey(feeScheduleKey).
.setFeeExemptKeys(feeExemptKeys).
.setCustomFees(customFees)
//Sign with the client operator private key and submit the transaction to a Hedera network
txResponse, err := transaction.Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
transactionReceipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the topic ID
newTopicID := *transactionReceipt.TopicID
fmt.Printf("The new topic ID is %v\n", newTopicID)
//v2.0.0
```
```rust Rust theme={null}
// Create a new topic
let transaction = TopicCreateTransaction::new()
.topic_memo("My topic memo")
.admin_key(admin_key)
.submit_key(submit_key)
.auto_renew_period(Duration::hours(24 * 30)); // 30 days
// Sign with the client operator private key and submit to a Hedera network
let tx_response = transaction.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the topic ID
let topic_id = receipt.topic_id.unwrap();
println!("The new topic ID is {:?}", topic_id);
// v0.34.0
```
## Get transaction values
| **Method** | **Type** | **Requirements** |
| ------------------------- | --------- | ---------------- |
| `getAdminKey(adminKey)` | Key | Optional |
| `getSubmitKey(submitKey)` | Key | Optional |
| `getTopicMemo(memo)` | String | Optional |
| `getAutoRenewAccountId()` | AccountId | Required |
| `getAutoRenewPeriod()` | Duration | Required |
| `getFeeScheduleKey()` | Key | Optional |
| `getFeeExemptKeys()` | List | Optional |
| `getCustomFees()` | List | Optional |
```java Java theme={null}
//Create the transaction
TopicCreateTransaction transaction = new TopicCreateTransaction()
.setFeeScheduleKey(feeScheduleKey);
//Get the fee schedule key from the transaction
Key getFeeScheduleKey = transaction.getFeeScheduleKey();
//V2.0.0
```
```javascript JavaScript theme={null}
const transaction = new TopicCreateTransaction()
.setFeeScheduleKey(feeScheduleKey);
//Get the fee schedule key from the transaction
const feeScheduleKey = transaction.getFeeScheduleKey();
//V2.0.0
```
```java Go theme={null}
transaction := hedera.NewTopicCreateTransaction().
SetFeeScheduleKey(feeScheduleKey)
getFeeScheduleKey := transaction.GetFeeScheduleKey()
//V2.0.0
```
This transaction supports [high-volume entity creation](/learn/core-concepts/high-volume-entity-creation)
(HIP-1313). Setting `setHighVolume(true)` routes the transaction through dedicated
high-volume throttle capacity with variable-rate pricing. Always pair this with
`setMaxTransactionFee()` to cap your costs.
# Delete a topic
Source: https://docs.hedera.com/native/consensus/delete-topic
A transaction that deletes a topic from the Hedera network. Once a topic is deleted, the topic cannot be recovered to receive messages and all submitMessage calls will fail. Older messages can still be accessed, even after the topic is deleted, via the mirror node.
**Transaction Signing Requirements**
* If the adminKey was set upon the creation of the topic, the adminKey is required to sign to successfully delete the topic.
* If no adminKey was set upon the creating of the topic, you cannot delete the topic and will receive an UNAUTHORIZED error.
#### Methods
Method
Type
Description
Requirement
setTopicId(\)
TopicId
The ID of the topic to delete
Required
```java Java theme={null}
//Create the transaction
TopicDeleteTransaction transaction = new TopicDeleteTransaction()
.setTopicId(newTopicId);
//Sign the transaction with the admin key, sign with the client operator and submit the transaction to a Hedera network, get the transaction ID
TransactionResponse txResponse = transaction.freezeWith(client).sign(adminKey).execute(client);
//Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
//V2.0.0
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = await new TopicDeleteTransaction()
.setTopicId(newTopicId)
.freezeWith(client);
//Sign the transaction with the admin key
const signTx = await transaction.sign(adminKey);
//Sign with the client operator private key and submit to a Hedera network
const txResponse = await signTx.execute(client);
//Request the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the transaction consensus status
const transactionStatus = receipt.status;
console.log("The transaction consensus status is " +transactionStatus);
//v2.0.5
```
```java Go theme={null}
//Create the transaction and freeze the transaction to prepare for signing
transaction := hedera.NewTopicDeleteTransaction().
SetTopicID(topicID).
FreezeWith(client)
//Sign the transaction with the admin key, sign with the client operator and submit the transaction to a Hedera network, get the transaction ID
txResponse, err := transaction.Sign(adminKey).Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
receipt, err = txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction consensus status
transactionStatus := receipt.Status
fmt.Printf("The transaction consensus status is %v\n", transactionStatus)
//v2.0.0
```
```rust Rust theme={null}
// Create the transaction to delete the topic
let transaction = TopicDeleteTransaction::new()
.topic_id(topic_id);
// Sign the transaction with the admin key
let tx_response = transaction
.freeze_with(&client)?
.sign(admin_key)
.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
## Get transaction values
| Method | Type | Description | Requirement |
| ----------------------- | ------- | ----------------------------- | ----------- |
| `getTopicId()` | TopicId | The ID of the topic to delete | Required |
```java Java theme={null}
//Create the transaction
TopicDeleteTransaction transaction = new TopicDeleteTransaction()
.setTopicId(newTopicId);
//Get topic ID
TopicId getTopicId = transaction.getTopicId();
//v2.0.0
```
```java JavaScript theme={null}
//Create the transaction
const transaction = new TopicDeleteTransaction()
.setTopicId(newTopicId);
//Get topic ID
const getTopicId = transaction.getTopicId();
//v2.0.0
```
```java Go theme={null}
//Create the transaction
transaction := hedera.NewTopicDeleteTransaction().
SetTopicID(topicID)
//Get topic ID
getTopicId := transaction.GetTopicID()
//v2.0.0
```
# Network Response
Source: https://docs.hedera.com/native/consensus/errors
Network response messages and their descriptions.
| Network Response Messages | Description |
| --------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INVALID_TOPIC_ID` | The Topic ID specified is not in the system. |
| `TOPIC_DELETED` | The Topic has been deleted |
| `INVALID_TOPIC_EXPIRATION_TIME` | The expiration time set for the topic is not valid |
| `INVALID_TOPIC_ADMIN_KEY` | The `adminKey` associated with the topic is not correct |
| `INVALID_TOPIC_SUBMIT_KEY` | The `submitKey` associated with the topic is not correct |
| `UNAUTHORIZED` | An attempted operation was not authorized (ie - a deleteTopic for a topic with no `adminKey`) |
| `INVALID_TOPIC_MESSAGE` | A `ConsensusService` message is empty |
| `INVALID_AUTORENEW_ACCOUNT` | The `autoRenewAccount` specified is not a valid, active account. |
| `AUTORENEW_ACCOUNT_NOT_ALLOWED` | An admin key was not specified on the topic, so there must not be an autorenew account. |
| `AUTORENEW_ACCOUNT_SIGNATURE_MISSING` | The `autoRenewAccount` didn't sign the transaction. |
| `INVALID_CHUNK_NUMBER` | Chunk number must be from 1 to total (chunks) inclusive |
| `InvalidChunkTransactionId` | For every chunk, the payer account that is part of initialTransactionID must match the Payer Account of this transaction. The entire initialTransactionID should match the transactionID of the first chunk, but this is not checked or enforced by Hedera except when the chunk number is 1. |
| `TopicExpired` | The topic has expired, was not automatically renewed, and is in a 7 day grace period before the topic will be deleted unrecoverably. This error response code will not be returned until autoRenew functionality is supported by HAPI. |
| `MAX_ENTRIES_FOR_FEE_EXEMPT_KEY_LIST_EXCEEDED` | The provided fee exempt key list size exceeded the limit. |
| `FEE_EXEMPT_KEY_LIST_CONTAINS_DUPLICATED_KEYS` | The provided fee exempt key list contains duplicated keys. |
| **`INVALID_KEY_IN_FEE_EXEMPT_KEY_LIST`** | The provided fee exempt key list contains an invalid key. |
| **`INVALID_FEE_SCHEDULE_KEY`** | The provided fee schedule key contains an invalid key. |
| **`FEE_SCHEDULE_KEY_CANNOT_BE_UPDATED`** | If a fee schedule key is not set when creating a topic, it cannot be added on update. |
| **`FEE_SCHEDULE_KEY_NOT_SET`** | If the topic's custom fees are updated, the topic must have a fee schedule key. |
| **`MAX_CUSTOM_FEE_LIMIT_EXCEEDED`** | The fee amount exceeds the amount that the payer is willing to pay. |
| **`NO_VALID_MAX_CUSTOM_FEE`** | There are no corresponding custom fees. |
| **`INVALID_MAX_CUSTOM_FEES`** | The provided list contains invalid max custom fees. |
| **`DUPLICATE_DENOMINATION_IN_MAX_CUSTOM_FEE_LIST`** | The provided max custom fee list contains fees with duplicate denominations. |
| **`DUPLICATE_ACCOUNT_ID_IN_MAX_CUSTOM_FEE_LIST`** | The provided max custom fee list contains fees with duplicate account IDs. |
| **`MAX_CUSTOM_FEES_IS_NOT_SUPPORTED`** | Max custom fees list is not supported for this operation. |
# Get topic info
Source: https://docs.hedera.com/native/consensus/get-info
Topic info returns the following values for a topic. Queries do not change the state of the topic or require network consensus. The information is returned from a single node processing the query.
**Topic Info Response:**
| **Field** | **Description** |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Topic ID** | The ID of the topic |
| **Admin Key** | Access control for update/delete of the topic. Null if there is no key. |
| **Submit Key** | Access control for ConsensusService.submitMessage. Null if there is no key. |
| **Sequence Number** | Current sequence number (starting at 1 for the first submitMessage) of messages on the topic. |
| **Running Hash** | SHA-384 running hash |
| **Expiration Time** | Effective consensus timestamp at (and after) which submitMessage calls will no longer succeed on the topic and the topic will expire and be marked as deleted. |
| **Topic Memo** | Short publicly visible memo about the topic. No guarantee of uniqueness. |
| **Auto Renew Period** | The lifetime of the topic and the amount of time to extend the topic's lifetime by |
| **Auto Renew Account** | Null if there is no autoRenewAccount. |
| **Ledger ID** | The ID of the network the response came from. See [HIP-198](https://hips.hedera.com/hip/hip-198). |
| **Fee Schedule Key** | The key that is authorized to modify the fee structure for submitting messages to this topic. If present, this key must be used to update the topic's fee settings. If absent, the topic's fee structure cannot be changed. |
| **Fee Exempt Key** | A list of accounts that do not have to pay a fee when submitting messages to the topic. If this field is present, it means certain accounts are allowed to submit messages for free. If absent, no exemptions exist. |
| **Fee Schedule** | The current fee structure that applies to message submissions for this topic. It specifies the amount charged per message, which can be denominated in HBAR or a chosen fungible token. If this field is present, the topic enforces fees for message submissions; if absent, message submission is free. |
**Query Signing Requirements**
* The client operator private key is required to sign the query request
**Query Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for the base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your query fee cost
```java Java theme={null}
//Create the account info query
TopicInfoQuery query = new TopicInfoQuery()
.setTopicId(newTopicId);
//Submit the query to a Hedera network
TopicInfo info = query.execute(client);
//Retrieve additional HIP-991 fields
Key feeScheduleKey = info.getFeeScheduleKey();
List customFees = info.getCustomFees();
List feeExemptKeys = info.getFeeExemptKeyList();
//Print the account key to the console
System.out.println(info);
System.out.println("Fee Schedule Key: " + feeScheduleKey);
System.out.println("Custom Fees: " + customFees);
System.out.println("Fee Exempt Key List: " + feeExemptKeys);
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the account info query
const query = new TopicInfoQuery()
.setTopicId(newTopicId);
//Submit the query to a Hedera network
const info = await query.execute(client);
//Retrieve additional HIP-991 fields
const feeScheduleKey = info.feeScheduleKey;
const customFees = info.customFees;
const feeExemptKeys = info.feeExemptKeyList;
//Print the account key to the console
console.log(info);
console.log("Fee Schedule Key:", feeScheduleKey);
console.log("Custom Fees:", customFees);
console.log("Fee Exempt Key List:", feeExemptKeys);
//v2.0.0
```
```go Go theme={null}
//Create the account info query
query, err := hedera.NewTopicInfoQuery().
SetTopicID(topicID)
//Submit the query to a Hedera network
info, err := query.Execute(client)
if err != nil {
panic(err)
}
//Retrieve additional HIP-991 fields
feeScheduleKey := info.GetFeeScheduleKey()
customFees := info.GetCustomFees()
feeExemptKeys := info.GetFeeExemptKeyList()
//Print the account key to the console
println(info)
fmt.Println("Fee Schedule Key:", feeScheduleKey)
fmt.Println("Custom Fees:", customFees)
fmt.Println("Fee Exempt Key List:", feeExemptKeys)
//v2.0.0
```
```rust Rust theme={null}
// Create the topic info query
let query = TopicInfoQuery::new()
.topic_id(topic_id);
// Submit the query to a Hedera network
let topic_info = query.execute(&client).await?;
// Print the topic info to the console
println!("{:?}", topic_info);
// v0.34.0
```
# Get topic messages
Source: https://docs.hedera.com/native/consensus/get-message
Subscribe to a topic ID's messages from a mirror node. You will receive all messages for the specified topic or within the defined start and end time.
**Query Fees**
* The SDK uses the [Hedera Consensus Service gRPC APIs](/reference/hcs-api) provided by the mirror node to perform this function for free.
### Methods
| Method | Type | Description | Requirement |
| ---------------------------- | ------------------ | --------------------------------------------------- | ----------- |
| `setTopicId()` | TopicId | The topic ID to subscribe to | Required |
| `setStartTime()` | Instant | The time to start subscribing to a topic's messages | Optional |
| `setEndTime()` | Instant | The time to stop subscribing to a topic's messages | Optional |
| `setLimit()` | long | The number of messages to return | Optional |
| `subscribe( | Required |
```java Java theme={null}
//Create the query
new TopicMessageQuery()
.setTopicId(newTopicId)
.subscribe(client, topicMessage -> {
System.out.println("at " + topicMessage.consensusTimestamp + " ( seq = " + topicMessage.sequenceNumber + " ) received topic message of " + topicMessage.contents.length + " bytes");
});
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the query
new TopicMessageQuery()
.setTopicId(topicId)
.setStartTime(0)
.subscribe(
client,
(message) => console.log(Buffer.from(message.contents, "utf8").toString())
);
//v2.0.0
```
```java Go theme={null}
//Create the query
_, err = hedera.NewTopicMessageQuery().
SetTopicID(topicID).
Subscribe(client, func(message hedera.TopicMessage) {
if string(message.Contents) == content {
wait = false
}
})
if err != nil {
panic(err)
}
//v2.0.0
```
# Submit a message
Source: https://docs.hedera.com/native/consensus/submit-message
Submit a topic message to Hedera Consensus Service, configure max chunks and chunk size, and set custom fee limits when paying HCS topic fees.
A transaction that submits a topic message to the Hedera network. To access the messages submitted to a topic ID, subscribe to the topic via a mirror node. The mirror node will publish the ordered messages to subscribers. Once the transaction is successfully executed, the receipt of the transaction will include the topic's updated sequence number and topic running hash.
## **Max Chunks**
The **max chunks** setting defines the maximum number of chunks into which a given message can be split. The default value is **20 chunks**, meaning a message can consist of up to 20 chunks by default. This value can be modified using the `setMaxChunks` method.
#### **Max Chunk Size**
🚨 **NOTE:** Max size of an HCS message: 1024 bytes (1 kb).
The **max chunk size** refers to the maximum size (in bytes) of each individual chunk of a message. By default, the max chunk size is **1024 bytes (1 KB)**. This value can be modified using the `setChunkSize` method.
## **Custom Fee Payment**
If a topic has custom fees enabled, users submitting messages must pay the required fee in **HBAR or HTS fungible tokens**. If `setCustomFeeLimits` is not specified in the transaction, the user would need to pay any fee associated with that topic ID. The transaction will only fail if the user does not have sufficient assets to cover the fee.
**Recommendation:** To avoid unexpected fees, it is strongly recommended to use `setCustomFeeLimits` when submitting a message. This ensures that only the intended fee structure is applied, providing a safeguard against unintended charges.
```java theme={null}
TopicMessageSubmitTransaction()
.setTopicId()
.setMessage()
.setCustomFeeLimits() // Ensure this covers the required amount
.execute(client);
```
## **Transaction Signing Requirements**
* Anyone can submit a message to a public topic.
* The `submitKey` is required to sign the transaction for a private topic.
## **Transaction Fees**
* Each transaction incurs a standard Hedera network fee based on network resource usage.
* If a custom fee is set for a topic, users submitting messages must pay this fee in HBAR or HTS tokens.
* The Fee Schedule Key allows authorized users to update fee structures. If set, it must sign transactions modifying fees.
* If the topic has custom fees, the sender must have sufficient balance to cover the fees unless they are exempt via the Fee Exempt Key List. It is recommended to use `setCustomFeeLimits` on the `TopicMessageSubmitTransaction` to ensure the expected fee structure is applied and avoid unexpected transaction failures due to insufficient funds.
* If you submit a message to a topic with a custom fee, the cost changes from the baseline $0.0001 USD to roughly $0.05 USD per `TopicMessageSubmitTransaction`.
* Use the [query fees table](/networks/fees#consensus-service) for the base transaction fee and the [Hedera Fee Estimator](https://hedera.com/fees) to estimate standard network fees.
## Methods
Method
Type
Description
Requirement
setTopicId(\)
TopicId
The topic ID to submit the message to
Required
setMessage(\)
String
The message in a String format
Optional
setMessage(\)
byte \[ ]
The message in a byte array format
Optional
setMessage(\)
ByteString
The message in a ByteString format
Optional
setChunkSize()
int
The max size of individual chunk for a given message. Default: 1024
bytes
Optional
setMaxChunks()
int
The max number of chunks a given message can be split into. Default: 20
Optional
setCustomFeeLimits()
List\
The maximum custom fees the sender is willing to pay
Optional
addCustomFeeLimit()
CustomFeeLimit
Adds a custom fee limit
Optional
```java Java highlight={18} theme={null}
//Create the transaction
TopicMessageSubmitTransaction transaction = new TopicMessageSubmitTransaction()
.setTopicId(newTopicId)
.setMessage("hello, HCS! ")
.setCustomFeeLimits(maxCustomFees); // Set custom fee limits if applicable
//Sign with the client operator key and submit transaction to a Hedera network, get transaction ID
TransactionResponse txResponse = transaction.execute(client);
//Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
//v2.0.0
```
```javascript JavaScript theme={null}
// Create the transaction
const transaction = await new TopicMessageSubmitTransaction()
.setTopicId(newTopicId)
.setMessage("Hello, HCS!")
.setCustomFeeLimits(maxCustomFees); // Set custom fee limits if applicable
// Execute transaction
const txResponse = await transaction.execute(client);
// Request the receipt
const receipt = await txResponse.getReceipt(client);
// Get the transaction consensus status
console.log("Transaction Status:", receipt.status);
//v2.0.0
```
```java Go theme={null}
//Create the transaction
transaction := hedera.NewTopicSubmitTransaction().
SetTopicID(topicID).
SetMessage([]byte(content)).
SetCustomFeeLimits(maxCustomFees) // Set custom fee limits if applicable
//Sign with the client operator private key and submit the transaction to a Hedera network
txResponse, err := transaction.Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
transactionReceipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction consensus status
transactionStatus := receipt.Status
fmt.Printf("The transaction consensus status is %v\n", transactionStatus)
//v2.0.0
```
```rust Rust theme={null}
// Create the transaction
let transaction = TopicMessageSubmitTransaction::new()
.topic_id(topic_id)
.message("Hello, HCS!")
.custom_fee_limits(max_custom_fees); // Set custom fee limits if applicable
// Sign with the client operator key and submit to a Hedera network
let tx_response = transaction.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
## Get transaction values
| Method | Type | Description |
| ---------------------- | ---------- | ------------------------------------------------ |
| `getTopicId()` | TopicId | The topic ID to submit the message to |
| `getMessage()` | ByteString | The message being submitted |
| `getCustomFeeLimits()` | Fee\[] | Extract the custom fee limits of the transaction |
```java Java theme={null}
//Create the transaction
TopicMessageSubmitTransaction transaction = new TopicMessageSubmitTransaction()
.setTopicId(newTopicId)
.setMessage("hello, HCS! ");
//Get the transaction message
ByteString getMessage = transaction.getMessage();
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = await new TopicMessageSubmitTransaction()
.setTopicId(newTopicId)
.setMessage("hello, HCS! ");
//Get the transaction message
const getMessage = transaction.getMessage();
//v2.0.0
```
```go Go theme={null}
//Create the transaction
transaction := hedera.NewTopicSubmitTransaction().
SetTopicID(topicID).
SetMessage([]byte(content))
//Get the transaction message
getMessage := transaction.GetMessage()
//v2.0.0
```
# Update a topic
Source: https://docs.hedera.com/native/consensus/update-topic
A transaction that updates the properties of an existing topic. This includes the topic memo, admin key, submit key, auto-renew account, auto-renew period and fee-related fields.
#### Topic Properties
Field
Description
Topic ID
Update the topic ID
Admin Key
Set a new admin key that authorizes update topic and delete topic transactions.
Submit Key
Set a new submit key for a topic that authorizes sending messages to this topic.
Topic Memo
Set a new short publicly visible memo on the new topic and is stored with the topic. (100 bytes)
Auto Renew Account
Set a new auto-renew account ID for this topic. Currently, rent is not enforced for topics so auto-renew payments will not be made.
Auto Renew Period
Set a new auto-renew period for this topic. Currently, rent is not enforced for topics so auto-renew payments will not be made.
NOTE: The minimum period of time is approximately 30 days (2592000 seconds) and the maximum period of time is approximately 92 days (8000001 seconds). Any other value outside of this range will return the following error: AUTORENEW\_DURATION\_NOT\_IN\_RANGE.
Fee Schedule Key
(Optional) A key that controls updates and deletions of topic fees. Must be set at creation; cannot be added later via updateTopic.
Fee Exempt Keys
(Optional) A list of keys that, if used to sign a message submission, allow the sender to bypass fees. Can be updated later via updateTopic.
Custom Fees
(Optional) A fee structure applied to message submissions for revenue generation. Can be updated later via updateTopic, but must be signed by the Fee Schedule Key. Defines a fixed fee required for each message submission to the topic. This fee can be set in HBAR or HTS fungible tokens and applies when messages are submitted.
**Transaction Signing Requirements**
* If an admin key is updated, the transaction must be signed by the pre-update admin key and post-update admin key.
* If the admin key was set during the creation of the topic, the admin key must sign the transaction to update any of the topic's properties.
* If no `adminKey` was defined during the creation of the topic, you can only extend the expirationTime.
* If a `TopicUpdateTransaction` updates the fee schedule, the Fee Schedule Key must sign the transaction. If the Fee Schedule Key is being updated, both the existing (old) and the new Fee Schedule Key must sign the transaction.
**Transaction Fees**
* Each **transaction** incurs a **standard Hedera network fee** based on network resource usage.
* If a **custom fee** is set for a topic, users submitting messages must pay this fee in **HBAR or HTS tokens**.
* The **Fee Schedule Key** allows authorized users to update fee structures. If set, it must sign transactions modifying fees.
* Fee exemptions can be granted using the **Fee Exempt Key List**.
* Use the [Hedera Fee Estimator](https://hedera.com/fees) to estimate standard network fees.
#### Methods
Method
Type
Requirements
setTopicId(\)
TopicId
Required
setAdminKey(\)
Key
Optional
setSubmitKey(\)
Key
Optional
setExpirationTime(\)
Instant
Optional
setTopicMemo(\)
String
Optional
setAutoRenewAccountId(\)
AccountId
Optional
setAutoRenewPeriod(\)
Duration
Optional
setFeeScheduleKey()
Key
Optional
setFeeExemptKeys()
List\
Optional
setCustomFees()
List\
Optional
addCustomFee()
CustomFixedFee
Optional
addFeeExemptKey()
Key
Optional
clearAdminKey()
Optional
clearSubmitKey()
Optional
clearTopicMemo()
Optional
clearAutoRenewAccountId()
Optional
```java Java theme={null}
// Update a topic to set new custom fees
TopicUpdateTransaction transaction = new TopicUpdateTransaction()
.setTopicId(topicId) // Set the topic ID to update
.setCustomFees(newCustomFees) // Set the new list of custom fees
.freezeWith(client) // Freeze the transaction
// Sign with the Fee Schedule Key to authorize fee changes
.sign(feeScheduleKey);
// Submit the transaction to the Hedera network
TransactionResponse txResponse = transaction.execute(client);
// Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
// Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " + transactionStatus);
// v2.0.0
```
```javascript JavaScript theme={null}
// Update a topic with new custom fees
const transaction = await new TopicUpdateTransaction()
.setTopicId(topicId)
.setCustomFees(newCustomFees)
.freezeWith(client)
.sign(feeScheduleKey);
// Sign with the Fee Schedule Key to authorize fee changes
const signTx = await transaction.sign(feeScheduleKey);
// Submit the transaction to the Hedera network
const txResponse = await signTx.execute(client);
// Request the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the transaction consensus status
const transactionStatus = receipt.status;
console.log("The transaction consensus status is " +transactionStatus);
//v2.0.0
```
```java Go theme={null}
//Create the transaction
transaction := hedera.NewTopicUpdateTransaction().
SetTopicId(topicId).
SetFeeScheduleKey(newFeeScheduleKey).
SetFeeExemptKeys(newFeeExemptKeys).
SetCustomFees(newCustomFees)
// Sign with the Fee Schedule Key to authorize fee changes
txResponse, err := transaction.Sign(feeScheduleKey).Execute(client)
if err != nil {
panic(err)
}
// Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
// Get the transaction consensus status
transactionStatus := receipt.Status
fmt.Printf("Transaction Status: %v\n", transactionStatus)
//v2.0.0
```
```rust Rust theme={null}
// Create the transaction to update the topic
let transaction = TopicUpdateTransaction::new()
.topic_id(topic_id)
.topic_memo("Updated topic memo")
.admin_key(new_admin_key)
.submit_key(new_submit_key)
.auto_renew_period(Duration::hours(24 * 30)); // 30 days
// Sign the transaction with the admin key
let tx_response = transaction
.freeze_with(&client)?
.sign(admin_key)
.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
## Get transaction values
Method
Type
Requirements
getTopicId()
TopicId
Required
getAdminKey()
Key
Optional
getSubmitKey()
Key
Optional
getTopicMemo()
String
Optional
getAutoRenewAccountId()
AccountId
Required
getAutoRenewPeriod()
Duration
Required
getFeeScheduleKey()
Key
Optional
getFeeExemptKeys()
List
Optional
getCustomFees()
List
Optional
```java Java theme={null}
//Create a transaction to add a submit key
TopicUpdateTransaction transaction = new TopicUpdateTransaction()
.setSubmitKey(submitKey);
//Get submit key
transaction.getSubmitKey()
//v2.0.0
```
```javascript JavaScript theme={null}
//Create a transaction to add a submit key
const transaction = new TopicUpdateTransaction()
.setSubmitKey(submitKey);
//Get submit key
transaction.getSubmitKey()
//v2.0.0
```
```java Go theme={null}
//Create the transaction
transaction := hedera.NewTopicUpdateTransaction()
SetSubmitKey()
transaction := transaction.GetSubmitKey()
//v2.0.0
```
# Estimating Fees with the SDK
Source: https://docs.hedera.com/native/fees/fee-estimation
Use FeeEstimateQuery to estimate transaction fees before submission, gate spending, and simulate high-volume pricing.
`FeeEstimateQuery` lets you estimate the cost of a transaction before submitting it. Use it to gate spending against a budget, surface fee previews to users, or simulate execution under high-volume congestion.
The query returns a structured breakdown of node, network, and service fees in tinycents (USD × 10⁻¹⁰). The same calculation runs on the consensus node at execution time, so the estimate reflects what you'll be charged — modulo the live HBAR exchange rate at consensus.
## Basic Usage
Freeze the transaction first, then pass it to `FeeEstimateQuery`. Either call the query directly or use the `estimateFee()` convenience method on the transaction.
```java Java theme={null}
// Freeze the transaction first — the body must be finalized before estimating
var transaction = new AccountCreateTransaction()
.setKeyWithoutAlias(newKey)
.setInitialBalance(new Hbar(1))
.freezeWith(client);
// Option 1: FeeEstimateQuery
FeeEstimateResponse response = new FeeEstimateQuery()
.setTransaction(transaction)
.setMode(FeeEstimateMode.INTRINSIC) // optional — INTRINSIC is the default
.execute(client);
// or option 2: Convenience method
// FeeEstimateResponse response = transaction.estimateFee().execute(client);
```
```javascript JavaScript theme={null}
const transaction = await new AccountCreateTransaction()
.setKeyWithoutAlias(newKey)
.setInitialBalance(new Hbar(1))
.freezeWith(client);
const response = await new FeeEstimateQuery()
.setTransaction(transaction)
.setMode(FeeEstimateMode.INTRINSIC)
.execute(client);
// or: const response = await transaction.estimateFee().execute(client);
```
```go Go theme={null}
tx, _ := hiero.NewAccountCreateTransaction().
SetKeyWithoutAlias(newKey.PublicKey()).
SetInitialBalance(hiero.NewHbar(1)).
FreezeWith(client)
response, err := hiero.NewFeeEstimateQuery().
SetTransaction(tx).
SetMode(hiero.FeeEstimateModeIntrinsic).
Execute(client)
// or: response, err := tx.EstimateFee().Execute(client)
```
## Estimation Modes
| Mode | Behavior |
| --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `INTRINSIC` (default) | Estimates based on the transaction's inherent properties (size, signatures, keys). Fast and stateless. |
| `STATE` | Estimates using the mirror node's latest known state (e.g., checks if accounts exist, includes state-dependent extras). Required for high-volume pricing simulation. |
## Response Structure
The response contains a structured breakdown of the fee components.
```json theme={null}
{
"high_volume_multiplier": 1,
"network": { "multiplier": 3, "subtotal": },
"node": {
"base": ,
"extras": [
{
"name": "Signatures",
"included": 1,
"count": 2,
"charged": 1,
"fee_per_unit": ,
"subtotal":
}
]
},
"service": { "base": , "extras": [] },
"total":
}
```
The components total according to:
```text theme={null}
node.subtotal = node.base + sum(node.extras[].subtotal)
network.subtotal = node.subtotal × network.multiplier
total = node.subtotal + network.subtotal + service.subtotal
```
## End-to-End: Estimate, Gate, Execute
A common pattern: estimate the fee, check against a budget, then execute only if the estimate is acceptable.
```java Java theme={null}
var transaction = new AccountCreateTransaction()
.setKeyWithoutAlias(newKey)
.setInitialBalance(new Hbar(1))
.setMaxTransactionFee(new Hbar(10)) // set before freezing
.freezeWith(client);
FeeEstimateResponse estimate = transaction.estimateFee().execute(client);
// Gate on budget — $1.50 = 15,000,000,000 tinycents
if (estimate.getTotal() > 15_000_000_000L) {
throw new RuntimeException("Fee estimate exceeds budget: " + estimate.getTotal());
}
var record = transaction
.execute(client)
.getRecord(client);
```
```javascript JavaScript theme={null}
const transaction = await new AccountCreateTransaction()
.setKeyWithoutAlias(newKey)
.setInitialBalance(new Hbar(1))
.setMaxTransactionFee(new Hbar(10)) // set before freezing
.freezeWith(client);
const estimate = await transaction.estimateFee().execute(client);
if (estimate.total.toBigInt() > 15_000_000_000n) {
throw new Error(`Fee estimate exceeds budget: ${estimate.total}`);
}
const record = await (await transaction.execute(client)).getRecord(client);
```
```go Go theme={null}
tx, _ := hiero.NewAccountCreateTransaction().
SetKeyWithoutAlias(newKey.PublicKey()).
SetInitialBalance(hiero.NewHbar(1)).
SetMaxTransactionFee(hiero.NewHbar(10)). // set before freezing
FreezeWith(client)
estimate, _ := tx.EstimateFee().Execute(client)
if estimate.Total > 15_000_000_000 {
panic(fmt.Sprintf("fee estimate exceeds budget: %d", estimate.Total))
}
resp, _ := tx.Execute(client)
record, _ := resp.GetRecord(client)
```
## High-Volume Pricing Simulation
For entity-creation transactions opted into the high-volume lane via `setHighVolume(true)`, the network may apply a fee multiplier under congestion. To simulate this before submitting, use `setHighVolumeThrottle()` on `FeeEstimateQuery` with `STATE` mode.
```java Java theme={null}
FeeEstimateResponse hvResponse = new FeeEstimateQuery()
.setTransaction(
new AccountCreateTransaction()
.setKeyWithoutAlias(newKey)
.setHighVolume(true)
.freezeWith(client)
)
.setMode(FeeEstimateMode.STATE)
.setHighVolumeThrottle(5000) // simulate 50% utilization
.execute(client);
// high_volume_multiplier uses 1-based scale: 1 = 1×, 4 = 4×
System.out.printf("Multiplier at 50%% load: %dx%n", hvResponse.getHighVolumeMultiplier());
```
```javascript JavaScript theme={null}
const hvResponse = await new FeeEstimateQuery()
.setTransaction(
new AccountCreateTransaction()
.setKeyWithoutAlias(newKey)
.setHighVolume(true)
.freezeWith(client)
)
.setMode(FeeEstimateMode.STATE)
.setHighVolumeThrottle(5000)
.execute(client);
console.log(`Multiplier at 50% load: ${hvResponse.highVolumeMultiplier}x`);
```
```go Go theme={null}
tx, _ := hiero.NewAccountCreateTransaction().
SetKeyWithoutAlias(newKey.PublicKey()).
SetHighVolume(true).
FreezeWith(client)
hvResponse, _ := hiero.NewFeeEstimateQuery().
SetTransaction(tx).
SetMode(hiero.FeeEstimateModeState).
SetHighVolumeThrottle(5000).
Execute(client)
fmt.Printf("Multiplier at 50%% load: %dx\n", hvResponse.HighVolumeMultiplier)
```
## Developer Notes
* **Freeze before estimating.** Call `freezeWith(client)` before `FeeEstimateQuery` — the transaction body must be finalized for the estimate to reflect your actual transaction.
* **Estimates are in tinycents; execution fees are in tinybars.** The response uses tinycents (USD × 10⁻¹⁰). The fee charged on execution is in tinybars (HBAR × 10⁻⁸), converted at the live exchange rate at consensus time.
* **Set `maxTransactionFee` with headroom.** Add 10–20% above the estimate — exchange rates shift between estimation and execution.
* **`high_volume_multiplier` scale differs from `TransactionRecord`.** The estimate response uses a 1-based scale (1 = 1×, 4 = 4×). `TransactionRecord.highVolumePricingMultiplier` after execution uses a 1000-based scale (1000 = 1×, 4000 = 4×). Both represent the same multiplier.
## SDK Versions
`FeeEstimateQuery` is available in:
* **Java**: v2.71.0+
* **Go**: v2.79.0+
* **JavaScript** (`@hiero-ledger/sdk`): v2.83.0+
## Related
The base-fee-plus-extras model that fee estimation calculates against.
The underlying REST endpoint backing `FeeEstimateQuery`.
The HIP defining the fee model.
The HIP defining the high-volume lane and congestion multiplier.
# Append to a file
Source: https://docs.hedera.com/native/files/append
A transaction that appends new file content to the end of an existing file. The contents of the file can be viewed by submitting a FileContentsQuery request.
**Transaction Signing Requirements**
* The key on the file is required to sign the transaction if different than the client operator account key
**Transaction Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your transaction fee cost
| **Constructor** | **Description** |
| ------------------------- | -------------------------------------------- |
| `FileAppendTransaction()` | Initializes the FileAppendTransaction object |
```java theme={null}
new FileAppendTransaction()
```
The default max transaction fee (1 hbar) is not enough to create a file. Use `setMaxTransactionFee()`to change the default max transaction fee from 1 hbar to 2 hbars. The default chunk size is 2,048 bytes.
### Methods
| **Method** | **Type** | **Description** | **Requirement** |
| ------------------------------ | --------- | ---------------------------- | --------------- |
| `setFileId()` | FileId | The ID of the file to append | Required |
| `setContents()` | String | The content in String format | Optional |
| `setContents()` | byte \[ ] | The content in byte format | Optional |
| `setChunkSize()` | int | The chunk size | Optional |
| `setMaxChunkSize()` | int | The max chunk size | Optional |
| `setHighVolume()` | boolean | The high-volume flag | Optional |
```java Java theme={null}
//Create the transaction
FileAppendTransaction transaction = new FileAppendTransaction()
.setFileId(newFileId)
.setContents("The appended contents");
//Change the default max transaction fee to 2 hbars
FileCreateTransaction modifyMaxTransactionFee = transaction.setMaxTransactionFee(new Hbar(2));
//Prepare transaction for signing, sign with the key on the file, sign with the client operator key and submit to a Hedera network
TransactionResponse txResponse = modifyMaxTransactionFee.freezeWith(client).sign(key).execute(client);
//Request the receipt
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = await new FileAppendTransaction()
.setFileId(newFileId)
.setContents("The appended contents")
.setMaxTransactionFee(new Hbar(2))
.freezeWith(client);
//Sign with the file private key
const signTx = await transaction.sign(fileKey);
//Sign with the client operator key and submit to a Hedera network
const txResponse = await signTx.execute(client);
//Request the receipt
const receipt = await txResponse.getReceipt(client);
//Get the transaction consensus status
const transactionStatus = receipt.status;
console.log("The transaction consensus status is " +transactionStatus);
//v2.0.5
```
```java Go theme={null}
//Create the transaction
transaction2 := hedera.NewFileAppendTransaction().
SetFileID(newFileId).
SetContents([]byte("The appended contents"))
//Change the default max transaction fee to 2 hbars
modifyMaxTransactionFee := transaction.SetMaxTransactionFee(hedera.HbarFrom(2, hedera.HbarUnits.Hbar))
//Prepare transaction for signing,
freezeTransaction, err := modifyMaxTransactionFee.FreezeWith(client)
if err != nil {
panic(err)
}
//Sign with the key on the file, sign with the client operator key and submit to a Hedera network
txResponse2 err := freezeTransaction.Sign(fileKey).Execute(client)
if err != nil {
panic(err)
}
//Request the receipt
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction consensus status
transactionStatus := receipt.Status
fmt.Println("The transaction consensus status is ", transactionStatus)
//v2.0.0
```
```rust Rust theme={null}
// Create the transaction
let transaction = FileAppendTransaction::new()
.file_id(file_id)
.contents("The appended contents")
.max_transaction_fee(Hbar::new(2));
// Sign with the file key and submit to a Hedera network
let tx_response = transaction
.freeze_with(&client)?
.sign(file_key)
.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
## Get transaction values
| **Method** | **Type** | **Description** | **Requirement** |
| --------------- | -------- | ------------------------------ | --------------- |
| `getFileId()` | FileId | The file ID in the transaction | Optional |
| `getContents()` | String | The content in the transaction | Optional |
```java Java theme={null}
//Create the transaction
FileAppendTransaction transaction = new FileAppendTransaction()
.setFileId(newFileId)
.setContents("The appended contents");
//Get the contents
ByteString getContents = transaction.getContents();
//v2.0.0
```
```java JavaScript theme={null}
//Create the transaction
const transaction = new FileAppendTransaction()
.setFileId(newFileId)
.setContents("The appended contents");
//Get the contents
const getContents = transaction.getContents();
```
```java Go theme={null}
//Create the transaction
transaction2 := hedera.NewFileAppendTransaction().
SetFileID(newFileId).
SetContents([]byte("The appended contents"))
//Get the contents
getContents2 := transaction2.GetContents()
//v2.0.0
```
This transaction supports [high-volume entity creation](/learn/core-concepts/high-volume-entity-creation)
(HIP-1313). Setting `setHighVolume(true)` routes the transaction through dedicated
high-volume throttle capacity with variable-rate pricing. Always pair this with
`setMaxTransactionFee()` to cap your costs.
# Create a file
Source: https://docs.hedera.com/native/files/create
A transaction that creates a new file on a Hedera network. The file is referenced by its file ID which can be obtained from the receipt or record once the transaction reaches consensus on a Hedera network. The file does not have a file name. If the file is too big to create with a single `FileCreateTransaction()`, the file can be appended with the remaining content multiple times using the `FileAppendTransaction()`.
The maximum file size is 1,024 kB.
**Transaction Signing Requirements**
* The key on the file is required to sign the transaction if different than the client operator account key
**Transaction Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your transaction fee cost
#### File Properties
| **Field** | **Description** |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Key(s)** | Set the keys which must sign any transactions modifying this file (the owner(s) of the file). All keys must sign to modify the file's contents or keys. No key is required to sign for extending the expiration time (except the one for the operator account paying for the transaction). The network currently requires a file to have at least one key (or key list or threshold key) but this requirement may be lifted in the future. |
| **Contents** | The contents of the file. The file contents can be recovered from requesting the FileContentsQuery. Note that the total size for a given transaction is limited to 6KiB (as of March 2020) by the network; if you exceed this you may receive a TRANSACTION\_OVERSIZE error. |
| **Expiration Time** | Set the instant at which this file will expire, after which its contents will no longer be available. Defaults to 1/4 of a Julian year from the instant was invoked. |
| **Memo** | Short publicly visible memo about the file. No guarantee of uniqueness. (100 characters max) |
### Methods
| **Constructor** | **Description** |
| ----------------------------- | -------------------------------------------- |
| `new FileCreateTransaction()` | Initializes the FileCreateTransaction object |
```java theme={null}
new FileCreateTransaction()
```
The default max transaction fee (1 hbar) is not enough to create a a file. Use `setDefaultMaxTransactionFee()`to change the default max transaction fee from 1 hbar to 2 hbars.
| **Method** | **Type** | **Requirement** |
| ------------------------------------- | ---------- | --------------- |
| `setKeys()` | Key | Required |
| `setContents()` | String | Optional |
| `setContents()` | bytes \[ ] | Optional |
| `setExpirationTime()` | Instant | Optional |
| `setFileMemo()` | String | Optional |
| `setHighVolume()` | boolean | Optional |
```java Java theme={null}
//Create the transaction
FileCreateTransaction transaction = new FileCreateTransaction()
.setKeys(fileKey)
.setContents(fileContents);
//Change the default max transaction fee to 2 hbars
FileCreateTransaction modifyMaxTransactionFee = transaction.setMaxTransactionFee(new Hbar(2));
//Prepare transaction for signing, sign with the key on the file, sign with the client operator key and submit to a Hedera network
TransactionResponse txResponse = modifyMaxTransactionFee.freezeWith(client).sign(fileKey).execute(client);
//Request the receipt
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the file ID
FileId newFileId = receipt.fileId;
System.out.println("The new file ID is: " + newFileId);
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = await new FileCreateTransaction()
.setKeys([filePublicKey]) //A different key then the client operator key
.setContents("the file contents")
.setMaxTransactionFee(new Hbar(2))
.freezeWith(client);
//Sign with the file private key
const signTx = await transaction.sign(fileKey);
//Sign with the client operator private key and submit to a Hedera network
const submitTx = await signTx.execute(client);
//Request the receipt
const receipt = await submitTx.getReceipt(client);
//Get the file ID
const newFileId = receipt.fileId;
console.log("The new file ID is: " + newFileId);
//v2.0.7
```
```go Go theme={null}
//Create the transaction
transaction := hedera.NewFileCreateTransaction().
SetKeys(filePublicKey).
SetContents([]byte("Hello, World"))
//Change the default max transaction fee to 2 hbars
modifyMaxTransactionFee := transaction.SetMaxTransactionFee(hedera.HbarFrom(2, hedera.HbarUnits.Hbar))
//Prepare transaction for signing,
freezeTransaction, err := modifyMaxTransactionFee.FreezeWith(client)
if err != nil {
panic(err)
}
//Sign with the key on the file, sign with the client operator key and submit to a Hedera network
txResponse, err := freezeTransaction.Sign(fileKey).Execute(client)
if err != nil {
panic(err)
}
//Request the receipt
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the file ID
newFileId := *receipt.FileID
fmt.Printf("The new file ID is %v\n", newFileId)
//v2.0.0
```
```rust Rust theme={null}
// Create a new file
let transaction = FileCreateTransaction::new()
.keys([file_key])
.contents("Hello, Hedera!")
.file_memo("My file memo")
.max_transaction_fee(Hbar::new(2));
// Sign with the client operator private key and submit to a Hedera network
let tx_response = transaction.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the file ID
let file_id = receipt.file_id.unwrap();
println!("The new file ID is {:?}", file_id);
// v0.34.0
```
## Get transaction values
| **Method** | **Type** | **Requirement** |
| --------------------- | ---------- | --------------- |
| `getKeys()` | Key | Optional |
| `getContents()` | ByteString | Optional |
| `getExpirationTime()` | Instant | Optional |
| `getFileMemo()` | String | Optional |
```java Java theme={null}
//Create the transaction
FileCreateTransaction transaction = new FileCreateTransaction()
.setKeys(key)
.setContents(fileContents);
//Get the file contents
ByteString getContents = transaction.getContents();
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = new FileCreateTransaction()
.setKeys(key)
.setContents(fileContents);
//Get the file contents
const getContents = transaction.getContents();
```
```java Go theme={null}
//Create the transaction
transaction := hedera.NewFileCreateTransaction().
SetKeys(filePublicKey).
SetContents([]byte("Hello, World"))
//Get the file contents
getContents := transaction.GetContents()
```
This transaction supports [high-volume entity creation](/learn/core-concepts/high-volume-entity-creation)
(HIP-1313). Setting `setHighVolume(true)` routes the transaction through dedicated
high-volume throttle capacity with variable-rate pricing. Always pair this with
`setMaxTransactionFee()` to cap your costs.
# Delete a file
Source: https://docs.hedera.com/native/files/delete
A transaction that deletes a file from a Hedera network. When deleted, a file's contents are truncated to zero length and it can no longer be updated or appended to, or its expiration time extended. When you request the contents or info of a deleted file, the network will return FILE\_DELETED.
**Transaction Signing Requirements**
* The key(s) on the file are required to sign the transaction
* If you do not sign with the key(s) on the file, you will receive an INVALID\_SIGNATURE network error
**Transaction Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your transaction fee cost
| Constructor | Description |
| ----------------------------- | -------------------------------------------- |
| `new FileDeleteTransaction()` | Initializes the FileDeleteTransaction object |
```java theme={null}
new FileDeleteTransaction()
```
### Methods
| Method | Type | Description |
| --------------------- | ------ | -------------------------------------------- |
| `setFileId()` | FileId | The ID of the file to delete in x.y.z format |
```java Java theme={null}
//Create the transaction
FileDeleteTransaction transaction = new FileDeleteTransaction()
.setFileId(newFileId);
//Modify the default max transaction fee to from 1 to 2 hbars
FileDeleteTransaction modifyMaxTransactionFee = transaction.setMaxTransactionFee(new Hbar(2));
//Prepare transaction for signing, sign with the key on the file, sign with the client operator key and submit to a Hedera network
TransactionResponse txResponse = modifyMaxTransactionFee.freezeWith(client).sign(key).execute(client);
//Request the receipt
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " + transactionStatus);
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = await new FileDeleteTransaction()
.setFileId(fileId)
.setMaxTransactionFee(new Hbar(2))
.freezeWith(client);
//Sign with the file private key
const signTx = await transaction.sign(fileKey);
//Sign with the client operator private key and submit to a Hedera network
const submitTx = await signTx.execute(client);
//Request the receipt
const receipt = await submitTx.getReceipt(client);
//Get the transaction consensus status
const transactionStatus = receipt.status;
console.log("The transaction consensus status " +transactionStatus3.toString());
//v2.0.5
```
```java Go theme={null}
//Create the transaction
transaction := hedera.NewFileDeleteTransaction().
SetFileID(fileId)
//Modify the default max transaction fee to from 1 to 2 hbars
modifyMaxTransactionFee := transaction.SetMaxTransactionFee(hedera.HbarFrom(2, hedera.HbarUnits.Hbar))
//Prepare the transaction for signing
freezeTransaction, err := modifyMaxTransactionFee.FreezeWith(client)
if err != nil {
panic(err)
}
//Sign with the key on the file, sign with the client operator key and submit to a Hedera network
txResponse, err := freezeTransaction.Sign(fileKey).Execute(client)
if err != nil {
panic(err)
}
//Request the receipt
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction status
transactionStatus := receipt.Status
fmt.Println("The transaction consensus status is ", transactionStatus)
//v2.0.0
```
```rust Rust theme={null}
// Create the transaction
let transaction = FileDeleteTransaction::new()
.file_id(file_id)
.max_transaction_fee(Hbar::new(2));
// Sign with the file key and submit to a Hedera network
let tx_response = transaction
.freeze_with(&client)?
.sign(file_key)
.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
## Get transaction values
| Method | Type | Description |
| --------------------- | ------ | ------------------------------------ |
| `getFileId()` | FileId | The ID of the file to delete (x.z.y) |
```java Java theme={null}
//Create the transaction
FileDeleteTransaction transaction = new FileDeleteTransaction()
.setFileId(newFileId);
//Get the file ID
FileId getFileId = transaction.getFileId();
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = new FileDeleteTransaction()
.setFileId(newFileId);
//Get the file ID
FileId getFileId = transaction.getFileId();
```
```java Go theme={null}
//Create the transaction
transaction := hedera.NewFileDeleteTransaction().
SetFileID(fileId)
//Get the file ID
getFileId := transaction.GetFileID()
```
```rust Rust theme={null}
// Create the transaction
let transaction = FileDeleteTransaction::new()
.file_id(file_id);
// Get the file ID
let file_id = transaction.get_file_id();
// v0.34.0
```
# Network Response Messages
Source: https://docs.hedera.com/native/files/errors
Network response messages and their descriptions.
| Network Response | Description |
| --------------------------------------- | ------------------------------------------------------------------------ |
| `FEE_SCHEDULE_FILE_PART_UPLOADED` | Fee Schedule Proto File Part uploaded |
| `FILE_CONTENT_EMPT` | The contents of file are provided as empty. |
| `FILE_DELETED` | the file has been marked as deleted |
| `FILE_SYSTEM_EXCEPTION` | Unexpected exception thrown by file system functions |
| `FILE_UPLOADED_PROTO_INVALID` | Fee Schedule Proto uploaded but not valid (append or update is required) |
| `FILE_UPLOADED_PROTO_NOT_SAVED_TO_DISK` | Fee Schedule Proto uploaded but not valid (append or update is required) |
| `INVALID_EXCHANGE_RATE_FILE` | Failed to update exchange rate file |
| `INVALID_FEE_FILE` | Failed to update fee file |
| `INVALID_FILE_ID` | The file id is invalid or does not exist |
| `INVALID_FILE_WACL` | File WACL keys are invalid |
| `MAX_FILE_SIZE_EXCEEDED` | File size exceeded the currently allowable limit |
| `NO_WACL_KEY` | WriteAccess Control Keys are not provided for the file |
# Get file contents
Source: https://docs.hedera.com/native/files/get-contents
A query to get the contents of a file. Queries do not change the state of the file or require network consensus. The information is returned from a single node processing the query.
**Query Signing Requirements**
* The client operator private key is required to sign the query request
**Query Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for the base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your query fee cost
| Constructor | Description |
| ------------------------- | -------------------------------------- |
| `new FileContentsQuery()` | Initializes a FileContentsQuery object |
```java theme={null}
new FileContentsQuery()
```
### Methods
| Method | Type | Description |
| --------------------- | ------ | ---------------------------------------------- |
| `setFileId()` | FileId | The ID of the file to get contents for (x.z.y) |
```java Java theme={null}
//Create the query
FileContentsQuery query = new FileContentsQuery()
.setFileId(newFileId);
//Sign with client operator private key and submit the query to a Hedera network
ByteString contents = query.execute(client);
//Change to Utf-8 encoding
String contentsToUtf8 = contents.toStringUtf8();
System.out.println(contentsToUtf8);
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the query
const query = new FileContentsQuery()
.setFileId(newFileId);
//Sign with client operator private key and submit the query to a Hedera network
const contents = await query.execute(client);
console.log(contents.toString());
//v2.0.7
```
```java Go theme={null}
//Create the query
query := hedera.NewFileContentsQuery().
SetFileID(newFileId)
//Sign with client operator private key and submit the query to a Hedera network
contents, err := query.Execute(client)
fmt.Println(string(contents))
//v2.0.0
```
```rust Rust theme={null}
// Create the query
let query = FileContentsQuery::new()
.file_id(file_id);
// Submit the query to a Hedera network
let contents = query.execute(&client).await?;
// Print the contents to the console
println!("File contents: {:?}", String::from_utf8_lossy(&contents));
// v0.34.0
```
## Get query values
| Method | Type | Description |
| ------------- | ------ | ---------------------------------------------- |
| `getFileId()` | FileId | The ID of the file to get contents for (x.z.y) |
```java Java theme={null}
//Create the query
FileContentsQuery query = new FileContentsQuery()
.setFileId(newFileId);
//Get file ID
FileId getFileId = query.getFileId();
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the query
const query = new FileContentsQuery()
.setFileId(newFileId);
//Get file ID
const getFileId = query.getFileId();
```
```java Go theme={null}
//Create the query
query := hedera.NewFileContentsQuery().
SetFileID(newFileId)
//Get file ID
getFileId := query.GetFileID()
//v2.0.0
```
```rust Rust theme={null}
// Create the query
let query = FileContentsQuery::new()
.file_id(file_id);
// Get the file ID
let file_id = query.get_file_id();
// v0.34.0
```
# Get file info
Source: https://docs.hedera.com/native/files/get-info
A query that returns the current state of a file. Queries do not change the state of the file or require network consensus. The information is returned from a single node processing the query.
**Query Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for the base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your query fee cost
**File Info Response**
| **Field** | Description |
| ------------------- | ------------------------------------------------------------------------------------------------- |
| **File ID** | The Hedera ID of the file |
| **Key(s)** | The current admin key(s) on the account |
| **Size** | The number of bytes in the file contents |
| **Expiration Time** | The current time at which the file is set to expire |
| **Deleted** | Whether or not the file has been deleted |
| **Ledger ID** | The ID of the network the response came from. See [HIP-198](https://hips.hedera.com/hip/hip-198). |
| **Memo** | A short description, if any |
\
**Query Signing Requirements**
* The client operator account paying for the query fees is required to sign
| Constructor | Description |
| --------------------- | ------------------------------------ |
| `new FileInfoQuery()` | Initializes the FileInfoQuery object |
```java theme={null}
new FileInfoQuery()
```
### Methods
| Method | Type | Description |
| --------------------- | ------ | ------------------------------------------------- |
| `setFileId()` | FileId | The ID of the file to get information for (x.y.z) |
```java Java theme={null}
//Create the query
FileInfoQuery query = new FileInfoQuery()
.setFileId(fileId);
//Sign the query with the client operator private key and submit to a Hedera network
FileInfo getInfo = query.execute(client);
System.out.println("File info response: " +getInfo);
```
```javascript JavaScript theme={null}
//Create the query
const query = new FileInfoQuery()
.setFileId(fileId);
//Sign the query with the client operator private key and submit to a Hedera network
const getInfo = await query.execute(client);
console.log("File info response: " + getInfo);
```
```java Go theme={null}
//Create the query
query := hedera.NewFileInfoQuery().
SetFileID(newFileId)
//Sign the query with the client operator private key and submit to a Hedera network
getInfo, err := query.Execute(client)
fmt.Println(getInfo)
```
```rust Rust theme={null}
// Create the query
let query = FileInfoQuery::new()
.file_id(file_id);
// Submit the query to a Hedera network
let file_info = query.execute(&client).await?;
// Print the file info to the console
println!("File info: {:?}", file_info);
// v0.34.0
```
**Sample Output:**
```
FileInfo{
fileId=0.0.104926,
size=26,
expirationTime=2021-02-10T17:48:15Z,
deleted=false,
keys=[ 302a300506032b6570032100100059296cc51f5d362a3859d3c3c74c6a480cffad9d669a10c1d447ce56e5bf
]
}
```
## Get query values
| Method | Type | Description |
| ------------- | ------ | ---------------------------------------------- |
| `getFileId()` | FileId | The ID of the file to get contents for (x.z.y) |
```java Java theme={null}
//Create the query
FileInfoQuery query = new FileInfoQuery()
.setFileId(fileId);
//Get file ID
FileId getFileId = query.getFileId();
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the query
const query = new FileInfoQuery()
.setFileId(fileId);
//Get file ID
const getFileId = query.getFileId();
//v2.0.0
```
```java Go theme={null}
//Create the query
query := hedera.NewFileContentsQuery().
SetFileID(newFileId)
//Get file ID
getFileId := query.GetFileID()
//v2.0.0
```
```rust Rust theme={null}
// Create the query
let query = FileInfoQuery::new()
.file_id(file_id);
// Get the file ID
let file_id = query.get_file_id();
// v0.34.0
```
# Update a file
Source: https://docs.hedera.com/native/files/update
A transaction that updates the state of an existing file on a Hedera network. Once the transaction has been processed, the network will be updated with the new field values of the file. If you need to access a previous state of the file, you can query a mirror node.
**Transaction Signing Requirements**
* The key or keys on the file are required to sign this transaction to modify the file properties
* If you are updating the keys on the file, you must sign with the old key and the new key
* If you do not sign with the key(s) on the file, you will receive an `INVALID_SIGNATURE` network error
**Transaction Fees**
* Please see the transaction and query [fees](/networks/fees#transaction-and-query-fees) table for base transaction fee
* Please use the [Hedera fee estimator](https://hedera.com/fees) to estimate your transaction fee cost
#### File Properties
| Field | Description |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **Key(s)** | Update the keys which must sign any transactions modifying this file. All keys must sign to modify the file's contents or keys. No key is required to sign for extending the expiration time (except the one for the operator account paying for the transaction). The network currently requires a file to have at least one key (or key list or threshold key) but this requirement may be lifted in the future. |
| **Contents** | The content to update the files with. |
| **Expiration Time** | If set, update the expiration time of the file. Must be in the future (may only be used to extend the expiration). To make a file inaccessible use FileDeleteTransaction. |
| **Memo** | Short publicly visible memo about the file. No guarantee of uniqueness. (100 characters max) |
| Constructor | Description |
| ----------------------------- | -------------------------------------------- |
| `new FileUpdateTransaction()` | Initializes the FileUpdateTransaction object |
```java theme={null}
new FileUpdateTransaction()
```
### Methods
**Note:** The total size for a given transaction is limited to 6KiB. If you exceed this value you will need to submit a FileUpdateTransaction that is less than 6KiB and then submit a FileAppendTransaction to add the remaining content to the file.
| Method | Type | Requirement |
| --------------------------------- | --------- | ----------- |
| `setFileId()` | FileId | Required |
| `setKey()` | Key | Optional |
| `setContents()` | byte \[ ] | Optional |
| `setContents()` | String | Optional |
| `setExpirationTime()` | Instant | Optional |
| `setFileMemo()` | String | Optional |
```java Java theme={null}
//Create the transaction
FileUpdateTransaction transaction = new FileUpdateTransaction()
.setFileId(fileId)
.setKeys(newKey);
//Modify the max transaction fee
FileUpdateTransaction txFee = transaction.setMaxTransactionFee(new Hbar(3));
//Freeze the transaction, sign with the original key, sign with the new key, sign with the client operator key and submit the transaction to a Hedera network
TransactionResponse txResponse = txFee.freezeWith(client).sign(fileKey).sign(newKey).execute(client);
//Get the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction consensus status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = await new FileUpdateTransaction()
.setFileId(fileId)
.setContents("The new contents")
.setMaxTransactionFee(new Hbar(2))
.freezeWith(client);
//Sign with the file private key
const signTx = await transaction.sign(fileKey);
//Sign with the client operator private key and submit to a Hedera network
const submitTx = await signTx.execute(client);
//Request the receipt
const receipt = await submitTx.getReceipt(client);
//Get the transaction consensus status
const transactionStatus = receipt.status;
console.log("The transaction consensus status " +transactionStatus3.toString());
//v2.0.5
```
```java Go theme={null}
//Create the transaction
transaction := hedera.NewFileUpdateTransaction().
SetFileID(fileId).
SetKeys(newKey)
//Modify the max transaction fee
modifyMaxTransactionFee := transaction.SetMaxTransactionFee(hedera.HbarFrom(2, hedera.HbarUnits.Hbar))
//Prepare the transaction for signing
freezeTransaction, err := modifyMaxTransactionFee.FreezeWith(client)
if err != nil {
panic(err)
}
//Sign with the key on the file, sign with the client operator key and submit to a Hedera network
txResponse, err := freezeTransaction.Sign(fileKey).Sign(newKey).Execute(client)
if err != nil {
panic(err)
}
//Request the receipt
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction status
transactionStatus := receipt.Status
fmt.Println("The transaction consensus status is ", transactionStatus)
//v2.0.0
```
```rust Rust theme={null}
// Create the transaction
let transaction = FileUpdateTransaction::new()
.file_id(file_id)
.keys([new_key])
.contents("The new contents")
.file_memo("Updated file memo")
.max_transaction_fee(Hbar::new(2));
// Sign with the file key and new key
let tx_response = transaction
.freeze_with(&client)?
.sign(file_key)
.sign(new_key)
.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction consensus status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
## Get transaction values
| Method | Type | Requirement |
| --------------------- | ---------- | ----------- |
| `getFileId()` | FileId | Optional |
| `getKey()` | Key | Optional |
| `setContents()` | ByteString | Optional |
| `getExpirationTime()` | Instant | Optional |
| `getFileMemo()` | String | Optional |
```java Java theme={null}
//Create the transaction
FileUpdateTransaction transaction = new FileUpdateTransaction()
.setFileId(fileId)
.setKeys(newKey);
//Get the contents of a file
Key getKey = transaction.getKey();
//v2.0.0
```
```java JavaScript theme={null}
//Create the transaction
const transaction = new FileUpdateTransaction()
.setFileId(newFileId);
//Get the contents of a file
const getKey = transaction.getKey();
```
```java Go theme={null}
//Create the transaction
transaction := hedera.NewFileUpdateTransaction().
SetFileID(fileId).
SetKeys(newKey)
//Get the contents of a file
getKey := transaction.GetKeys()
//v2.0.0
```
```rust Rust theme={null}
// Create the transaction
let transaction = FileUpdateTransaction::new()
.file_id(file_id)
.keys([new_key]);
// Get the key
let key = transaction.get_key();
// v0.34.0
```
# Network Address Book
Source: https://docs.hedera.com/native/fundamentals/address-book
The address book contains the node ID and node address information to communicate with Hedera node(s) in a specific network. There are two ways you can get the address book for a network.
* You can submit a `FileContentsQuery()` to a consensus node for file `0.0.101` or `0.0.102`
* You can also query the Hedera mirror node using the `AddressBookQuery()`
```java Java theme={null}
//Create the query
FileContentsQuery fileQuery = new FileContentsQuery()
.setFileId( FileId.fromString("0.0.102"));
//Sign with the operator private key and submit to a Hedera network
ByteString contents = fileQuery.execute(client);
System.out.println(contents.toStringUtf8());
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the query
const fileQuery = new FileContentsQuery()
.setFileId( FileId.fromString("102"));
//Sign with the operator private key and submit to a Hedera network
const contents = await fileQuery.execute(client);
console.log(contents.toString())
//v2.0.7
```
```go Go theme={null}
//Create the query
fileQuery := hedera.NewFileContentsQuery().
SetFileID(hedera.FileIDForAddressBook())
//Sign with the operator private key and submit to a Hedera network
contents, err := fileQuery.Execute(client)
if err != nil {
panic(err)
}
fmt.Println(string(contents))
//v2.0.0
```
```rust Rust theme={null}
// Create the query
let file_query = FileContentsQuery::new()
.file_id(FileId::from_str("0.0.102"));
// Sign with the operator private key and submit to a Hedera network
let contents = file_query.execute(&client).await?;
println!("{:?}", String::from_utf8(contents)?);
// v0.34.0
```
```java Java theme={null}
//Mirror node address book query
NodeAddressBook addressBook = new AddressBookQuery()
.setFileId(FileId.ADDRESS_BOOK)
.execute(client);
System.out.println(addressBook);
//SDK Version: 2.10.0-beta.1
```
```javascript JavaScript theme={null}
//Mirror node address book query
const addressBook = new AddressBookQuery()
.setFileId(FileId.ADDRESS_BOOK)
.execute(client);
console.log(addressBook);
//SDK Version: 2.10.0
```
```go Go theme={null}
//Mirror node address book
addressBook, err := hedera.NewAddressBookQuery().
SetFileID(hedera.FileIDForAddressBook()).
Execute(client)
//Print address book to console
fmt.Print(addressBook)
//SDK Version: 2.10.0
```
```rust Rust theme={null}
// Mirror node address book query
let address_book = NodeAddressBookQuery::new()
.execute(&client)
.await?;
println!("{:?}", address_book);
// v0.34.0
```
#### Sample Output:
```txt theme={null}
35.231.208.1480.0.3"308201a2300d06092a864886f70d01010105000382018f003082018a02820181009f1f8a121c2fd6c76fd508d3e429f0c64bcb44c82a70573552aadcad071569e721958f5a5d09f9587ffafcfbe5341a2f0114acae346ef3c90213d3436ebb27f4350c990c5c8c3f8e1e36707bc08d42560823e3f24e09a03ad0955a5098019629dd04b27b251dce055f3ddcb0a41d66f0941b0b87cdfe3498d46038ab5df06f62a5ade08598573a88c8f5860dc1492a6e186485a9b13250e6d17b80cd39c5c819109e73ca732db23ef8baa776ec85ce0091becb2edefbaa5ed3e5dbfbd1f885a4fa881af3f144a8a565853533d89393592086b2d1d362e45bfe1fb45683aba6c640979ad6b46877184726c6ebd58b2eae85c7cfe3fbabef5f6cced850034b3847206c2d678c361876026b8d351e002af5e0ffe6f5b1f295fdc2f469caa2d2381ea0b48ca987cc2c8e635e8b19ce5e172a93761a8d490a9a4518d7255880a14d77b7ba774892b92a40bb81362e34fc6d5178d9b30112934205cb77fb9a282427394564a8554ea47286a47f86239e75c94789ce98c99844782462944f613167d7b502030100012:`ffd6ada74a3a34a904bea47603086f8bef3b6be18abed44c4d40e12fb130b97bd6b855aec5d0b90b0b8c7354d5f3b0e4B
35.199.15.1770.0.4"308201a2300d06092a864886f70d01010105000382018f003082018a0282018100c557af579fa83501be899b28907765bfdfcd52ab432b0195a1f1ecd86fc00ab6c5509b0fdd97edd3cb5cea56a295f312abb550831dbf963f450118b4fcc6e22cf4676200ce9cc8edfbbf558dc69f024264ad7d3dab23bed2133c274e6934489155db1087f90370905c64185a6211dc742fb9a6909d82186947b277463dfb3ff0acd47eff12ead1f6972ef2c1203793c45e77575be4fa110c7e40fa8db9c6187d113f4704014179071abf59be7d2b0de82de4215dc25506b1c9c26e4917401c997506e377e6bf03b688727e7940fad69c5e0da3cd5cbd2be777350aea2d0d47e97a448c84be6ce134d64bee0985c29162f4c1e567cca93d06a3c1be8abce35b557fb77f4fe671a66dec790756d0e8818165f2bacaa891aae7ac7437fc7175b6eb6deb7472378751bb6bf9b0e1483f9668e9fdbd5604c39b14d9e2bedeec846a980d704d171e7ba4b7fcd1a30d945ca12f47a325d9398aa18f97066054d4d15fc8994e2debe73e9271d548683f61ea44fb25071e3518a78ed3eb37e71a0691f2670203010001(2:`f0d94accf6dff372874c9dbd8d7992eb317af5001ca4196aba265809cb3d200ba961a5438c3a5ed05c83bdf9cd115d22B
35.225.201.1950.0.5"308201a2300d06092a864886f70d01010105000382018f003082018a02820181009ba457b73305f04a91cc46b1b965c4e841751abc8b1415a0badfd1f32c2482386a22725eb7ec74dea21e50617d648ea5ac393741ab01b8efb321239b8d4fdb1dfbeb9e3f39aa46580dd045d18ca44d002c37ddb527cce4ddc32bfc73419671f4ca4464a3f2a84fc85c71acf0e5a89626df69a81474ed16529f801a8afa97e435c4e04a964a357527288843e58f0a05cf5153ee4507b2c68b3d7fb54ae6a95a959c87a12f630e95c7b1b3c3695e858662417926d76c16983faf61225038745907e9cf13d67c2acd503ca451c85933ac4118acc279801cb968349903145ced27629dd08916317093587a77c2205cfa52543b53c3b6ea15b84e3d2c30c1ed752a4633c36b25b9893ea02ad562eb9b7868b3b4f47f4a25e356064962ac7b25e582944f00d30798a262f9214d8c5e74d0a8376cc2d6ba64e18f5e4a40afac625062d2ca23cd2800708321d3834314f0e5844859232673a32e70ae0d711e310581bcdb14e87134694c6e0930f46b37b96d49a64573947331e7e507d9e56de5e6146f2f0203010001(2:`ca678ebcbd3dc8648f7ed03fb59f0e21af67513eaee51318e6b549be5ace906edc1ffa26d93a57acec9be77f40eaeed7B
35.247.109.1350.0.6"308201a2300d06092a864886f70d01010105000382018f003082018a0282018100c42ccac5fbc691fbbebda87ffd1e75bdcd8922494cf44fdbccee49788521c378bf77db0934ec0d2183d7c51db66f864c11ab7de1ac3c4cfdc1f093a2d6f37e2b34cbe4c8131f9683ad42878c83d3554c645aa167bcfb064a83dc45c5b1158499f9d92587fff7abcd5f221cd8150548413000fa6e5659089b1dfd65766ea78eaedfca6b45455fd8ab5984dbe35e5795d2c635ea7974d43e8eae4febffe492e707b48b1b0fc6481ae9e09d39133009b7d26402e6e52e5e91b2b380d88f0be7fb4b303e70219785057aa94ce924c4926e916569286e86b3ba651ca2a0a63df4f6907fefe3483d93b4ce1d4d03c7142111375b2c2c51d4eb839e37af530b2cbd6f50d4cb36e27937170d9cddac0ace2cc24b804b0a27351cf830b76525e26dfb9dbf49a056624a76862494e7263d0d70cebae952943e55842f5cad13fcf60a2e6dcf7a1d533f3a5bb54ec21918c76e525ba29146675831e17e36c61fe85498828d09b762015412b2e527849baec1cffc77de4c294c550811e598ff24da15a34569dd0203010001(2:`2471f3fe8140681fe91913d2cc063f065e4490ae62ff5d548a5abe131d2af96cbe3ac25bbe24366ca4f8f0e76cf945f3B
35.235.65.510.0.7"308201a2300d06092a864886f70d01010105000382018f003082018a0282018100902f0490a9b7f5d2cd1c0d96c6a6990f573b5f0eb5bdbba39661ef023092419344669969a68a4c7071d329990fb1792e9001cb5598ea71c2d6676824320ee4cabf1dd357ae7f2adbedc1b1b0a9d95623779b4c4c7b47c4787a16ee7188c7217177624a9264ab39c41f7ff0b45a89bda40c4ad07c4d596d5f09d7056bcb5a35f44f95a59c266e09892dcbe46ad51f2d2b3e991a8f6658e1f2cb94c773eb44c44e892d1e55c1076f1608319ee657e40f192967543ab42ab222386d17586e253748dabd025e50b50ae6050720e239d64ee6fb4507c0614dd4be7afdb1330890ff3a6e176527c3116af129a9ac5e336d9f601e7127a6d7d820ad2f902dac9b248668a1bab08d10342ea69a7097132ff7120cc64fcde7840c656ba1732ba95e9c36751175e4ec3d84a7e0d28842b41bbbbd6f28e46c3a6633e1827965c55820d50dae2b0465cc0d42e195b9d1532e6225eb998d6a49079a8a1cd4d0175de3c87f97614847b3cbb17aa34be820b7b3ad98ac3faef993a6778974782c0c4ae3fabbcc430203010001(2:`f357873d4114a1aef03adc6ba69efaf2690e227abc16a6fc6e5049a63fbd9688004b14e463c20e38436a3a24d3182dd8B
34.106.247.650.0.8"308201a2300d06092a864886f70d01010105000382018f003082018a028201810091d7dfff78f4efbe5890450c5bc9e3534bffadad93fb7afb15bc7bcf67d3d3b413bd99940dd82564ada04ab2e4edf0a1c0b8fb7e1a8092e9138e960be2cc68b5b97f57d281c5872e97a479fc848363160e3863b57b33e4869b185ace5e36bd43ae5fa678c9eb66f1f4014786826b2f8fa7e0060f4405c0a8f9da7205ff4683a243fa0f315f1afbb4a4d140d02234e4473fb92fcb38f3eb28c60cf7cbfb64e069c18086e4dd61938920ae0fd7c193e6e104e65b817ed9398e232237fdf08322c9cec09d4099272a7c015d22b4dcc969f6ea1f518902105df60092b55a41b4f32b957b57d84e5b223905e8698951733ea9f2e2461ec0d6522ee816d5850facfeb412cff9b99943a87dc0d046447ce93b97e16d73b96b4263962f81fcf9458e57577c780a6f1615aa7a12326738e269bb731f89e891622e577ea54420bf0ca46be6fc4f71cf2681ac0252aa885e13be672cd284590427dcd137cf311625e8bee3b08fdcaaf465b387ce7cb33816f2c14a6b99ac7d734318cfc59b7ed939bafef8790203010001(2:`4931a78202d55f10b31575785c3f439db6819bd11003df7bc2ce92e29a517b7c21880deb4c01795744b576cd43b8498dB
34.125.23.490.0.9"308201a2300d06092a864886f70d01010105000382018f003082018a0282018100c6e18c8fbf4cd4eb104542cb20aaaa252d95f052f1086d581c44ad737bf6676c0c3f789af5265b8afb79b50912da84e0afcf7547cb1fff08d0527017eb6dc5cdf83b51969d44336a6387cd70b94bf4c9baf2029840e5f4f863d7081f0fa81e0863adedb8b89a5dac2bb552d6e7b9fba222ac28c57075538fc957992942d341fa2876e6b507e9ce7ed572e8cfda5defa364fdf8d8e23829a4ccbb478f11eee3b32ab85e072951c5d9420115fba327073494f43b5f6bebf84152e356e7b16ba764b7a3b52cb2734640163be1465e6d1fa4c6e6f66684a635c9a556aa7100dbe645df8f4c423ae45a08cb35b4bc187886e2299b5c0210a5fba3b9449f483ef94ed922e1e98c113be166b89c73582243135d442306abe5a71b77018ff335d6dd79542697b168238b96727fd1339b5f82a3b6a597d976037ae2506456c8b34e9fbf3bc32410441c4bfc8eba58597254efebfaa78809a5c8854729a5ba78ece19fc8407dd8894a6bc7844037d878cace6c152c2e89e8a64b068a6c237e09993be806890203010001(2 :`64e098615bf405f7ed5a4013446b89c488cfcd6bb25a4a676dc77eea11d33d702682f0a69a8030e8c5777d0e42203799B
```
# Hedera Client
Source: https://docs.hedera.com/native/fundamentals/client
## 1. Configure your Hedera Network
Build your client to interact with any of the Hedera network nodes. Mainnet, testnet, and previewnet are the three Hedera networks you can submit transactions and queries to.
For a predefined network (preview, testnet, and mainnet), the mirror node client is configured to the corresponding network mirror node. The default mainnet mirror node connection is to the [whitelisted mirror node](/operators/mirror-node#mainnet).
To access the ***public mainnet mirror node***, use `setMirrorNetwork()` and enter `mainnet.mirrornode.hedera.com:433` for the endpoint. The gRPC API requires TLS. The following SDK versions are compatible with TLS:
* **Java:** v2.3.0+
* **JavaScript**: v2.4.0+
* **Go:** v2.4.0+
Method
Type
Description
Client.forPreviewnet()
Constructs a Hedera client pre-configured for Previewnet access
Client.forTestnet()
Constructs a Hedera client pre-configured for Testnet access
Client.forMainnet()
Constructs a Hedera client pre-configured for Mainnet access
Client.forNetwork(\)
Map\
Construct a client given a set of nodes. It is the responsibility of the caller to ensure that all nodes in the map are part of the same Hedera network. Failure to do so will result in undefined behavior.
Configure a client based on a JSON file at the given path.
Client.forName(\)
String
Provide the name of the network. mainnet testnet previewnet
Client.\.setMirrorNetwork(\)
List\
Define a specific mirror network node(s) ip:port in string format
Client.\.getMirrorNetwork()
List\
Return the mirror network node(s) ip:port in string format
Client.setTransportSecurity()
boolean
Set if transport security should be used. If transport security is enabled all connections to nodes will use TLS, and the server's certificate hash will be compared to the hash stored in the node address book for the given network.
Client.setNetworkUpdatePeriod()
Duration
Client automatically updates the network via a mirror node query at regular intervals. You can set the interval at which the address book is updated.
Client.setNetworkFromAddressBook(\)
AddressBook
Client can be set from a NodeAddressBook.
Client.setLedgerId(\)
LedgerId
The ID of the network. LedgerId.MAINNET LedgerId.TESTNET LedgerId.PREVIEWNET
Client.getLedgerId()
LedgerId
Get the ledger ID
Client.setVerifyCertificates()
boolean
Set if server certificates should be verified against an existing address book.
```java Java theme={null}
// From a pre-configured network
Client client = Client.forTestnet();
//For a specified network
Map nodes = new HashMap<>();
nodes.put("34.94.106.61:50211" ,AccountId.fromString("0.0.10"));
Client.forNetwork(nodes);
//v2.0.0
```
```javascript JavaScript theme={null}
// From a pre-configured network
const client = Client.forTestnet();
//For a specified network
const nodes = {"34.94.106.61:50211": new AccountId(10)}
const client = Client.forNetwork(nodes);
//v2.0.7
```
```go Go theme={null}
// From a pre-configured network
client := hedera.ClientForTestnet()
//For a specified network
node := map[string]AccountID{
"34.94.106.61:50211": {Account: 10}
}
client := Client.forNetwork(nodes)
//v2.0.0
```
```rust Rust theme={null}
// From a pre-configured network
let client = Client::for_testnet();
// For a specified network
let nodes = HashMap::from([
("34.94.106.61:50211".to_string(), AccountId::from(10))
]);
let client = Client::for_network(nodes);
// v0.34.0
```
## 2. Define the operator account ID and private key
The operator is the account that will, by default, pay the transaction fee for transactions and queries built with this client. The operator account ID is used to generate the default transaction ID for all transactions executed with this client. The operator private key is used to sign all transactions executed by this client.
| Method | Type |
| ------------------------------------------------------------------------------ | ----------------------------------------------------- |
| `Client..setOperator()` | AccountId, PrivateKey |
| `Client..setOperatorWith()` | AccountId, PrivateKey, Function\ |
### From an account ID and private key
```java theme={null}
// Operator account ID and private key from string value
AccountId OPERATOR_ID = AccountId.fromString("0.0.96928");
Ed25519PrivateKey OPERATOR_KEY = PrivateKey.fromString("302e020100300506032b657004220420b9c3ebac81a72aafa5490cc78111643d016d311e60869436fbb91c7330796928");
// Pre-configured client for test network (testnet)
Client client = Client.forTestnet()
//Set the operator with the account ID and private key
client.setOperator(OPERATOR_ID, OPERATOR_KEY);
```
```javascript JavaScript theme={null}
// Your account ID and private key from string value
const OPERATOR_ID = AccountId.fromString("0.0.96928");
const OPERATOR_KEY = PrivateKey.fromString("302e020100300506032b657004220420b9c3ebac81a72aafa5490cc78111643d016d311e60869436fbb91c7330796928");
// Pre-configured client for test network (testnet)
const client = Client.forTestnet()
//Set the operator with the account ID and private key
client.setOperator(OPERATOR_ID, OPERATOR_KEY);
```
```go Go theme={null}
// Operator account ID and private key from string value
operatorAccountID, err := hedera.AccountIDFromString("0.0.96928")
if err != nil {
panic(err)
}
operatorKey, err := hedera.PrivateKeyFromString("302e020100300506032b65700422042012a4a4add3d885bd61d7ce5cff88c5ef2d510651add00a7f64cb90de33596928")
if err != nil {
panic(err)
}
// Pre-configured client for test network (testnet)
client := hedera.ClientForTestnet()
//Set the operator with the operator ID and operator key
client.SetOperator(operatorAccountID, operatorKey)
```
```rust Rust theme={null}
// Operator account ID and private key from string value
let my_account_id = AccountId::from_str("0.0.96928")?;
let my_private_key = PrivateKey::from_str("302e020100300506032b657004220420b9c3ebac81a72aafa5490cc78111643d016d311e60869436fbb91c7330796928")?;
// Pre-configured client for test network (testnet)
let mut client = Client::for_testnet();
// Set the operator with the account ID and private key
client.set_operator(my_account_id, my_private_key);
// v0.34.0
```
### From a .env file
The .env file is created in the root directory of the SDK. The `.env` file stores account ID and the associated private key information to reference throughout your code. You will need to import the relevant dotenv module to your project files. The sample .env file may look something like this:
**.env**
```
OPERATOR_ID=0.0.941
OPERATOR_KEY=302e020100300506032b65700422042012a4a4add3d885bd61d7ce5cff88c5ef2d510651add00a7f64cb90de3359bc5e
```
```java Java theme={null}
//Grab the account ID and private key of the operator account from the .env file
AccountId OPERATOR_ID = AccountId.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_ID")));
Ed25519PrivateKey OPERATOR_KEY = Ed25519PrivateKey.fromString(Objects.requireNonNull(Dotenv.load().get("OPERATOR_KEY")));
// Pre-configured client for test network (testnet)
Client client = Client.forTestnet()
//Set the operator with the account ID and private key
client.setOperator(OPERATOR_ID, OPERATOR_KEY);
```
```javascript JavaScript theme={null}
//Grab the account ID and private key of the operator account from the .env file
const operatorId = process.env.OPERATOR_ID;
const operatorKey = process.env.OPERATOR_KEY;
// Pre-configured client for test network (testnet)
const client = Client.forTestnet()
//Set the operator with the account ID and private key
client.setOperator(operatorId, operatorKey);
```
```go Go theme={null}
err := godotenv.Load(".env")
if err != nil {
panic(fmt.Errorf("Unable to load environment variables from demo.env file. Error:n%v\n", err))
}
//Get the operator account ID and private key
OPERATOR_ID := os.Getenv("OPERATOR_ID")
OPERATOR_KEY := os.Getenv("OPERATOR_KEY")
myAccountID, err := hedera.AccountIDFromString(OPERATOR_ID)
if err != nil {
panic(err)
}
operatorKey, err := hedera.PrivateKeyFromString(OPERATOR_KEY)
if err != nil {
panic(err)
}
```
```rust Rust theme={null}
err := dotenv::dotenv().ok();
if err.is_err() {
panic!("Unable to load environment variables from demo.env file. Error:n{:?}", err);
}
// Get the operator account ID and private key
let my_account_id = AccountId::from_str(std::env::var("OPERATOR_ID").expect("OPERATOR_ID environment variable not set")).expect("Invalid OPERATOR_ID format");
let my_private_key = PrivateKey::from_str(std::env::var("OPERATOR_KEY").expect("OPERATOR_KEY environment variable not set")).expect("Invalid OPERATOR_KEY format");
```
## 3. Additional client modifications
The **max transaction fee** and **max query payment** are both set to 100\_000\_000 tinybar (1 HBAR). This amount can be modified by using **`setDefaultMaxTransactionFee()`**and **`setDefaultMaxQueryPayment()`**`.`
Method
Type
Description
Client.\.SetDefaultRegenerateTransactionId(\)
boolean
Whether or not to regenerate the transaction IDs
Client.\.getDefaultRegenerateTransactionId(\)
boolean
Get the default regenerate transaction ID boolean value
Client.\.setDefaultMaxTransactionFee(\)
Hbar
The maximum transaction fee the client is willing to pay
Client.\.getDefaultMaxTransactionFee()
Hbar
Get the default max transaction fee that is set
Client\.setDefaultMaxQueryPayment(\)
Hbar
The maximum query payment the client will pay.
Default: 1 hbar
Client\.getDefaultMaxQueryPayment()
Hbar
Get the default max query payment
Client.\.setNetwork(\)
Map\
Replace all nodes in this Client with a new set of nodes (e.g. for an Address Book update)
Client.\.getNetwork()
Map\
Get the network nodes
Client.\.setRequestTimeout(\)
Duration
The period of time a transaction or query request will retry from a "busy" network response
Client.\.getRequestTimeout()
Duration
Get the period of time a transaction or query request will retry from a "busy" network response
Client.\.setMinBackoff(\)
Duration
The minimum amount of time to wait between retries. When retrying, the delay will start at this time and increase exponentially until it reaches the maxBackoff
Client.\.getMinBackoff()
Duration
Get the minimum amount of time to wait between retries
Client.\.setMaxBackoff(\)
Duration
The maximum amount of time to wait between retries. Every retry attempt will increase the wait time exponentially until it reaches this time.
Client.\.getMaxBackoff()
Duration
Get the maximum amount of time to wait between retries
Client.\.setAutoValidateChecksums(\)
boolean
Validate checksums
Client.\.setCloseTimeout(\)
Duration
Timeout for closing either a single node when setting a new network, or closing the entire network
Client.\.setMaxNodeAttempts(\)
int
Set the max number of times a node can return a bad gRPC status before we remove it from the list
Client.\.getMaxNodeAttempts()
int
Get the max node attempts set
Client.\.setMinNodeReadmitTime(\)
Duration
The min time to wait before attempting to readmit nodes
Client.\.getMinNodeReadmitTime()
Duration
Get the minimum node readmit time
Client.\.setMaxNodeReadmitTime(\)
Duration
The max time to wait before attempting to readmit nodes
Client.\.getMaxNodeReadmitTime()
Duration
Get the max node readmit time
```java Java theme={null}
// For test network (testnet)
Client client = Client.forTestnet()
//Set your account as the client's operator
client.setOperator(operatorId, operatorKey);
//Set the default maximum transaction fee (in Hbar)
client.setDefaultMaxTransactionFee(new Hbar(10));
//Set the maximum payment for queries (in Hbar)
client.setDefaultMaxQueryPayment(new Hbar(5));
//v2.0.0
```
```javascript JavaScript theme={null}
// For test network (testnet)
const client = Client.forTestnet()
//Set your account as the client's operator
client.setOperator(operatorId, operatorKey);
//Set the default maximum transaction fee (in Hbar)
client.setDefaultMaxTransactionFee(new Hbar(10));
//Set the maximum payment for queries (in Hbar)
client.setDefaultMaxQueryPayment(new Hbar(5));
//v2.0.0
```
```go Go theme={null}
// For test network (testnet)
client := hedera.ClientForTestnet()
//Set your account as the client's operator
client.SetOperator(operatorId, operatorKey)
// Set default max transaction fee
client.SetDefaultMaxTransactionFee(hedera.HbarFrom(10, hedera.HbarUnits.Hbar))
// Set max query payment
client.setDefaultMaxQueryPayment(hedera.HbarFrom(5, hedera.HbarUnits.Hbar))
```
```rust Rust theme={null}
// For test network (testnet)
let mut client = Client::for_testnet();
// Set your account as the client's operator
client.set_operator(my_account_id, my_private_key);
// Set the default maximum transaction fee (in Hbar)
client.set_default_max_transaction_fee(Hbar::from(10));
// Set the maximum payment for queries (in Hbar)
client.set_default_max_query_payment(Hbar::from(5));
// v0.34.0
```
# HBAR
Source: https://docs.hedera.com/native/fundamentals/hbars
| **Constructor** | **Type** | **Description** |
| -------------------- | -------- | --------------------------- |
| `new Hbar()` | Hbar | Initializes the Hbar object |
```java theme={null}
new Hbar()
```
## HBAR from:
Construct ***HBAR*** from different representations.
| **Method** | **Type** | **Description** |
| ------------------------------- | ---------------------------- | --------------------------------------------------------------- |
| `Hbar.from()` | long / BigDecimal | Returns an Hbar whose value is equal to the specified value |
| `Hbar.from()` | long / BigDecimal , HbarUnit | Returns an Hbar representing the value in the given units |
| `Hbar.fromString()` | CharSequence | Converts the provided string into an amount of hbars |
| `Hbar.fromString()` | CharSequence, HbarUnit | Converts the provided string into an amount of hbars |
| `Hbar.fromTinybars()` | long | Returns an Hbar converted from the specified number of tinybars |
```java Java theme={null}
//10 HBAR
new Hbar(10);
//10 HBAR from hbar value
Hbar.from(10);
//100 tinybars from HBAR convert to unit
Hbar.from(100, HbarUnit.TINYBAR);
// 10 HBAR converted from string value
Hbar.fromString("10");
//100 tinybars from string value
Hbar.fromString("10", HbarUnit.TINYBAR);
// v2.0.0+
```
```javascript JavaScript theme={null}
// 10 HBAR
new Hbar(10);
//10 HBAR
Hbar.from(10);
//100 tinybars
Hbar.from(100, HbarUnit.TINYBAR);
// 10 HBAR converted from string value
Hbar.fromString("10");
//100 tinybars from string value
Hbar.fromString("100", HbarUnit.TINYBAR);
```
```go Go theme={null}
//100 HBAR
hedera.NewHbar(10)
//100 tinybars
hedera.HbarFrom(10, hedera.HbarUnits.Tinybar)
// v2.0.0+
```
```rust Rust theme={null}
// 10 HBAR
Hbar::new(10);
// 10 HBAR from hbar value
Hbar::from(10);
// 100 tinybars from HBAR convert to unit
Hbar::from_tinybars(100);
// 10 HBAR converted from string value
Hbar::from_str("10")?;
// v0.34.0
```
### HBAR to:
Convert ***HBAR*** to a different unit/format.
| **Method** | **Type** | **Description** |
| ------------------ | -------- | ------------------------------------------------------------------- |
| `to()` | HbarUnit | Specify the unit of hbar to convert to. Use `As` for Go. |
| `toString()` | HbarUnit | String value of the hbar unit to convert to. Use `String()` for Go. |
| `toTinybars()` | Long | Hbar value converted to tinybars |
```java Java theme={null}
//10 HBAR converted to tinybars
new Hbar(10).to(HbarUnit.TINYBAR);
//10 HBAR converted to tinybars
new Hbar(10).toString(HbarUnit.TINYBAR);
//10 HBAR converted to tinybars
new Hbar(10).toTinybars();
// v2.0.0+
```
```javascript JavaScript theme={null}
//10 HBAR converted to tinybars
new Hbar(10).to(HbarUnit.TINYBAR);
//10 HBAR converted to tinybars
new Hbar(10).toString(HbarUnit.TINYBAR);
//10 HBAR converted to tinybars
new Hbar(10).toTinybars();
```
```go Go theme={null}
//10 HBAR converted to tinybars
hedera.NewHbar(10).As(hedera.HbarUnits.Tinybar)
//10 HBAR to string format
hedera.NewHbar(10).String()
//10 HBAR converted to tinybars
hedera.NewHbar(10).AsTinybar()
// v2.0.0+
```
```rust Rust theme={null}
// 10 HBAR converted to tinybars
Hbar::new(10).to(HbarUnit::Tinybar);
// 10 HBAR converted to tinybars
Hbar::new(10).to_tinybars();
// v0.34.0
```
## **HBAR** constants:
Provided constant values of ***HBAR***.
| **Method** | **Type** | **Description** |
| ----------- | -------- | -------------------------------------------------------------------------- |
| `Hbar.MAX` | Hbar | A constant value of the maximum number of hbars (50\_000\_000\_000 hbars) |
| `Hbar.MIN` | Hbar | A constant value of the minimum number of hbars (-50\_000\_000\_000 hbars) |
| `Hbar.ZERO` | Hbar | A constant value of zero hbars |
```java Java theme={null}
//The maximum number of hbars
Hbar hbarMax = Hbar.MAX;
//The minimum number of hbars
Hbar hbarMin = Hbar.MIN;
//A constant value of zero hbars
Hbar hbarZero = Hbar.ZERO;
// v2.0.0+
```
```javascript JavaScript theme={null}
//The maximum number of hbars
const hbarMax = Hbar.MAX;
//The minimum number of hbars
const hbarMin = Hbar.MIN;
//A constant value of zero hbars
const hbarZero = Hbar.ZERO;
```
```go Go theme={null}
//The maximum number of hbars
hbarMax := hedera.MaxHbar
//The minimum number of hbars
hbarMin := hedera.MinHbar
//A constant value of zero hbars
hbarZero := hedera.ZeroHbar
// v2.0.0+
```
```rust Rust theme={null}
// The maximum number of hbars
let hbar_max = Hbar::MAX;
// The minimum number of hbars
let hbar_min = Hbar::MIN;
// A constant value of zero hbars
let hbar_zero = Hbar::ZERO;
// v0.34.0
```
## **HBAR** units
Modify the ***HBAR*** representation to one of the ***HBAR*** denominations.
| **Function** | **Description** |
| ------------------- | ----------------------------------------------------------------------- |
| `HbarUnit.TINYBAR` | The atomic (smallest) unit of hbar, used natively by the Hedera network |
| `HbarUnit.MICROBAR` | Equivalent to 100 tinybar or 1⁄1,000,000 hbar. |
| `HbarUnit.MILLIBAR` | Equivalent to 100,000 tinybar or 1⁄1,000 hbar |
| `HbarUnit.HBAR` | The base unit of hbar, equivalent to 100 million tinybar. |
| `HbarUnit.KILOBAR` | Equivalent to 1 thousand hbar or 100 billion tinybar.HbarUnit.Megabar |
| `HbarUnit.MEGABAR` | Equivalent to 1 million hbar or 100 trillion tinybar. |
| `HbarUnit.GIGABAR` | Equivalent to 1 billion hbar or 100 quadrillion tinybar. |
```java Java theme={null}
//100 tinybars
Hbar.from(100, HbarUnit.TINYBAR);
// v2.0.0+
```
```javascript JavaScript theme={null}
//100 tinybars
Hbar.from(100, HbarUnit.TINYBAR);
// v2.0.0+
```
```go Go theme={null}
//100 tinybars
hedera.HbarFrom(100, hedera.HbarUnits.Tinybar)
// v2.0.0+
```
## HBAR decimal places
The decimal precision of ***HBAR*** varies across the different Hedera APIs. While HAPI, JSON-RPC Relay, and Hedera Smart Contract Service (EVM) provide 8 decimal places, the **`msg.value`** in JSON-RPC Relay provides 18 decimal places.
API
Decimal
Hedera API (HAPI) (Crypto + SCS Service (msg.value))
8
Hedera Smart Contract Service (EVM)
8
JSON RPC Relay (passed as arguments)
8
JSON RPC Relay (msg.value)
18
***Note:** The JSON-RPC Relay **`msg.value`** uses 18 decimals when it returns HBAR. As a result, the **`gasPrice`** also uses 18 decimal places since it is only utilized from the JSON-RPC Relay.*
# SDKs
Source: https://docs.hedera.com/native/fundamentals/index
Community SDKs, wallets, decentralized identity, and REST options that complement the official Hedera SDKs.
Six official SDKs (JavaScript, Java, Go, Swift, Rust, C++) are listed on the [Native SDKs landing page](/native#pick-your-language). This page catalogs community SDKs, wallet integrations, decentralized identity libraries, and REST options that supplement them.
**React Native:** The JavaScript SDK supports React Native with Expo on Android devices and emulators. React Native Bare is not currently supported.
***
## Try without installing
Use the [Developer Playground](https://portal.hedera.com/playground) to exercise account, token, and consensus operations on testnet directly from your browser, no SDK install required.
***
## Community language SDKs
Community-maintained SDKs that extend Hedera support beyond the six official languages.
`pip install hiero-sdk-python`. Community-maintained, Apache 2.0.
***
## Wallet & auth integrations
Connect end-user wallets and embedded auth flows to your Hedera dApp.
Connect to HashPack via the HashConnect protocol. MIT.
Hedera WalletConnect implementation maintained by Kabila. Apache 2.0.
Tutorial: build a Hedera dApp that connects to MetaMask via WalletConnect.
Email/social auth and embedded wallet creation for Hedera. MIT.
***
## Decentralized identity
W3C-compliant [DID Documents](https://www.w3.org/TR/did-core/) and a [Verifiable Credentials](https://www.w3.org/TR/vc-data-model/) registry backed by the Hedera Consensus Service.
Reference Java implementation. Apache 2.0.
TypeScript-friendly DID and VC management. Apache 2.0.
Python implementation maintained under Hiero. Apache 2.0.
***
## REST & serverless
If you'd rather call HTTP than embed an SDK.
Serverless REST wrapper around the Hedera API. Apache 2.0. [GitHub](https://github.com/trust-enterprises/hedera-rest-api).
Laravel bindings on top of the Trust Enterprises REST API. Apache 2.0. [GitHub](https://github.com/trust-enterprises/hedera-laravel).
***
Building something that should live here? Open a PR or drop into [Discord](https://hedera.com/discord).
# Local Network
Source: https://docs.hedera.com/native/fundamentals/local-network
**Hiero Local Node Deprecation (September 2026)**
Hiero Local Node is entering a 6-month deprecation period. Support ends September 2026. Migrate local testing and CI workflows to [Solo](https://solo.hiero.org/docs/) before then. [Learn more](https://hedera.com/blog/hiero-local-node-deprecation-6-month-transition-to-solo/).
While you are developing your application, you can use the Hedera-supported networks (previewnet and testnet) to test against. You can also run your own local network — a consensus node and mirror node on your machine — for development without testnet rate limits, resets, or faucet dependencies.
With a local network set up, you can:
* Create and submit transactions and queries to a consensus node
* Interact with the mirror node via REST APIs
## Recommended: Solo
[Solo](https://solo.hiero.org/docs/) is the recommended way to run a local Hedera network. It deploys a full stack — consensus node, mirror node, JSON-RPC relay, and block explorer — and is compatible with all Hiero SDKs.
Install Solo and start a local network.
Point the JavaScript, Java, or Go SDK at your Solo network.
The SDK connection pattern is the same as shown below — `Client.forNetwork()` and `Client.setMirrorNetwork()` — only the endpoint addresses differ. See [Using Solo with Hiero SDKs](https://solo.hiero.org/docs/using-solo/using-solo-with-hiero-sdks/) for the host and port values for your Solo deployment.
## Local Node (deprecated)
The walkthrough below uses Hiero Local Node, which is in a 6-month deprecation period ending September 2026. The steps remain valid until then, but new projects should use [Solo](#recommended-solo) instead.
### 1. Set Up your local network
Set-up your local network by following the instructions found in the [readme](https://github.com/hiero-ledger/hiero-local-node#docker) of the `hedera-local-node` project. This will create a Hedera network composed of one consensus node and one mirror node. The consensus node will process incoming transactions and queries. The mirror node stores the history of transactions. Both nodes are created at startup.
### 2. Configure your network
Once you have your local network up and running, you will need to configure your Hedera client to point to your local network in your project of choice. Your project should have your language specific Hedera SDK as a dependency and imported into your project. You may reference the environment setup instructions if you don't know how.
Your local network IP address and port will be `127.0.0.1:50211` and your local mirror node IP and port will be `127.0.0.1:5600`. The consensus node account ID is `0.0.3`. This is the node account ID that will receive your transaction and query requests. It is recommended to store these variables in an environment or config file. These values will be hard-coded in the example for demonstration purposes.
Configure your local network by using `Client.forNetwork()`. This allows you to set a custom consensus network by providing the IP address and port. `Client.setMirrorNetwork()` allows you to set a custom mirror node network by providing the IP address and port.
```java Java theme={null}
//Create your local client
Client client = Client.forNetwork(Collections.singletonMap("127.0.0.1:50211", AccountId.fromString("0.0.3"))).setMirrorNetwork(List.of("127.0.0.1:5600"));
```
```javascript JavaScript theme={null}
//Create your local client
const node = {"127.0.0.1:50211": new AccountId(3)};
const client = Client.forNetwork(node).setMirrorNetwork("127.0.0.1:5600");
```
```go Go theme={null}
//Create your local client
node := make(map[string]hedera.AccountID, 1)
node["127.0.0.1:50211"] = hedera.AccountID{Account: 3}
mirrorNode := []string{"127.0.0.1:5600"}
client := hedera.ClientForNetwork(node)
client.SetMirrorNetwork(mirrorNode)
```
### 3. Set your local node transaction fee paying account
You will need an account ID and key to pay for the [fees](/networks/fees) associated with each transaction and query that is submitted to your local network. You will use the account ID and key provided by the local node on startup to set up your operator account ID and key. The operator is the default account that pays for transaction and query fees.
| **Account ID** | `0.0.2` |
| --------------- | -------------------------------------------------------------------------------------------------- |
| **Private Key** | `302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137` |
**Note**: It is not good practice to post your private keys in any public place. These keys are provided only for development and testing purposes only. They do not exist on any production networks.
```java Java wrap theme={null}
client.setOperator(AccountId.fromString("0.0.2"), PrivateKey.fromString("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"));
```
```javascript JavaScript wrap theme={null}
client.setOperator(AccountId.fromString("0.0.2"),PrivateKey.fromString("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"));
```
```go Go wrap theme={null}
accountId, err := hedera.AccountIDFromString("0.0.2")
privateKey, err := hedera.PrivateKeyFromString(
"302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137")
client.SetOperator(accountId, privateKey)
```
### 4. Submit your transaction
Submit a transaction that will create a new account in your local network. The console should print out the new account ID. In this example, we are using the same key as the transaction fee paying account as the key for the new account. You can also create a [new key](/native/keys/generate-key-pair) if you wish.
```java Java theme={null}
//Submit a transaction to your local node
TransactionResponse newAccount = new AccountCreateTransaction()
.setKeyWithoutAlias(PrivateKey.fromString("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"))
.setInitialBalance(new Hbar(1))
.execute(client);
//Get the receipt
TransactionReceipt receipt = newAccount.getReceipt(client);
//Get the account ID
AccountId newAccountId = receipt.accountId;
System.out.println(newAccountId);
```
```javascript JavaScript theme={null}
//Submit a transaction to your local node
const newAccount = await new AccountCreateTransaction()
.setKeyWithoutAlias(PrivateKey.fromString("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"))
.setInitialBalance(new Hbar(1))
.execute(client);
//Get receipt
const receipt = await newAccount.getReceipt(client);
//Get the account ID
const newAccountId = receipt.accountId;
console.log(newAccountId);
```
```go Go theme={null}
//Submit a transaction to your local node
newAccount, err := hedera.NewAccountCreateTransaction().
SetKeyWithoutAlias(privateKey).
SetInitialBalance(hedera.NewHbar(1)).
Execute(client)
if err != nil {
println(err.Error(), ": error getting balance")
return
}
//Get receipt
receipt, err := newAccount.GetReceipt(client)
//Get the account ID
newAccountId := receipt.AccountID
fmt.Print(newAccountId)
```
### 5. View your transaction
You can view the executed transaction by querying your local mirror node.
The local mirror node endpoint URL is `http://localhost:5551/`.
You can view the transactions that were submitted to your local node by submitting this request:
```http theme={null}
http://localhost:5551/api/v1/transactions
```
The list of supported mirror node REST APIs can be found [here](/reference/rest-api). You have now set-up your local environment. Check out the following links for more examples.
### Code Check ✅
```java Java wrap theme={null}
import com.hedera.hashgraph.sdk.*;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;
public class LocalNode {
public static void main(String[] args) throws TimeoutException, PrecheckStatusException, ReceiptStatusException, InterruptedException, IOException {
//Create your local client
Client client = Client.forNetwork(Collections.singletonMap("127.0.0.1:50211", AccountId.fromString("0.0.3"))).setMirrorNetwork(List.of("127.0.0.1:5600"));
//Set the transaction fee paying account
client.setOperator(AccountId.fromString("0.0.2"), PrivateKey.fromString("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"));
//Submit a transaction to your local node
TransactionResponse newAccount = new AccountCreateTransaction()
.setKeyWithoutAlias(PrivateKey.fromString("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"))
.setInitialBalance(new Hbar(1))
.execute(client);
//Get the receipt
TransactionReceipt receipt = newAccount.getReceipt(client);
//Get the account ID
AccountId newAccountId = receipt.accountId;
System.out.println(newAccountId);
}
}
```
```javascript JavaScript theme={null}
const {
Client,
PrivateKey,
Hbar,
AccountId,
AccountCreateTransaction,
} = require("@hashgraph/sdk");
async function main() {
//Create your local client
const node = {"127.0.0.1:50211": new AccountId(3)}
const client = Client.forNetwork(node).setMirrorNetwork("127.0.0.1:5600");
//Set the transaction fee paying account
client.setOperator(AccountId.fromString("0.0.2"),PrivateKey.fromString("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"));
//Submit a transaction to your local node
const newAccount = await new AccountCreateTransaction()
.setKeyWithoutAlias(PrivateKey.fromString("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137"))
.setInitialBalance(new Hbar(1))
.execute(client);
//Get receipt
const receipt = await newAccount.getReceipt(client);
//Get the account ID
const newAccountId = receipt.accountId;
console.log(newAccountId);
}
void main();
```
```go Go theme={null}
package main
import (
"fmt"
hedera "github.com/hiero-ledger/hiero-sdk-go/v2/sdk"
)
func main() {
//Create your local node client
node := make(map[string]hedera.AccountID, 1)
node["127.0.0.1:50211"] = hedera.AccountID{Account: 3}
mirrorNode := []string{"127.0.0.1:5600"}
client := hedera.ClientForNetwork(node)
client.SetMirrorNetwork(mirrorNode)
//Set the transaction fee paying account
accountId, err := hedera.AccountIDFromString("0.0.2")
privateKey, err := hedera.PrivateKeyFromString("302e020100300506032b65700422042091132178e72057a1d7528025956fe39b0b847f200ab59b2fdd367017f3087137")
client.SetOperator(accountId, privateKey)
//Submit a transaction to your local node
newAccount, err := hedera.NewAccountCreateTransaction().
SetKeyWithoutAlias(privateKey).
SetInitialBalance(hedera.NewHbar(1)).
Execute(client)
if err != nil {
println(err.Error(), ": error getting balance")
return
}
//Get receipt
receipt, err := newAccount.GetReceipt(client)
//Get the account ID
newAccountId := receipt.AccountID
fmt.Print(newAccountId)
}
```
# Specialized Types
Source: https://docs.hedera.com/native/fundamentals/specialized-types
## [AccountId](https://github.com/hashgraph/hedera-sdk-java/blob/master/src/main/java/com/hedera/hashgraph/sdk/account/AccountId.java)
An `AccountId` is composed of a \.\.\ (eg. 0.0.10).
* Shard number (`shardNum`**)** represents the shard number (`shardId`). It will default to 0 today, as Hedera only performs in one shard.
* Realm number (`realmNum`) represents the realm number (`realmId`). It will default to 0 today, as realms are not yet supported.
* Account represents either an account number or an account alias
* Account number (`accountNum`) represents the account number (`accountId`)
* Account alias (alias) represented by the public key bytes
* The public key bytes are the result of serializing a protobuf Key message for any primitive key type
* Currently, only primitive key bytes are supported as an alias
* Threshold keys, key list, contract ID, and delegatable\_contract\_id are not supported
* The alias can only be used in place of an account ID in transfer transactions in its current version
Together these values make up your `AccountId`. When an `AccountId` is specified, be sure all three values are included.
### Constructor
Constructor
Type
Description
new AccountId(\,\,\)
long, long, long
Constructs an AccountId with 0 for shardNum and realmNum (e.g., 0.0.\)
### Methods
Methods
Type
Description
AccountId.fromString(\)
String
Constructs an AccountId from a string formatted as \.\.\
AccountId.fromEvmAddress(\)
String
Constructs an AccountId from a solidity address in string format
AccountId.fromBytes(bytes)
byte\[]
Constructs an AccountId from bytes
AccountId.toSolidityAddress()
String
Constructs a solidity address from AccountID
AccountId.toString()
String
Constructs an AccountID from string
AccountId.aliasKey
PublicKey
The alias key of the AccountID
AccountId.aliasEvmAddress
EVM address
The EVM address of the AccountID
AccountId.toBytes()
byte\[]
Constructs an AccountID from bytes
### Example
```java Java theme={null}
AccountId accountId = new AccountId(0 ,0 ,10);
System.out.println(accountId);
// Constructs an accountId from String
AccountId accountId = AccountId.fromString("0.0.10");
System.out.println(accountId);
```
```javascript JavaScript theme={null}
const accountId = new AccountId(100);
console.log(`${accountId}`);
// Construct accountId from String
const accountId = AccountId.fromString(`100`);
console.log(`${accountId}`);
```
```go Go theme={null}
hedera.AccountIDFromString("0.0.3")
```
## [FileId](https://github.com/hashgraph/hedera-sdk-java/blob/master/src/main/java/com/hedera/hashgraph/sdk/file/FileId.java)
A `FileId` is composed of a \.\.\ (eg. 0.0.15).
* **shardNum** represents the shard number (`shardId`). It will default to 0 today, as Hedera only performs in one shard.
* **realmNum** represents the realm number (`realmId`). It will default to 0 today, as realms are not yet supported.
* **fileNum** represents the file number
Together these values make up your accountId. When an `FileId` is requested, be sure all three values are included.
### Constructor
Constructor
Type
Description
new FileId(\,\,\)
long, long, long
Constructs a FileId with 0 for shardNum and realmNum (e.g., 0.0.\)
### Methods
Methods
Type
Description
FileId.fromString()
String
Constructs an FileId from a string formatted as
\.\.\
FileId.fromSolidityAddress()
String
Constructs an FileId from a solidity address in string format
FileId.ADDRESS\_BOOK
FileId
The public node address book for the current network
FileId.EXCHANGE\_RATES
FileId
The current exchange rate of HBAR to USD
FileId.FEE\_SCHEDULE
FileId
The current fee schedule for the network
### Example
```java Java theme={null}
FileId fileId = new FileId(0,0,15);
System.out.println(fileId);
//Constructs a FileId from string
FileId fileId = FileId.fromString("0.0.15");
System.out.println(fileId);
```
```javascript JavaScript theme={null}
const newFileId = new FileId(100);
console.log(`${newFileId}`);
//Construct a fileId from a String
const newFileIdFromString = FileId.fromString(`100`);
console.log(`${newFileIdFromString}`);
```
```go Go theme={null}
hedera.FileIDFromString("0.0.3")
```
## [ContractId](https://github.com/hashgraph/hedera-sdk-java/blob/master/src/main/java/com/hedera/hashgraph/sdk/contract/ContractId.java)
A `ContractId` is composed of a \.\.\ (eg. 0.0.20).
* **shardNum** represents the shard number (`shardId`). It will default to 0 today, as Hedera only performs in one shard.
* **realmNum** represents the realm number (`realmId`). It will default to 0 today, as realms are not yet supported.
* **contractNum** represents the contract number
Together these values make up your `ContractId`. When an `ContractId` is requested, be sure all three values are included. ContractId's are automatically assigned when you create a new smart contract.
### Constructor
| **Constructor** | **Type** | **Description** |
| ----------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------ |
| `new ContractId(,,)` | long, long, long | Constructs a `ContractId` with 0 for `shardNum` and `realmNum` (e.g., `0.0.`) |
### Methods
Methods
Type
Description
ContractId.fromString(\)
String
Constructs a ContractId from a string formatted as
\.\.\
ContractId.fromSolidityAddress(\)\[deprecated use ConractId.fromEvmAddress()]
String
Constructs a ContractId from a solidity address in string format \[deprecated use ContractId.fromEvmAddres()]
ContractId.toSolidityAddress(\)
String
Contruct a Solidity address from a Hedera contract ID
ContractId.fromEvmAddress(\, \, \)
long, long, String
Constructs a ContractId from evm address
### Example
```java Java theme={null}
ContractId contractId = new ContractId(0,0,20);
System.out.println(contractId);
// Constructs a ContractId from string
ContractId contractId = ContractId.fromString("0.0.20");
System.out.println(contractId);
```
```javascript JavaScript theme={null}
const newContractId = new ContractId(100);
console.log(`${newContractId}`);
// Construct a contractId from a String
const newContractId = ContractId.fromString(`100`);
console.log(`${newContractId}`);
```
## [TopicId](https://github.com/hashgraph/hedera-sdk-java/blob/master/src/main/java/com/hedera/hashgraph/sdk/consensus/ConsensusTopicId.java)
A `topicId` is composed of a \.\.\ (eg. 0.0.100).
* **shardNum** represents the shard number (`shardId`). It will default to 0 today, as Hedera only performs in one shard.
* **realmNum** represents the realm number (`realmId`). It will default to 0 today, as realms are not yet supported.
* **topicNum** represents the topic number (`topicId`)
### Constructor
Constructor
Type
Description
new ConsensusTopicId(\,\,\)
long, long, long
Constructs a TopicId with 0 for shardNum and realmNum (e.g., 0.0.\)
Methods
Type
Description
fromString(\)
String
Constructs a topic ID from a String
ConsensusTopicId.toString()
Constructs a topic ID to String format
### Example
```java Java theme={null}
ConsensusTopicId topicId = new ConsensusTopicId(0,0,100);
System.out.println(topicId)
```
```javascript JavaScript theme={null}
const topicId = new ConsensusTopicId(0,0,100);
console.log(topicId)
```
```go Go theme={null}
hedera.TopicIDFromString("0.0.3")
```
# Native SDKs
Source: https://docs.hedera.com/native/index
Build on Hedera using the JavaScript, Java, Go, Swift, and Rust SDKs: full access to HTS, HCS, scheduled transactions, smart contracts, and the file service.
# Generate a new key pair
Source: https://docs.hedera.com/native/keys/generate-key-pair
Generate ECDSA secp256k1 or Ed25519 key pairs with the Hiero SDKs to sign Hedera transactions, derive public keys, and set EVM-compatible account addresses.
**ECDSA is recommended for new accounts and applications.** It enables setting an **EVM Address from Public Key** on the account, native compatibility with EVM tooling, and `msg.sender` resolution in Solidity. See [Keys and Signatures](/learn/core-concepts/keys) for the full comparison.
## ECDSA (secp256k1\_)
Create a new ***ECDSA*** (secp256k1) key pair used to sign transactions and queries on a Hedera network. The private key is kept confidential and is used to sign transactions that modify the state of an account, topic, token, smart contract, or file entity on the network. The public key can be shared with other users on the network.
| **Method** | **Type** | **Description** |
| ---------------------------------------------------------------------- | :--------: | ----------------------------------------------- |
| `PrivateKey.generateECDSA()` | PrivateKey | Generates an ECSDA private key |
| `PrivateKey.generateECDSA().getPublicKey()` | PublicKey | Derive a public key from this ECDSA private key |
| `PrivateKey.generateECDSA().publicKey()` | PublicKey | Derive a public key from this ECDSA private key |
| `PrivateKey.generateECDSA().publicKey().toAccountId(, )` | long, long | Constructs an account ID from a public key |
```java Java theme={null}
PrivateKey privateKey = PrivateKey.generateECDSA();
PublicKey publicKey = privateKey.getPublicKey();
System.out.println("private key = " + privateKey);
System.out.println("public key = " + publicKey);
System.out.println("evm address = " + publicKey.toEvmAddress());
```
```javascript JavaScript theme={null}
const privateKey = await PrivateKey.generateECDSAAsync();
const publicKey = privateKey.publicKey;
console.log("private key = " + privateKey);
console.log("public key = " + publicKey);
console.log("evm address = " + publicKey.toEvmAddress());
```
```go Go theme={null}
privateKey, err := hedera.GenerateEcdsaPrivateKey()
if err != nil {
panic(err)
}
publicKey := privateKey.PublicKey()
fmt.Printf("private key = %v\n", privateKey)
fmt.Printf("public key = %v\n", publicKey)
fmt.Printf("evm address = %v\n", publicKey.ToEvmAddress())
```
```rust Rust theme={null}
// Generate ECDSA key pair
let private_key = PrivateKey::generate_ecdsa()?;
let public_key = private_key.public_key();
println!("private key = {:?}", private_key);
println!("public key = {:?}", public_key);
println!("evm address = {:?}", public_key.to_evm_address());
// v0.34.0
```
```python Python theme={null}
from hiero_sdk_python.crypto.private_key import PrivateKey
private_key = PrivateKey.generate_ecdsa()
public_key = private_key.public_key()
print(f"private key = {private_key}")
print(f"public key = {public_key}")
print(f"evm address = {public_key.to_evm_address()}")
```
**Sample Output:**
```bash wrap theme={null}
private key = 3030020100300706052b8104000a042204205edee337f333dbf6ae3e0b1e1298cbe1fe37faf47b9faed326a3917b0e828891
public key = 302d300706052b8104000a03220002e1f3b3bad1e0d97ed129725efaa4fc0289735a9f355438d2619676f1ff3f30dd
```
## ED25519
Create a new ***ED25519*** key pair used to sign transactions and queries on the Hedera network. The private key is kept confidential and is used to sign transactions that modify the state of an account, topic, token, smart contract, or file entity on the network. The public key can be shared with other users on the network.
`PrivateKey.generate()` is **deprecated** and silently generates an ED25519 key. Use `PrivateKey.generateECDSA()` for new accounts (recommended) or `PrivateKey.generateED25519()` if ED25519 is required.
| **Method** | **Type** | **Description** |
| ----------------------------------------------------------------------- | :--------: | ------------------------------------------------------------ |
| `PrivateKey.generateED25519()` | PrivateKey | Generates an Ed25519 private key |
| `PrivateKey.generateED25519().getPublicKey()` | PublicKey | Derive a public key from this Ed25519 private key |
| `PrivateKey.generateED25519().publicKey()` | PublicKey | Derive a public key from this Ed25519 private key |
| `PrivateKey.generateED25519().publicKey().toAccountId(,)` | long, long | Contruct an alias account ID from a alias public key address |
```java Java theme={null}
PrivateKey privateKey = PrivateKey.generateED25519();
PublicKey publicKey = privateKey.getPublicKey();
System.out.println("private key = " + privateKey);
System.out.println("public key = " + publicKey);
```
```javascript JavaScript theme={null}
const privateKey = await PrivateKey.generateED25519Async();
const publicKey = privateKey.publicKey;
console.log("private key = " + privateKey);
console.log("public key = " + publicKey);
```
```go Go theme={null}
privateKey, err := hedera.GenerateEd25519PrivateKey()
if err != nil {
panic(err)
}
publicKey := privateKey.PublicKey()
fmt.Printf("private key = %v\n", privateKey)
fmt.Printf("public key = %v\n", publicKey)
```
```rust Rust theme={null}
// Generate ED25519 key pair
let private_key = PrivateKey::generate_ed25519()?;
let public_key = private_key.public_key();
println!("private key = {:?}", private_key);
println!("public key = {:?}", public_key);
// Generate ECDSA key pair
let private_key = PrivateKey::generate_ecdsa()?;
let public_key = private_key.public_key();
println!("private key = {:?}", private_key);
println!("public key = {:?}", public_key);
// v0.34.0
```
**Sample Output:**
```bash wrap theme={null}
private key = 302e020100300506032b657004220420b9c3ebac81a72aafa5490cc78111643d016d311e60869436fbb91c73307ed35a
public key = 302a300506032b65700321001a5a62bb9f35990d3fea1a5bb7ef6f1df0a297697adef1e04510c9d4ecc5db3f
```
# Import an existing key
Source: https://docs.hedera.com/native/keys/import-key
Construct keys in another format to a key representation or import keys from a file.
Method
Type
Description
PrivateKey.fromString(\)
String
Constructs a private key string to PrivateKey
PublicKey.fromString(\)
String
Constructs a public key string to PublicKey
PrivateKey.fromStringECDSA(\)
String
Constructs an ECDSA key from a private key string
PublicKey.fromStringECDSA(\)
String
Constructs an ECDSA public key from a public key string
PrivateKey.fromBytesECDSA(\)
byte\[ ]
Constructs an ECDSA key from a private key bytes
PublicKey.fromBytesECDSA(\)
byte\[ ]
Constructs an ECDSA public key from a public key bytes
PrivateKey.fromStringED25519(\)
String
Constructs an ED25519 key from a private key string
PublicKey.fromStringED25519(\)
String
Constructs an ED25519 public key from a public key string
PrivateKey.fromBytesED25519(\)
byte \[ ]
Constructs an ED25519 key from a private key bytes
PublicKey.fromBytesED25519(\)
byte \[ ]
Constructs an ED25519 public key from a public key bytes
PrivateKey.fromBytes(\)
byte \[ ]
Constructs a private key from bytes to PrivateKey
PublicKey.fromBytes(\)
byte \[ ]
Contructs a public key from bytes to PublicKey
PrivateKey.fromPem(\)
String
Parse a private key from a PEM encoded string
PrivateKey.fromPem(\)
String, String
Parse a private key from a PEM encoded string. The private key may be encrypted, e.g. if it was generated by OpenSSL.
PrivateKey.readPem(\)
Reader
Parse a private key from a PEM encoded reader
PrivateKey.readPem(\)
Reader, String
Parse a private key from a PEM encoded stream. The key may be encrypted, e.g. if it was generated by OpenSSL.
```java Java theme={null}
//Converts an ECDSA private key string to PrivateKey
PrivateKey privateKey = PrivateKey.fromStringECDSA("3030020100300706052b8104000a042204208776c6b831a1b61ac10dac0304a2843de4716f98b09147a169f41d7b4d48ad5");
//The public key associated with the private key
PublicKey publicKey = PublicKey.fromStringECDSA("3036301006072a8648ce3d020106052b8104000a032200029bba954cd41e8ab9f05a0b3b038b1c2c35b9bc16b4e38bc67a2e5ebab2fe72fb");
```
```javascript JavaScript theme={null}
//Converts an ECDSA private key string to PrivateKey
const privateKey = PrivateKey.fromStringECDSA("3030020100300706052b8104000a042204208776c6b831a1b61ac10dac0304a2843de4716f98b09147a169f41d7b4d48ad5");
//The public key associated with the private key
const publicKey = PublicKey.fromStringECDSA("3036301006072a8648ce3d020106052b8104000a032200029bba954cd41e8ab9f05a0b3b038b1c2c35b9bc16b4e38bc67a2e5ebab2fe72fb");
```
```go Go theme={null}
//Converts an ECDSA private key string to PrivateKey
privateKey, err := hedera.PrivateKeyFromStringECDSA("3030020100300706052b8104000a042204208776c6b831a1b61ac10dac0304a2843de4716f98b09147a169f41d7b4d48ad5")
if err != nil {
panic(err)
}
//The public key associated with the private key
publicKey, err := hedera.PublicKeyFromStringECDSA("3036301006072a8648ce3d020106052b8104000a032200029bba954cd41e8ab9f05a0b3b038b1c2c35b9bc16b4e38bc67a2e5ebab2fe72fb")
if err != nil {
panic(err)
}
```
```rust Rust theme={null}
// Import ECDSA keys from strings
let private_key = PrivateKey::from_str("3030020100300706052b8104000a042204208776c6b831a1b61ac10dac0304a2843de4716f98b09147a169f41d7b4d48ad5");
let public_key = PublicKey::from_str("3036301006072a8648ce3d020106052b8104000a032200029bba954cd41e8ab9f05a0b3b038b1c2c35b9bc16b4e38bc67a2e5ebab2fe72fb");
// Import from PEM
let private_key = PrivateKey::from_pem("-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----");
// With password if encrypted
let private_key = PrivateKey::from_pem_with_password("-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----", "password");
println!("Imported private key: {:?}", private_key);
println!("Imported public key: {:?}", public_key);
// v0.34.0
```
# Create a key list
Source: https://docs.hedera.com/native/keys/key-list
Create a key list key structure where all the keys in the list are required to sign transactions that modify accounts, topics, tokens, smart contracts, or files. A key list can contain a [Ed25519](/native/keys/generate-key-pair#ed25519) or [ECDSA](/native/keys/generate-key-pair#ecdsa-secp256k1) (secp256k1\_)\_ key type.
If all the keys in the key list key structure do not sign, the transaction will fail and return an "INVALID\_SIGNATURE" error. A key list can have repeated keys. A signature for the repeated key will count as many times as the key is listed in the key list. For example, a key list has three keys. Two of the three public keys in the list are the same. When a user signs a transaction with the repeated key it will account for two out of the three keys required signature.
Method
Type
Description
KeyList.of(\)
Key
Keys to add to the key list
```java theme={null}
//Generate 3 keys
PrivateKey key1 = PrivateKey.generate();
PublicKey publicKey1 = key1.getPublicKey();
PrivateKey key2 = PrivateKey.generate();
PublicKey publicKey2 = key2.getPublicKey();
PrivateKey key3 = PrivateKey.generate();
PublicKey publicKey3 = key3.getPublicKey();
//Create a key list where all 3 keys are required to sign
KeyList keyStructure = KeyList.of(key1, key2, key3);
System.println(keyStructure)
//v2.0.0
```
**Sample Output**
```
KeyList{threshold=null,
keys=[302e020100300506032b6570042204201cd556de918842179791d9edd75cdd2b5d34c5c73b0239ec0b34c67eedc020fd, 302e020100300506032b6570042204209ca1ce4463b71c72bba0219c37e18347a5145a9797c6546a6c99e50255c54be3, 302e020100300506032b657004220420982bb43f4947e8376e2f0ebfde086d24323b04d731da29446e5bc399ffbe06e1]
}
```
```java theme={null}
//Generate 3 keys
const key1 = PrivateKey.generate();
const publicKey1 = key1.publicKey;
const key2 = PrivateKey.generate();
const publicKey2 = key2.publicKey;
const key3 = PrivateKey.generate();
const publicKey3 = key3.publicKey;
//Create a list of the keys
const publicKeyList = [];
publicKeyList.push(publicKey1);
publicKeyList.push(publicKey2);
publicKeyList.push(publicKey3);
//Create a key list where all 3 keys are required to sign
const keys = new KeyList(publicKeyList);
//v2.0.13
```
```java theme={null}
//Generate 3 keys
key1, err := hedera.GeneratePrivateKey()
if err != nil {
panic(err)
}
publicKey1, err := key1.PublicKey()
key2, err := hedera.GeneratePrivateKey()
if err != nil {
panic(err)
}
publicKey2, err := key2.PublicKey()
key3, err := hedera.GeneratePrivateKey()
if err != nil {
panic(err)
}
publicKey3, err := key3.PublicKey()
//Create a key list where all 3 keys are required to sign
keys := make([]hedera.PublicKey, 3)
keys[0] = publicKey1
keys[1] = publicKey2
keys[2] = publicKey3
keyStructure := hedera.NewKeyList().AddAllPublicKeys(keys)
fmt.Printf("The key list is %v\n", keyStructure)
//v2.0.0
```
```rust theme={null}
let key_list = KeyList::new()
println!("The key list is {:?}", key_list);
// v0.34.0
```
# Generate a mnemonic phrase
Source: https://docs.hedera.com/native/keys/mnemonic-generate
Generate a 12 or 24-word mnemonic phrase that can be used to recover the private keys that are associated with it.
Method
Type
Description
Mnemonic.generate24()
Mnemonic
Generates a 24-word recovery phrase that can be used to recover a private key
Mnemonic.generate12()
Mnemonic
Generates a 12-word recovery phrase that can be used to recover a private key
```java Java theme={null}
// 24-word recovery phrase
Mnemonic mnemonic = Mnemonic.generate24();
System.out.println("mnemonic 24 word = " + mnemonic);
//12 word recovery phrase
Mnemonic mnemonic12 = Mnemonic.generate12();
System.out.println("mnemonic 12 word = " + mnemonic12);
//v2.0.0
```
```javascript JavaScript theme={null}
// generate a 24-word mnemonic
const mnemonic = await Mnemonic.generate();
console.log(mnemonic)
```
```java Go theme={null}
//Generate 24 word mnemonic
mnemonic24, err := hedera.GenerateMnemonic()
if err != nil {
panic(err)
}
privateKey, err := mnemonic24.ToPrivateKey( /* passphrase */ "")
if err != nil {
panic(err)
}
publicKey := privateKey.PublicKey()
fmt.Printf("mnemonic = %v\n", mnemonic)
//v2.0.0
```
# Recover keys from a mnemonic phrase
Source: https://docs.hedera.com/native/keys/mnemonic-recover
Recover private keys from a mnemonic phrase.
Method
Type
Description
PrivateKey.fromMnemonic(\)
Mnemonic
Recover a private key from a mnemonic phrase compatible with the iOS and Android wallets
PrivateKey.fromMnemonic(\)
Mnemonic. String
Recover a private key from a generated mnemonic phrase and a passphrase
```java Java theme={null}
//Use the mnemonic to recover the private key
PrivateKey privateKey = PrivateKey.fromMnemonic(mnemonic);
PublicKey publicKey = privateKey.publicKey();
//v2.0.0
```
```java JavaScript theme={null}
//Use a recovered mnemonic to recover the private key
const recoveredMnemonic = await Mnemonic.fromString(mnemonic.toString());
const privateKey = await recoveredMnemonic.toPrivateKey();
//v2.0.5
```
```java Go theme={null}
recoveredKey, err := hedera.PrivateKeyFromMnemonic(mnemonic, "")
publicKey := recoveredKey.PublicKey()
//v2.0.0
```
```rust Rust theme={null}
// Recover private key from mnemonic phrase with passphrase
let private_key = PrivateKey::from_mnemonic_with_passphrase(&mnemonic, "passphrase")?;
let public_key = private_key.public_key();
// v0.34.0
```
# Create a threshold key
Source: https://docs.hedera.com/native/keys/threshold-key
Create a key structure that requires the defined threshold value to sign. A threshold key can contain a [Ed25519](/native/keys/generate-key-pair#ed25519) or [ECDSA](/native/keys/generate-key-pair#ecdsa-secp256k1) (secp256k1\_)\_ key type. You can use either the public key or the private key to create the key structure. If the threshold requirement is not met when signing transactions, the network will return an "INVALID\_SIGNATURE" error.
| **Method** | **Type** | **Description** |
| ----------------------------------------- | -------- | -------------------------------------------------------------------------------------------------- |
| `KeyList.withThreshold()` | int | The number of keys required to sign transactions to modify the account i.e. transfers, update, etc |
```java Java expandable theme={null}
//Generate 3 keys
PrivateKey key1 = PrivateKey.generate();.
PublicKey publicKey1 = key1.getPublicKey();
PrivateKey key2 = PrivateKey.generate();
PublicKey publicKey2 = key2.getPublicKey();
PrivateKey key3 = PrivateKey.generate();
PublicKey publicKey3 = key3.getPublicKey();
PrivateKey[] keys = new PrivateKey[3]; //You can also use the 3 public keys here
keys[0] = key1;
keys[1] = key2;
keys[2] = key3;
//A key structure that requires one of the 3 keys to sign
KeyList thresholdKey = KeyList.withThreshold(1);
//Add the three keys to the thresholdKey
Collections.addAll(thresholdKey, keys);
System.out.println("The 1/3 threshold key structure" +thresholdKey);
//v2.0.0
```
```javascript JavaScript expandable theme={null}
// Generate our key lists
const privateKeyList = [];
const publicKeyList = [];
for (let i = 0; i < 4; i += 1) {
const privateKey = PrivateKey.generate();
const publicKey = privateKey.publicKey;
privateKeyList.push(privateKey);
publicKeyList.push(publicKey);
console.log(`${i}: pub key:${publicKey}`);
console.log(`${i}: priv key:${privateKey}`);
}
// Create our threshold key
const thresholdKey = new KeyList(publicKeyList,1);
console.log("The 1/3 threshold key structure" +thresholdKey);
//2.0.2
```
```go Go expandable theme={null}
//Generate 3 keys
key1, err := hedera.GeneratePrivateKey()
if err != nil {
panic(err)
}
publicKey1 := key1.PublicKey()
key2, err := hedera.GeneratePrivateKey()
if err != nil {
panic(err)
}
publicKey2:= key2.PublicKey()
key3, err := hedera.GeneratePrivateKey()
if err != nil {
panic(err)
}
publicKey3 := key3.PublicKey()
//Create a key list where all 3 keys are required to sign
keys := make([]hedera.PublicKey, 3)
keys[0] = publicKey1
keys[1] = publicKey2
keys[2] = publicKey3
//A key structure that requires one of the 3 keys to sign
thresholdKey := hedera.KeyListWithThreshold(1).
AddAllPublicKeys(keys)
fmt.Printf("The 1/3 threshold key structure %v\n", thresholdKey)
//v2.0.0
```
```rust Rust theme={null}
// Generate 2 keys
let key1 = PrivateKey::generate_ed25519();
let key2 = PrivateKey::generate_ed25519();
// Create a threshold key that requires 1 out of 2 keys to sign
let threshold_key = KeyList {
keys: vec![key1.public_key().into(), key2.public_key().into()],
threshold: Some(1),
};
println!("The 1/2 threshold key structure {:?}", threshold_key);
// v0.34.0
```
**Sample Output:**
```
KeyList{threshold=1,
keys=[
302e020100300506032b657004220420984bd6b4e0cac783654f30c8797655953c6ab432e78bc09a34fbda594c6395ed,
302e020100300506032b657004220420a4a7bd506f33868416d53eff55b3e8a254e17accf6cb37f44975792ededac120,
302e020100300506032b657004220420f8a6f2ba3174391e619a87506fb0b86c6e481809563a797f4f84715d1a471695]
}
```
# Run a Local Node in Codespaces
Source: https://docs.hedera.com/native/local-dev/cde/codespaces
**Hiero Local Node Deprecation (September 2026)**
Hiero Local Node is entering a 6-month deprecation period. Support ends September 2026. Migrate local testing and CI workflows to [Solo](https://solo.hiero.org/docs/) before then. [Learn more](https://hedera.com/blog/hiero-local-node-deprecation-6-month-transition-to-solo/).
Codespaces is a cloud development environment (CDE) that's hosted in the cloud. You can customize your project for GitHub Codespaces by committing configuration files to your repository (often known as Configuration-as-Code), which creates a repeatable codespaces configuration for all users of your project. [GitHub Codespaces overview](https://docs.github.com/en/codespaces/overview)
***
## Prerequisites
* Review first the [Quickstart for GitHub Codespaces](https://docs.github.com/en/codespaces/getting-started/quickstart) guide.
* Install VS Code Desktop application.
* In [Editor preference](https://github.com/settings/codespaces) change your client to `Visual Studio Code` (Should not be `Visual Studio Code for the Web`)
***
## Configure Dev Container
To configure t he dev container, open the [Hedela Local Node repo](https://github.com/hiero-ledger/hiero-local-node) and click on the `Code`->`Codespaces`->`...`-> `Configure dev container`.
This will open the dev container configuration file where you can customize your configuration like the CPUs and memory.
**Note**: If you make changes to your config file, commit and push your changes before running local node, to ensure the project starts with the right configuration.
## Creating and Running Your Codespace
Open the [Hedela Local Node repo](https://github.com/hiero-ledger/hiero-local-node) and click on the `Code`->`Codespaces`->`...`-> `New with options...` button and choose the appropriate settings:
Once your codespace is created, the template repository will be automatically cloned into it. Your codespace is all set up and have the local node running!
***
## Conclusion and Additional Resources
Congrats on successfully setting up your Codespace and running a Hedera Local Node!
**➡** [**Hedera Local Node Repository**](https://github.com/hiero-ledger/hiero-local-node#readme)
**➡** [**Quickstart for GitHub Codespaces**](https://docs.github.com/en/codespaces/getting-started/quickstart)
**➡** [**Adding Dev Container Config to Repo**](https://docs.github.com/en/codespaces/setting-up-your-project-for-codespaces/adding-a-dev-container-configuration)
# Run a Local Node in Gitpod
Source: https://docs.hedera.com/native/local-dev/cde/gitpod
**Hiero Local Node Deprecation (September 2026)**
Hiero Local Node is entering a 6-month deprecation period. Support ends September 2026. Migrate local testing and CI workflows to [Solo](https://solo.hiero.org/docs/) before then. [Learn more](https://hedera.com/blog/hiero-local-node-deprecation-6-month-transition-to-solo/).
The local network comprises the consensus node, mirror node, [JSON-RPC relay](https://github.com/hashgraph/hedera-json-rpc-relay#readme), and other Consensus Node services and now be set up without Docker and draining your computer’s resources by using Gitpod. Gitpod provides Cloud Development Environments (CDEs) and allows developers to work from any device without the need to maintain static and brittle local development environments. By the end of this tutorial, you will have your Hedera local node running on Gitpod.
***
## Prerequisites
* Signed into your GitHub account in your browser.
* [Register](https://gitpod.io/login/) a Gitpod account with your GitHub account.
* If this is your first time using Gitpod, please read the [Gitpod getting started](https://www.gitpod.io/docs/introduction/getting-started) guide.
* Install the browser extension: [Gitpod browser extension](https://www.gitpod.io/docs/configure/user-settings/browser-extension).
* The Mirror Node Web Explorer requires [VS Code Desktop](https://www.gitpod.io/docs/references/ides-and-editors/vscode) to be installed, as [VS Code Browser](https://www.gitpod.io/docs/references/ides-and-editors/vscode-browser) has limitations related to communicating with local ports, e.g. `http://127.0.0.1:5551/`.
***
## Set Up Gitpod Permissions
Enable `public_repo` permission for GitHub provider on [Gitpod’s Git integrations page](https://gitpod.io/user/integrations).
***
## Running the Hedera Local Node
The `hedera-local-node` project repository already has a Gitpod configuration file ([`.gitpod.yml`](https://github.com/hiero-ledger/hiero-local-node/blob/main/.gitpod.yml)), which makes it easy to run it within a workspace on Gitpod. Open the [Hedera Local Node repo](https://github.com/hiero-ledger/hiero-local-node). Click on the Gitpod `Open` button.
The Gitpod browser extension modifies the Github UI to add this button. This will spin up a new Gitpod workspace with your choice of CDE which will run the Hedera Local Node in your cloud environment.
### **Testing the Setup**
To confirm everything is running smoothly, run the `curl` commands below to query the mirror node for a list of accounts, query the JSON-RPC relay for the latest block, and open the mirror node explorer (HashScan) using the local endpoint ([http://localhost:8080/devnet/dashboard](http://localhost:8080/devnet/dashboard)).
**Mirror Node REST API**
The following command queries the Mirror Node for a list of accounts on your Hedera network.
```bash theme={null}
curl "http://localhost:5551/api/v1/accounts" \
-X GET
```
See the [Mirror Node interact API docs](https://testnet.mirrornode.hedera.com/api/v1/docs/) for a full list of available APIs.
**JSON RPC Relay**
The following command queries the RPC Relay for the latest block on your Hedera network.
```bash theme={null}
curl "" \\
-X POST \\
-H "Content-Type: application/json" \\
--data '{"method":"eth_getBlockByNumber","params":["latest",false],"id":1,"jsonrpc":"2.0"}'
```
See the [endpoint table](https://github.com/hashgraph/hedera-json-rpc-relay/blob/main/docs/rpc-api.md#endpoint-table) in `hedera-json-rpc-relay` for a full list of available RPCs.
**Mirror Node Explorer (Hashscan)**
Visit the local mirror node explorer endpoint ([http://localhost:8080/devnet/dashboard](http://localhost:8080/devnet/dashboard)) in your web browser. Ensure that `LOCALNET` is selected, as this will show you the Hedera network running within your Gitpod, and not one of the public nodes.
### Shut Down the Gitpod Workspace
**Note**: Gitpod usage is billed by the hour on paid plans, and hours are limited on the free plans. Therefore, once completed, remember to stop the Gitpod workspace.
***
## Conclusion and Additional Resources
Congrats on successfully setting up your Gitpod workspace and running a Hedera Local Node!
**➡** [**Hedera Local Node Repository**](https://github.com/hiero-ledger/hiero-local-node#readme)
**➡** [**Gitpod Documentation**](https://www.gitpod.io/docs/introduction/getting-started)
# How to Run Hedera Local Node in a Cloud Development Environment (CDE)
Source: https://docs.hedera.com/native/local-dev/cde/index
**Hiero Local Node Deprecation (September 2026)**
Hiero Local Node is entering a 6-month deprecation period. Support ends September 2026. Migrate local testing and CI workflows to [Solo](https://solo.hiero.org/docs/) before then. [Learn more](https://hedera.com/blog/hiero-local-node-deprecation-6-month-transition-to-solo/).
The [**Hedera Local Node**](https://github.com/hiero-ledger/hiero-local-node) project enables developers to establish their own local network for development and testing. The local network comprises the consensus node, mirror node, [JSON-RPC relay](https://github.com/hashgraph/hedera-json-rpc-relay#readme), and other Consensus Node services be set up without Docker and draining your computer’s resources by using .Cloud Development Environments (CDEs). CDEs allows developers to work from any device without the need to maintain static and brittle local development environments.
## **Available Services**
The Hedera local node comes with various services, each serving different functions, and accessible locally. These are the endpoints for each service:
| Type | Endpoint |
| --------------------------------- | -------------------------------------------------------------------------------- |
| Consensus Node Endpoint | [http://localhost:50211/](http://localhost:50211/) |
| Mirror Node GRPC Endpoint | [http://localhost:5600/](http://localhost:5600/) |
| Mirror Node REST API Endpoint | [http://localhost:5551/](http://localhost:5551/) |
| JSON RPC Relay Endpoint | [http://localhost:7546/](http://localhost:7546/) |
| JSON RPC Relay Websocket Endpoint | [http://localhost:8546/](http://localhost:8546/) |
| Mirror Node Explorer (HashScan) | [http://localhost:8080/devnet/dashboard](http://localhost:8080/devnet/dashboard) |
| Grafana UI | [http://localhost:3000/](http://localhost:3000/) |
| Prometheus UI | [http://localhost:9090/](http://localhost:9090/) |
You may access these services on `localhost`, and these endpoints are set up to be accessed from your own computer as if they were running locally. Since Gitpod and Codespaces are cloud-based development environments, “localhost” here refers to a virtual environment on cloud servers that you're accessing through your browser. Gitpod and Codespaces redirects these local addresses to your cloud workspace, making it feel as though you're working on a local setup.
# Set Up a Hedera Local Node using the NPM CLI
Source: https://docs.hedera.com/native/local-dev/setup-cli-npm
**Hiero Local Node Deprecation (September 2026)**
Hiero Local Node is entering a 6-month deprecation period. Support ends September 2026. Migrate local testing and CI workflows to [Solo](https://solo.hiero.org/docs/) before then. [Learn more](https://hedera.com/blog/hiero-local-node-deprecation-6-month-transition-to-solo/).
Hedera is an open-source, public, proof-of-stake network. Its network services offer low and fixed fees, 10k TPS, and instant transaction finality. Learn more about the [Hedera platform and how it works](https://hedera.com/how-it-works).
In this tutorial, we will adopt, set up, and run a Hedera node locally using the [@hashgraph/hedera-local](https://www.npmjs.com/package/@hashgraph/hedera-local) NPM Command Line Interface (CLI) tool with `docker compose`.
> This tutorial is based on the [Hedera Local Node README documentation](https://github.com/hiero-ledger/hiero-local-node).
> Already familiar with using a cloud service? Check out the other options for setting up and running the Hedera node locally. See the [Useful resources section](https://docs.google.com/document/d/1gWKWF-fzc0VlKhRhjZhecatHnTXkHy9RQeKfD6Klnak/edit#heading=h.5zlu1j5vb4rk) for more information.
## Prerequisites
To get started with this tutorial, ensure that you have the following software installed:
* [Node.js](https://nodejs.org/) >= v14.x (Check version: `node -v`)
* NPM >= v6.14.17 (Check version: `npm -v`)
* [Docker](https://www.docker.com/) >= v20.10.x (Check version: `docker -v`)
* [Docker Compose](https://docs.docker.com/compose/) >= v2.12.3 (Check version: `docker compose version`)
* Hardware: Minimum 16GB RAM
### Installation
* Node.js and NPM: Refer to the [official installation guide](https://nodejs.org/en/learn/getting-started/how-to-install-nodejs).
* Docker: See [Docker Setup Guide](https://github.com/hiero-ledger/hiero-local-node?tab=readme-ov-file#note) to get docker up and running (note: specific instructions may vary based on the OS).
## Getting Started
Clone the GitHub repo, navigate to the project folder using the commands below;
```js theme={null}
git clone https://github.com/hiero-ledger/hiero-local-node.git
cd hedera-local-node
```
### Install CLI Tool
The command below can be used to install the official release from the [NPM](https://www.npmjs.com/package/@hashgraph/hedera-local) repository.
```js theme={null}
npm install @hashgraph/hedera-local -g
```
> **Note: This version may not reflect the most recent changes to the main branch of this repository. It also uses a baked in version of the Docker Compose definitions and will not reflect any local changes made to the repository.**
#### Local development Installation
Install the dependencies locally.
```js theme={null}
npm install && npm install -g
```
### Running the Node:
Start the local node (Note: Ensure Docker is running):
```js theme={null}
npm run start
```
**You can pass the following CLI flags, this would be used later in the following sections:**
```js theme={null}
--d / --detached - Start the local node in detached mode.
--h / --host - Override the default host.
```
**Other NPM commands:**
* `npm run restart` to restart the network
* `npm run stop` to stop the network
* `npm run generate-accounts` to generate new accounts - network must be running first
**You should see the following response in the terminal:**
```bash theme={null}
hedera-local-node % npm run start
> @hashgraph/hedera-local@2.26.2 restart
> npm run build && node ./build/index.js restart
> @hashgraph/hedera-local@2.26.2 build
> rimraf ./build && tsc
[Hedera-Local-Node] INFO (StateController) [✔︎] Starting restart procedure!
[Hedera-Local-Node] INFO (CleanUpState) ⏳ Initiating clean up procedure. Trying to revert unneeded changes to files...
[Hedera-Local-Node] INFO (CleanUpState) [✔︎] Clean up of consensus node properties finished.
[Hedera-Local-Node] INFO (CleanUpState) [✔︎] Clean up of mirror node properties finished.
[Hedera-Local-Node] INFO (StopState) ⏳ Initiating stop procedure. Trying to stop docker containers and clean up volumes...
[Hedera-Local-Node] INFO (StopState) ⏳ Stopping the network...
[Hedera-Local-Node] INFO (StopState) [✔︎] Hedera Local Node was stopped successfully.
[Hedera-Local-Node] INFO (InitState) ⏳ Making sure that Docker is started and it is correct version...
[Hedera-Local-Node] INFO (DockerService) ⏳ Checking docker compose version...
[Hedera-Local-Node] INFO (DockerService) ⏳ Checking docker resources...
[Hedera-Local-Node] WARNING (DockerService) [!] Port 3000 is in use.
[Hedera-Local-Node] INFO (InitState) ⏳ Setting configuration with latest images on host 127.0.0.1 with dev mode turned off using turbo mode in single node configuration...
[Hedera-Local-Node] INFO (InitState) [✔︎] Local Node Working directory set to /Users/owanate/Library/Application Support/hedera-local.
[Hedera-Local-Node] INFO (InitState) [✔︎] Hedera JSON-RPC Relay rate limits were disabled.
[Hedera-Local-Node] INFO (InitState) [✔︎] Needed environment variables were set for this configuration.
[Hedera-Local-Node] INFO (InitState) [✔︎] Needed bootsrap properties were set for this configuration.
[Hedera-Local-Node] INFO (InitState) [✔︎] Needed bootsrap properties were set for this configuration.
[Hedera-Local-Node] INFO (InitState) [✔︎] Needed mirror node properties were set for this configuration.
[Hedera-Local-Node] INFO (StartState) ⏳ Starting Hedera Local Node...
```
To generate default accounts and start the local node in detached mode, use the command below:
```js theme={null}
npm run start -- -d
```
**You should see the following response in the terminal:**
```bash theme={null}
hedera-local-node % npm run start -- -d
> @hashgraph/hedera-local@2.26.2 start
> npm run build && node ./build/index.js start -d
> @hashgraph/hedera-local@2.26.2 build
> rimraf ./build && tsc
[Hedera-Local-Node] INFO (StartState) [✔︎] Hedera Local Node successfully started!
[Hedera-Local-Node] INFO (NetworkPrepState) ⏳ Starting Network Preparation State...
[Hedera-Local-Node] INFO (NetworkPrepState) [✔︎] Imported fees successfully!
[Hedera-Local-Node] INFO (NetworkPrepState) [✔︎] Topic was created!
[Hedera-Local-Node] INFO (AccountCreationState) ⏳ Starting Account Creation state in synchronous mode ...
[Hedera-Local-Node] INFO (AccountCreationState) |-----------------------------------------------------------------------------------------|
[Hedera-Local-Node] INFO (AccountCreationState) |-----------------------------| Accounts list (ECDSA keys) |----------------------------|
[Hedera-Local-Node] INFO (AccountCreationState) |-----------------------------------------------------------------------------------------|
[Hedera-Local-Node] INFO (AccountCreationState) | id | private key | balance |
[Hedera-Local-Node] INFO (AccountCreationState) |-----------------------------------------------------------------------------------------|
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1002 - 0x7f109a9e3b0d8ecfba9cc23a3614433ce0fa7ddcc80f2a8f10b222179a5a80d6 - 10000 ℏ |
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1003 - 0x6ec1f2e7d126a74a1d2ff9e1c5d90b92378c725e506651ff8bb8616a5c724628 - 10000 ℏ |
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1004 - 0xb4d7f7e82f61d81c95985771b8abf518f9328d019c36849d4214b5f995d13814 - 10000 ℏ |
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1005 - 0x941536648ac10d5734973e94df413c17809d6cc5e24cd11e947e685acfbd12ae - 10000 ℏ |
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1006 - 0x5829cf333ef66b6bdd34950f096cb24e06ef041c5f63e577b4f3362309125863 - 10000 ℏ |
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1007 - 0x8fc4bffe2b40b2b7db7fd937736c4575a0925511d7a0a2dfc3274e8c17b41d20 - 10000 ℏ |
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1008 - 0xb6c10e2baaeba1fa4a8b73644db4f28f4bf0912cceb6e8959f73bb423c33bd84 - 10000 ℏ |
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1009 - 0xfe8875acb38f684b2025d5472445b8e4745705a9e7adc9b0485a05df790df700 - 10000 ℏ |
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1010 - 0xbdc6e0a69f2921a78e9af930111334a41d3fab44653c8de0775572c526feea2d - 10000 ℏ |
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1011 - 0x3e215c3d2a59626a669ed04ec1700f36c05c9b216e592f58bbfd3d8aa6ea25f9 - 10000 ℏ |
[Hedera-Local-Node] INFO (AccountCreationState) |-----------------------------------------------------------------------------------------|
[Hedera-Local-Node] INFO (AccountCreationState) |--------------------------------------------------------------------------------------------------------------------------------------|
[Hedera-Local-Node] INFO (AccountCreationState) |------------------------------------------------| Accounts list (Alias ECDSA keys) |--------------------------------------------------|
[Hedera-Local-Node] INFO (AccountCreationState) |--------------------------------------------------------------------------------------------------------------------------------------|
[Hedera-Local-Node] INFO (AccountCreationState) | id | public address | private key | balance |
[Hedera-Local-Node] INFO (AccountCreationState) |--------------------------------------------------------------------------------------------------------------------------------------|
[Hedera-Local-Node] INFO (AccountCreationState) | 0.0.1012 - 0x67d8d32e9bf1a9968a5ff53b87d777aa8ebbee69 - 0x105d050185ccb907fba04dd92d8de9e32c18305e097ab41dadda21489a211524 - 10000 ℏ |
.....
[Hedera-Local-Node] INFO (AccountCreationState) |-----------------------------------------------------------------------------------------|
[Hedera-Local-Node] INFO (AccountCreationState) [✔︎] Accounts created succefully!
[Hedera-Local-Node] INFO (CleanUpState) ⏳ Initiating clean up procedure. Trying to revert unneeded changes to files...
[Hedera-Local-Node] INFO (CleanUpState) [✔︎] Clean up of consensus node properties finished.
[Hedera-Local-Node] INFO (CleanUpState) [✔︎] Clean up of mirror node properties finished.
```
## Verify Running Node
There are different ways to verify that a node is running;
* Check Block Number using Hashscan Block Explorer
* Send cURL request to `getBlockNumber`
### Check Block Number using Hashscan Block Explorer
Visit the local mirror node explorer endpoint ([http://localhost:8080/devnet/dashboard](http://localhost:8080/devnet/dashboard)) in your web browser. Ensure that `LOCALNET` is selected, as this will show you the Hedera network running within your local network.
Select any of the listed blocks to view the details (Consensus, Block, Transaction Hash, etc) for a particular block.
### Send cURL request to getBlockNumber
Let's verify that we are able to interact with Hedera Testnet using JSON-RPC by issuing an `eth_getBlockByNumber` JSON-RPC request.
**Enter the curl command below:**
```bash theme={null}
curl http://localhost:7546/ \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_getBlockByNumber","params":["latest",false],"id":1,"jsonrpc":"2.0"}'
```
**You should get the following response:**
```bash theme={null}
curl http://localhost:7546/ \
-X POST \
-H "Content-Type: application/json" \
--data '{"method":"eth_getBlockByNumber","params":["latest",false],"id":1,"jsonrpc":"2.0"}'
{"result":{"timestamp":"0x667c000e","difficulty":"0x0","extraData":"0x","gasLimit":"0xe4e1c0","baseFeePerGas":"0xa54f4c3c00","gasUsed":"0x0","logsBloom":"0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","miner":"0x0000000000000000000000000000000000000000","mixHash":"0x0000000000000000000000000000000000000000000000000000000000000000","nonce":"0x0000000000000000","receiptsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","sha3Uncles":"0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347","size":"0x93d","stateRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","totalDifficulty":"0x0","transactions":[],"transactionsRoot":"0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421","uncles":[],"withdrawals":[],"withdrawalsRoot":"0x0000000000000000000000000000000000000000000000000000000000000000","number":"0x1604","hash":"0xfef0932ffb429840fe765d6d87c77425e2991326ddae6747dcce5c929c69ef38","parentHash":"0xef1ef331626f4f50ba2541d440b45cac51c5d8d6b4c46407a00c15d593c31e96"},"jsonrpc":"2.0","id":1}%
```
### Troubleshooting
Find below some common errors and how to troubleshoot them:
**Error: Node cannot start properly because necessary ports are in use!**
```js theme={null}
hedera-local-node % npm run start -- -d
> @hashgraph/hedera-local@2.26.2 start
> npm run build && node ./build/index.js start -d
> @hashgraph/hedera-local@2.26.2 build
> rimraf ./build && tsc
[Hedera-Local-Node] INFO (StateController) [✔︎] Starting start procedure!
[Hedera-Local-Node] INFO (InitState) ⏳ Making sure that Docker is started and it is correct version...
[Hedera-Local-Node] INFO (DockerService) ⏳ Checking docker compose version...
[Hedera-Local-Node] INFO (DockerService) ⏳ Checking docker resources...
[Hedera-Local-Node] ERROR (DockerService) [✘] [✘] Port 5551 is in use.
[Hedera-Local-Node] ERROR (DockerService) [✘] [✘] Port 8545 is in use.
[Hedera-Local-Node] ERROR (DockerService) [✘] [✘] Port 5600 is in use.
[Hedera-Local-Node] ERROR (DockerService) [✘] [✘] Port 5433 is in use.
[Hedera-Local-Node] ERROR (DockerService) [✘] [✘] Port 8082 is in use.
[Hedera-Local-Node] ERROR (DockerService) [✘] [✘] Port 6379 is in use.
[Hedera-Local-Node] WARNING (DockerService) [!] Port 7546 is in use.
[Hedera-Local-Node] WARNING (DockerService) [!] Port 8080 is in use.
[Hedera-Local-Node] WARNING (DockerService) [!] Port 3000 is in use.
[Hedera-Local-Node] ERROR (DockerService) [✘] [✘] Node cannot start properly because necessary ports are in use!
```
**Fix**
* **Option 1:** Instead of starting another instance of the network, use the `npm run generate-accounts` to generate new accounts for an already started network.
* **Option 2:** If you get the above error, ensure that you terminate any existing Docker processes for the local node, and also any other processes that are bound to these port numbers, before running the npm start command. You can run `docker compose down -v`, `git clean -xfd`, `git reset --hard` to fix this.
## Useful Terms
For an in depth explanation of the different terms below, see the [glossary documentation](/support/glossary).
* Accounts list (ED25519 keys)
* Private keys
* Public address
## Next Steps
Want to learn how to deploy smart contracts on Hedera? Visit the guide on how to [Deploy a Smart Contract Using Hardhat and Hedera JSON-RPC Relay](/evm/development/json-rpc).
## Summary
In this tutorial, we successfully set up and ran the Hedera local node using the [NPM CLI](https://www.npmjs.com/package/@hashgraph/hedera-local) tool, generated default accounts and solved common errors encountered when running the local node.
## Useful Resources
* Set and Run a Hedera Node using the [Local Hedera Package](https://github.com/hiero-ledger/hiero-local-node?tab=readme-ov-file#using-hedera-local).
* [Setup node using Docker CLI](https://github.com/hiero-ledger/hiero-local-node?tab=readme-ov-file#docker).
* Use [local network variables](https://github.com/hiero-ledger/hiero-local-node?tab=readme-ov-file#network-variables) to interact with Consensus and Mirror Nodes
* Using [Grafana and Prometheus Endpoints](https://github.com/hiero-ledger/hiero-local-node?tab=readme-ov-file#grafana--prometheus).
[GitHub](https://github.com/owans) |
[Medium](https://medium.com/@owanateamachree)
[GitHub](https://github.com/owans) |
[Medium](https://medium.com/@owanateamachree)
[GitHub](https://github.com/theekrystallee) |
[X](https://X.com/theekrystallee)
[GitHub](https://github.com/theekrystallee) |
[X](https://X.com/theekrystallee)
# How to Set Up a Hedera Local Node
Source: https://docs.hedera.com/native/local-dev/setup-local-node
**Hiero Local Node Deprecation (September 2026)**
Hiero Local Node is entering a 6-month deprecation period. Support ends September 2026. Migrate local testing and CI workflows to [Solo](https://solo.hiero.org/docs/) before then. [Learn more](https://hedera.com/blog/hiero-local-node-deprecation-6-month-transition-to-solo/).
**Looking for the recommended replacement?** [Solo](https://solo.hiero.org/docs/) is a Kubernetes-native local network that replaces Local Node and is compatible with the Hiero SDKs. See the [Solo quickstart](https://solo.hiero.org/docs/simple-solo-setup/quickstart/) to get started.
The [**Hedera Local Node**](https://github.com/hiero-ledger/hiero-local-node) project enables developers to establish their own local network for development and testing. The local network comprises the consensus node, mirror node, [JSON-RPC relay](https://github.com/hashgraph/hedera-json-rpc-relay#readme), and other Hedera products, and can be set up using the CLI tool and Docker. This setup allows you to seamlessly build and deploy smart contracts from your local environment.
By the end of this tutorial, you'll be equipped to run a Hedera local node and generate keys, allowing you to test your projects and deploy projects in your local environment.
***
## Prerequisites
* [Node.js](https://nodejs.org/en) >= v14.x
* [NPM](https://docs.npmjs.com/downloading-and-installing-node-js-and-npm) >= v6.14.17
* Minimum 16GB RAM
* [Docker](https://www.docker.com/) >= v20.10.x
* [Docker Compose](https://docs.docker.com/compose/) >= v2.12.3
* Have Docker running on your machine with the correct configurations.
Ensure the **`VirtioFS`** file sharing implementation is enabled in the docker settings.
Ensure the following configurations are set at minimum in Docker **Settings** -> **Resources** and are available for use:
* **CPUs:** 6
* **Memory:** 8GB
* **Swap:** 1 GB
* **Disk Image Size:** 64 GB
Ensure the **`Allow the default Docker sockets to be used (requires password)`** is enabled in Docker **Settings -> Advanced**.
**Note:** The image may look different if you are on a different version
*Local node can be run using Docker or NPM but we will use Docker for this tutorial. *[Here](https://github.com/hiero-ledger/hiero-local-node#official-npm-release)* are the installation steps for NPM.*
***
## Table of Contents
1. [Start Your Local Network](#start-your-local-network)
2. [Generate Keys](#generate-keys)
3. [Stop Your Local Network](#stop-your-local-network)
4. [Additional Resources](#additional-resources)
***
## Start Your Local Network
Open a new terminal and navigate to your preferred directory where your Hedera Local Node project will live. Run the following command to clone the repo and install dependencies to your local machine:
```bash theme={null}
git clone https://github.com/hiero-ledger/hiero-local-node.git
cd hedera-local-node
npm install
```
For Windows users: You will need to update the file endings of `compose-network/mirror-node/init.sh` by running this in WSL:
```bash theme={null}
dos2unix compose-network/mirror-node/init.sh
```
Ensure Docker is installed and open on your machine before running this command to get the network up and running:
```bash theme={null}
// starts and generates the first 30 accounts
npm run start -- -d
or
// will start local node but will not generate the first 30 accounts
docker compose up -d
```
***
## Generate Keys
To generate accounts with random private keys, run the `generate-accounts` command. Specify the number of accounts generated by appending the number to the `hedera generate-account` command. For example, to generate 5 accounts, run `hedera generate-accounts 5`.
```
Generating accounts in synchronous mode...
|-----------------------------------------------------------------------------------------|
|-----------------------------| Accounts list ( ECDSA keys) |----------------------------|
|-----------------------------------------------------------------------------------------|
| id | private key | balance |
|-----------------------------------------------------------------------------------------|
| 0.0.1033 - 0xced34a00d3fff542e350a5e61cb41509812bf23ea581f83a0a862c94d8c69704 - 10000 ℏ |
| 0.0.1034 - 0xa4189ab682ba43925ce654ca09800bba86cf8b1b7f889006d5170d95f4fed365 - 10000 ℏ |
| 0.0.1035 - 0xf9106e9841677136c9cbe8c114dab80470ca62a15bfe9c777006bcb114288c22 - 10000 ℏ |
| 0.0.1036 - 0xe3517a9235971be1e1f95e791f3ffd7d753a652799fa11f1ace626036c4db275 - 10000 ℏ |
| 0.0.1037 - 0x636926cf2f6f9fd0a58043c600390eeef0bbed9d4b8a113ea68a8d67f922d04e - 10000 ℏ |
|-----------------------------------------------------------------------------------------|
|--------------------------------------------------------------------------------------------------------------------------------------|
|------------------------------------------------| Accounts list (Alias ECDSA keys) |--------------------------------------------------|
|--------------------------------------------------------------------------------------------------------------------------------------|
| id | public address | private key | balance |
|--------------------------------------------------------------------------------------------------------------------------------------|
| 0.0.1038 - 0xaBE90e20f394629e054Bc1E8F1338Fe8ea94F0b5 - 0x444913bd258f764e62db6c87abde7ca52ec22985db8c91b8c3b2b4f2c51775f0 - 10000 ℏ |
| 0.0.1039 - 0x26d941d8E1f6bF9B0F7e5156fA6ff02acEd0DF3E - 0xea25f427caf7029989669f93926b7902dde5361b176b4bc17b8ec0a967beaa0b - 10000 ℏ |
| 0.0.1040 - 0x64001c2d1f3a8d3574435B4F125944018E2E584D - 0xf2deb678a1e67e288d8a128334f41c890e7600b2a5471ecc9a3af4824e3021b7 - 10000 ℏ |
| 0.0.1041 - 0x6bE22CD9D16b64969683B74897E4EBB30c7c30E8 - 0xb9c2480cdbdddb2ecd6e032b87820c29e8791ad4f53b89f829269d856c835819 - 10000 ℏ |
| 0.0.1042 - 0x992d8aD211b28B23589c0b3Fe30de6C90662C4aB - 0x7e8bb0d85a8d80fa2eb2c9f6bd5c9b1a2c2f9f6992c7fffd201c8e81f0ec0000 - 10000 ℏ |
|--------------------------------------------------------------------------------------------------------------------------------------|
|-----------------------------------------------------------------------------------------|
|-----------------------------| Accounts list (ED25519 keys) |----------------------------|
|-----------------------------------------------------------------------------------------|
| id | private key | balance |
|-----------------------------------------------------------------------------------------|
| 0.0.1043 - 0xd4917e152ca922b8bfbafffc3486512ae25ec0a75b05c44f517b11cd12fd949b - 10000 ℏ |
| 0.0.1044 - 0xbaeec69382fbb43e4d521b3d8717c9cba610a1fbcaededaaf4408c3138a683ae - 10000 ℏ |
| 0.0.1045 - 0x1f5c4b2efd3c36d29e9d2e16a825abd001f99bff2388bb8c6011cd5f956023c9 - 10000 ℏ |
| 0.0.1046 - 0x1976acdd5e71ce7e8db4cb0aa112fa1c16876155f0f20b9b7029916073f1d67f - 10000 ℏ |
| 0.0.1047 - 0x6e29f48b11ffc77e277f0500d607b35956da58f1ed30aad003fb1846bfffc483 - 10000 ℏ |
|-----------------------------------------------------------------------------------------|
```
**Please note**: Since the first 10 accounts generated are with predefined private keys, if you need 5 generated with random keys, you will run `hedera start 15`. The same rule applies when you use the `hedera generate-accounts` command.
Grab any of the account private keys generated from the ***Alias ECDSA keys Accounts list***. This will be used as the `LOCAL_NODE_OPERATOR_PRIVATE_KEY` environment variable value in your `.env` file of your project.
***
## Stop Your Local Network
To stop your local node, you can run the `hedera stop` command. If you want to keep any files created manually in the working directory, please save them before executing this command.
```
Stopping the network...
Stopping the docker containers...
Cleaning the volumes and temp files...
```
Alternatively, run `docker compose down -v; git clean -xfd; git reset --hard` to stop the local node and reset it to its original state.
```bash theme={null}
[+] Running 27/27
✔ Container mirror-node-web3 Removed 3.5s
✔ Container json-rpc-relay-ws Removed 10.8s
✔ Container mirror-node-monitor Removed 3.7s
✔ Container relay-cache Removed 0.9s
✔ Container prometheus Removed 0.9s
✔ Container record-sidecar-uploader Removed 0.0s
✔ Container grafana Removed 0.9s
✔ Container hedera-explorer Removed 10.4s
✔ Container json-rpc-relay Removed 10.7s
✔ Container account-balances-uploader Removed 0.1s
✔ Container envoy-proxy Removed 1.0s
✔ Container mirror-node-grpc Removed 2.7s
✔ Container mirror-node-rest Removed 10.4s
✔ Container network-node Removed 10.8s
✔ Container mirror-node-importer Removed 10.4s
✔ Container record-streams-uploader Removed 0.0s
✔ Container haveged Removed 0.0s
✔ Container mirror-node-db Removed 0.3s
✔ Container minio Removed 0.0s
✔ Volume prometheus-data Removed 0.0s
✔ Volume minio-data Removed 0.0s
✔ Volume mirror-node-postgres Removed 0.1s
✔ Volume grafana-data Removed 0.2s
✔ Network network-node-bridge Removed 0.1s
✔ Network hedera-local-node_default Removed 0.2s
✔ Network cloud-storage Removed 0.2s
✔ Network mirror-node Removed 0.2s
Removing .husky/_/
Removing network-logs/
Removing node_modules/
HEAD is now at ......
```
***📣 Note**: All available commands can be checked out* [*here*](https://github.com/hiero-ledger/hiero-local-node/tree/main?tab=readme-ov-file#using-hedera-local)*.*
***
## Additional Resources
**➡** [**Hedera Local Node Repository**](https://github.com/hiero-ledger/hiero-local-node#readme)
**➡** [**Hedera Local Node CLI Tool Commands**](https://github.com/hiero-ledger/hiero-local-node#using-hedera-local)
**➡** [**Hedera Local Node Docker Setup** ](https://www.youtube.com/watch?v=KOhzu6ftmbY)**\[Video Tutorial]**
# Pseudorandom Number Generator
Source: https://docs.hedera.com/native/prng
A transaction that generates a pseudorandom number. When the pseudorandom number generate transaction executes, its transaction record will contain the 384-bit array of pseudorandom bytes. The transaction has an optional `range` parameter. If the parameter is given and is positive, then the record will contain a 32-bit pseudorandom integer `r`, where `0 <= r < range` instead of containing the 384 pseudorandom bits.
When the `n`th transaction needs a pseudorandom number, it is given the running hash of all records up to and including the record for transaction `n-3`. If it needs 384 bits, then it uses the entire hash. If it needs 256 bits, it uses the first 256 bits of the hash. If it needs a random number `r` that is in the range `0 <= r < range`, then it lets `x` be the first 32 bits of the hash (interpreted as a signed integer).
The choice of using the hash up to transaction `n-3` rather than `n-1` is to ensure the transactions can be processed quickly. Because the thread calculating the hash will have more time to complete it before it is needed. The use of `n-3` rather than `n-1000000` is to make it hard to predict the pseudorandom number in advance.\\
Reference: [HIP-351](https://hips.hedera.com/hip/hip-351)
| Field | Description |
| --------- | ----------------------------------------------------------- |
| **Range** | The specified range to return the pseudorandom number from. |
#### Methods
| Method | Type | Requirement |
| ------------------- | ------- | ----------- |
| `setRange()` | integer | Optional |
| `getRange()` | integer | Optional |
```java Java theme={null}
//Create the transaction with range set
TransactionResponse transaction = new PrngTransaction()
//Set the range
.setRange(250)
.execute(client);
//Get the record
TransactionRecord transactionRecord = transaction.getRecord(client);
//Get the number
int prngNumber = transactionRecord.prngNumber;
System.out.println(prngNumber);
//SDK version 2.17.0
```
```javascript JavaScript theme={null}
//Create the transaction with range set
const transaction = await new PrngTransaction()
//Set the range
.setRange(250)
.execute(client);
//Get the record
const transactionRecord = await transaction.getRecord(client);
//Get the number
const prngNumber = transactionRecord.prngNumber;
console.log(prngNumber);
//SDK version 2.17.0
```
```go Go theme={null}
transaction, err := hedera.NewPrngTransaction().
// Set the range
SetRange(250).
Execute(client)
if err != nil {
println(err.Error(), ": error executing rng transaction")
return
}
transactionRecord, err := createResponse.GetRecord(client)
if err != nil {
println(err.Error(), ": error getting receipt")
return
}
if transactionRecord.PrngNumber != nil {
println(err.Error(), ": error, pseudo-random number is nil")
return
}
println("The pseudorandom number is:", *transactionRecord.PrngNumber)
//Version 2.17.0
```
```rust Rust theme={null}
// Create the transaction with range set
let transaction = PrngTransaction::new()
.range(250)
.execute(&client)
.await?;
// Get the record
let record = transaction.get_record(&client)?;
// Get the number
if let Some(prng_number) = record.prng_number {
println!("The pseudorandom number is: {:?}", prng_number);
} else {
println!("No pseudorandom number returned in the record.");
}
// v2.17.0+
```
# Queries
Source: https://docs.hedera.com/native/queries
Queries are requests that do not require network consensus. Queries are processed only by the single node the request is sent to. Below is a list of network queries by service.
#### **Recommend Using Mirror Node REST API**
For obtaining token information and historical data, consider using the Mirror Node REST API endpoint [**Get Token Account Balance**](https://docs.hedera.com/api-reference/tokens/list-token-balances) which offers several advantages:
* **Cost-effective and scalable:** [Mirror node providers](/operators/mirror-node#mainnet) offer paid plans with a large number of queries included. The Hedera-hosted mirror node offers free queries with specific throttles for testing. While some SDK queries are currently free, these are subject to change in the future.
* **Performance:** Mirror nodes don't burden consensus nodes, allowing them to focus on processing transactions and providing efficient access to historical data without impacting network performance.
* **Historical data:** Mirror nodes store complete transaction history, records, and events - ideal for analytics, auditing, and monitoring past activity.
📚 **For more details on querying data, read:** [Querying Data on Hedera: SDK vs Mirror Node REST API](https://hedera.com/blog/querying-data-on-hedera-sdk-vs-mirror-node-rest-api/)
#### **DEPRECATION NOTICE: `AccountBalanceQuery`**
The `AccountBalanceQuery` is deprecated and will be completely removed in **July 2026**. This is the only SDK method presented on this page and it will no longer function after this date.
A gradual throttle reduction begins in **May 2026**. To avoid rate limiting and future service disruptions, you must migrate to the Mirror Node REST API.
📚 **For the full migration guide, read:** [Migrating from AccountBalanceQuery: What You Need to Know](https://hedera.com/blog/migrating-from-accountbalancequery-what-you-need-to-know)
| Cryptocurrency Accounts | Consensus | Tokens | File Service | Smart Contracts | Schedule Service |
| --------------------------------------------------- | -------------------------------------------------- | ----------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------- | ----------------------------------------------- |
| [AccountBalanceQuery](/native/accounts/get-balance) | [TopicInfoQuery](/native/consensus/get-info) | [TokenBalanceQuery](/native/tokens/get-balance) | [FileContentsQuery](/native/files/get-contents) | [ContractCallQuery](/native/smart-contracts/get-function) | [ScheduleInfoQuery](/native/scheduled/get-info) |
| [AccountInfoQuery](/native/accounts/get-info) | [TopicMessageQuery](/native/consensus/get-message) | [TokenInfoQuery](/native/tokens/get-info) | [FileInfoQuery](/native/files/get-info) | [ContractByteCodeQuery](/native/smart-contracts/get-bytecode) | |
## Get Query Cost
A query that returns the cost of a query prior to submitting the query to the network node for processing. If the cost of the query is greater than the default max query payment (1 HBAR) you can use `setMaxQueryPayment()` to change the default.
Method
Type
Description
getCost(\)
Client
Get the cost of the query in HBAR
getCost(\)
Client, Duration
The max length of time the SDK will attempt to retry in the event of repeated busy responses from the node(s)
getCostAsync(\)
Client
Get the cost of a query asynchronously
```java Java theme={null}
//Create the query request
AccountBalanceQuery query = new AccountBalanceQuery()
.setAccountId(accountId);
//Get the cost of the query
Hbar queryCost = query.getCost(client);
System.out.println("The account balance query cost is " +queryCost);
//v2.0.0
```
```javascript JavaScript theme={null}
//Create the query request
const query = new AccountBalanceQuery()
.setAccountId(accountId);
//Get the cost of the query
const queryCost = await query.getCost(client);
console.log("The account balance query cost is " +queryCost);
//v2.0.0
```
```java Go theme={null}
//Create the query request
query := hedera.NewAccountBalanceQuery().
SetAccountID(newAccountId)
//Get the cost of the query
cost, err := query.GetCost(client)
if err != nil {
panic(err)
}
fmt.Printf("The account balance query cost is: %v\n ", cost.String())
//v2.0.0
```
```rust Rust theme={null}
// Create the query request
let query = AccountBalanceQuery::new()
.account_id(account_id);
// Get the cost of the query
let query_cost = query.get_cost(&client).await?;
println!("The account balance query cost is {:?}", query_cost);
// v0.34.0
```
# Go Quickstart
Source: https://docs.hedera.com/native/quickstart/go
Connect to Hedera testnet from Go, query your balance, and transfer HBAR.
This page gets a Go program talking to Hedera testnet: SDK install, operator credentials, balance query, and an HBAR transfer.
## Prerequisites
* Go 1.21 or later
* A Hedera testnet account with ECDSA keys from the [developer portal](https://portal.hedera.com)
## Step 1: Initialize the module and install the SDK
```bash theme={null}
mkdir hedera-quickstart && cd hedera-quickstart
go mod init hedera-quickstart
go get github.com/hiero-ledger/hiero-sdk-go/v2@latest
go get github.com/joho/godotenv
```
## Step 2: Credentials
Create a `.env` file (and add it to `.gitignore`):
```dotenv theme={null}
OPERATOR_ID=0.0.1234
OPERATOR_KEY=302d300706052b8104000a032200033456...
```
`OPERATOR_ID` is your Hedera account ID. `OPERATOR_KEY` is the DER-encoded ECDSA private key; use the **HEX Encoded Private Key** value from the developer portal.
## Step 3: Connect, query, transfer
Create `main.go`:
```go theme={null}
package main
import (
"fmt"
"log"
"os"
hedera "github.com/hiero-ledger/hiero-sdk-go/v2/sdk"
"github.com/joho/godotenv"
)
func main() {
if err := godotenv.Load(); err != nil {
log.Fatalf("failed to load .env: %v", err)
}
operatorID, err := hedera.AccountIDFromString(os.Getenv("OPERATOR_ID"))
if err != nil {
log.Fatalf("bad OPERATOR_ID: %v", err)
}
operatorKey, err := hedera.PrivateKeyFromString(os.Getenv("OPERATOR_KEY"))
if err != nil {
log.Fatalf("bad OPERATOR_KEY: %v", err)
}
// Connect to testnet with the operator as the default payer.
client := hedera.ClientForTestnet()
client.SetOperator(operatorID, operatorKey)
// 1. Query the operator's balance.
balance, err := hedera.NewAccountBalanceQuery().
SetAccountID(operatorID).
Execute(client)
if err != nil {
log.Fatalf("balance query failed: %v", err)
}
fmt.Printf("Operator balance: %v\n", balance.Hbars)
// 2. Transfer 1 HBAR to account 0.0.3.
recipient, _ := hedera.AccountIDFromString("0.0.3")
response, err := hedera.NewTransferTransaction().
AddHbarTransfer(operatorID, hedera.HbarFrom(-1, hedera.HbarUnits.Hbar)).
AddHbarTransfer(recipient, hedera.HbarFrom(1, hedera.HbarUnits.Hbar)).
Execute(client)
if err != nil {
log.Fatalf("transfer failed: %v", err)
}
receipt, err := response.GetReceipt(client)
if err != nil {
log.Fatalf("receipt failed: %v", err)
}
fmt.Printf("Transfer status: %s\n", receipt.Status)
fmt.Printf("Transaction ID: %s\n", response.TransactionID)
}
```
## Step 4: Run it
```bash theme={null}
go run main.go
```
Expected output:
```text theme={null}
Operator balance: 10000 ℏ
Transfer status: SUCCESS
Transaction ID: 0.0.1234@1700000000.123456789
```
Look up the transaction on HashScan:
```text theme={null}
https://hashscan.io/testnet/transaction/
```
**Prefer a local network?** [Solo](https://solo.hiero.org/docs/) runs a full Hedera stack on your machine, no testnet rate limits, no faucet, no resets. See [Using Solo with Hiero SDKs](https://solo.hiero.org/docs/using-solo/using-solo-with-hiero-sdks/) to point the SDK at a local Solo network instead of testnet.
## What's next
Generate a new account programmatically and fund it from your operator.
Mint a native HTS token with custom supply and decimals.
Publish a message to HCS for verifiable, ordered audit logs.
Full API reference on GitHub.
The four Hiero SDKs (JavaScript, Java, Go, Python) share the same API surface, so code translates almost line-for-line between them. Differences are mostly language-idiomatic.
# Java Quickstart
Source: https://docs.hedera.com/native/quickstart/java
Connect to Hedera testnet from Java, query your balance, and transfer HBAR.
This page gets a Java application talking to Hedera testnet: SDK install, operator credentials, balance query, and an HBAR transfer.
## Prerequisites
* JDK 11 or later
* Maven 3.8+ or Gradle 8+
* A Hedera testnet account with ECDSA keys from the [developer portal](https://portal.hedera.com)
## Step 1: Add the SDK dependency
For Maven, add to `pom.xml`:
```xml theme={null}
com.hedera.hashgraphsdk2.72.0io.grpcgrpc-netty-shaded1.73.0
```
For Gradle, add to `build.gradle`:
```groovy theme={null}
dependencies {
implementation 'com.hedera.hashgraph:sdk:2.72.0'
implementation 'io.grpc:grpc-netty-shaded:1.73.0'
}
```
`grpc-netty-shaded` is the network transport. Without it the SDK compiles, then throws at runtime when you actually try to talk to the network.
## Step 2: Credentials
Create a `.env` file in your project root (and add it to `.gitignore`):
```dotenv theme={null}
OPERATOR_ID=0.0.1234
OPERATOR_KEY=302d300706052b8104000a032200033456...
```
`OPERATOR_ID` is your Hedera account ID. `OPERATOR_KEY` is the DER-encoded ECDSA private key; use the **HEX Encoded Private Key** value from the developer portal.
## Step 3: Connect, query, transfer
Create `src/main/java/HederaQuickstart.java`:
```java theme={null}
import com.hedera.hashgraph.sdk.*;
import io.github.cdimascio.dotenv.Dotenv;
public class HederaQuickstart {
public static void main(String[] args) throws Exception {
Dotenv env = Dotenv.load();
AccountId operatorId = AccountId.fromString(env.get("OPERATOR_ID"));
PrivateKey operatorKey = PrivateKey.fromString(env.get("OPERATOR_KEY"));
// Connect to testnet using the operator account as the default payer.
Client client = Client.forTestnet();
client.setOperator(operatorId, operatorKey);
// 1. Query the operator's balance.
Hbar balance = new AccountBalanceQuery()
.setAccountId(operatorId)
.execute(client)
.hbars;
System.out.println("Operator balance: " + balance);
// 2. Transfer 1 HBAR to account 0.0.3 (a test recipient).
AccountId recipient = AccountId.fromString("0.0.3");
TransactionResponse response = new TransferTransaction()
.addHbarTransfer(operatorId, Hbar.from(-1))
.addHbarTransfer(recipient, Hbar.from(1))
.execute(client);
TransactionReceipt receipt = response.getReceipt(client);
System.out.println("Transfer status: " + receipt.status);
System.out.println("Transaction ID: " + response.transactionId);
client.close();
}
}
```
Add a `.env` loader to your dependencies if you don't have one. `io.github.cdimascio:dotenv-java:3.0.0` is the common pick.
## Step 4: Run it
```bash theme={null}
# Maven (package as a JAR with dependencies, then run it)
mvn package
java -cp target/your-artifact-with-dependencies.jar HederaQuickstart
# Gradle
./gradlew run
```
Expected output:
```text theme={null}
Operator balance: 10000 ℏ
Transfer status: SUCCESS
Transaction ID: 0.0.1234@1700000000.123456789
```
Look up the transaction on HashScan:
```text theme={null}
https://hashscan.io/testnet/transaction/
```
**Prefer a local network?** [Solo](https://solo.hiero.org/docs/) runs a full Hedera stack on your machine, no testnet rate limits, no faucet, no resets. See [Using Solo with Hiero SDKs](https://solo.hiero.org/docs/using-solo/using-solo-with-hiero-sdks/) to point the SDK at a local Solo network instead of testnet.
## What's next
Generate a new account programmatically and fund it from your operator.
Mint a native HTS token with custom supply and decimals.
Publish a message to HCS for verifiable, ordered audit logs.
Full API reference on GitHub.
The four Hiero SDKs (JavaScript, Java, Go, Python) share the same API surface, so code translates almost line-for-line between them. Differences are mostly language-idiomatic.
# Quickstart
Source: https://docs.hedera.com/native/quickstart/javascript
This quickstart walks you through submitting your first Hedera transaction using the playground, creating and funding a testnet account via the developer portal, and viewing the transaction on HashScan.
***
## Submit Your First Transaction
Under the **Account & HBAR** native services, click the **Transfer HBAR** transaction from the left navigation
1. Replace `receiverAccount` with account ID "0.0.800"
2. Then click on **Get Account Balance** under Queries
Click the **Execute** button to submit your first transaction.
When you click **Execute**, you’ll be prompted to sign up for a developer portal account. Once logged in, click the **CREATE ACCOUNT** button to complete the testnet account creation flow.
Your new testnet account will be automatically funded with **1000 HBAR**. View your account ID and key pair from the portal dashboard.
View and verify the transaction details and success confirmation. Click the HashScan link from the transaction output on the playground. View your transaction details, account history, and network activity.
```
-------------------------------- Transfer HBAR ------------------------------
Receipt status : SUCCESS
Transaction ID : 0.0.6239936@1751330868.909246536
Hashscan URL : https://hashscan.io/testnet/tx/0.0.6239936@1751330868.909246536
-------------------------------- Account Balance ------------------------------
HBAR account balance : 995.99724961 ℏ
Token account balance : {}
```
***
**Prefer a local network?** [Solo](https://solo.hiero.org/docs/) runs a full Hedera stack on your machine, no testnet rate limits, no faucet, no resets. See [Using Solo with Hiero SDKs](https://solo.hiero.org/docs/using-solo/using-solo-with-hiero-sdks/) to point the SDK at a local Solo network instead of testnet.
## Next Step
* [Create an Account](/learn/getting-started/create-portal-account)
* [Create a Token](/native/tutorials/tokens/create-first-token)
* [Create a Topic](/native/tutorials/consensus/create-first-topic)
# Create a schedule transaction
Source: https://docs.hedera.com/native/scheduled/create
A `ScheduleCreateTransaction` is a consensus node transaction that creates a schedule entity on the Hedera network. The entity ID for a schedule transaction is called a `ScheduleID`. After successfully executing a schedule create transaction, you can retrieve the network assigned `ScheduleID` by requesting the transaction receipt. The receipt also includes the scheduled transaction ID, which can be used to request the record of the scheduled transaction after its successful execution.
When creating a transaction to schedule you do not need to use `.freezeWith(client)` method.
Example:
```java Java theme={null}
TransferTransaction transactionToSchedule = new TransferTransaction()
.addHbarTransfer(newAccountId, Hbar.fromTinybars(-1))
.addHbarTransfer(operatorId, Hbar.fromTinybars(1));
```
Refer to this [page](/learn/core-concepts/transactions/scheduled#overview) to view the types of transactions that can be scheduled.
### **Schedule Transaction Duplicate**
If two users submit the same `ScheduleCreateTransaction`, the first transaction to reach consensus will create the **schedule ID**.
For example, if User A and User B submit the same scheduled transaction and User A's transaction reaches consensus first, User B's transaction will return the status `IDENTICAL_SCHEDULE_ALREADY_CREATED`. If User B is required to sign the scheduled transaction, they must submit and sign a `ScheduleSignTransaction` to add their signature.
### **Schedule Transaction Deletion**
To retain the ability to delete a schedule transaction, you will need to set the admin key field when creating a schedule transaction. The admin key will be required to sign the `ScheduleDeleteTransaction` to delete the scheduled transaction from the network. If you do not assign an admin key during the creation of the schedule transaction, you will have an immutable schedule transaction.
### **Transaction Signing Requirements**
* The signature of the account paying for the creation of the schedule transaction
* The signature of the payer account responsible for covering the execution fees of the scheduled transaction. For example, if you are scheduling a transfer transaction, the assigned transaction fee payer must provide a signature. If no transaction fee payer account is specified, the schedule's transaction fee payer account will be used by default to cover the execution fees.
* The admin key signature, if set
* You can optionally sign with any of the required signatures for the scheduled transaction. Freeze the schedule transaction and call the `.sign()` method to add signatures in this transaction.
### **Transaction Properties**
| **Field** | **Description** |
| -------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Schedulable Transaction Body** | The transaction body of the transaction that is being scheduled. Ex. Transfer Transaction |
| **Admin Key** | A key that can delete the schedule transaction prior to execution or expiration |
| **Payer Account ID** | The account which is going to pay the transaction fees for the execution of the scheduled transaction. If not populated, the account paying for the schedule transaction will be charged (optional). |
| **Expiration Time** | A timestamp for specifying when the transaction should be evaluated for execution and then expire (optional). The maximum allowed value is ***62 days (***[***5356800 seconds***](https://github.com/hashgraph/hedera-services/blob/develop/hedera-node/hedera-config/src/main/java/com/hedera/node/config/data/SchedulingConfig.java#L35)***)***. |
| **Wait for Expiry** | Set the transaction to execute at the specified expiration time. The default behavior is to execute at the time the minimum number of signatures are received (optional). |
| **Memo** | Publicly visible information about the schedule entity, up to 100 bytes. No guarantee of uniqueness (optional). |
### Methods
| **Method** | **Type** | **Requirement** |
| ---------------------------------------- | ----------- | --------------- |
| `setScheduledTransaction()` | Transaction | Required |
| `setAdminKey()` | Key | Optional |
| `setPayerAccountId()` | AccountId | Optional |
| `setScheduleMemo()` | String | Optional |
| `setExpirationTime(expirationTime)` | Instant | Optional |
| `setWaitForExpiry()` | boolean | Optional |
| `setHighVolume()` | boolean | Optional |
| `getAdminKey()` | Key | Optional |
| `getPayerAccountId()` | AccountId | Optional |
| `getScheduleMemo()` | String | Optional |
| `getHighVolume()` | boolean | Optional |
This transaction supports [high-volume entity creation](/learn/core-concepts/high-volume-entity-creation)
(HIP-1313). Setting `setHighVolume(true)` routes the transaction through dedicated
high-volume throttle capacity with variable-rate pricing. Always pair this with
`setMaxTransactionFee()` to cap your costs.
```java Java theme={null}
//Create a schedule transaction
ScheduleCreateTransaction transaction = new ScheduleCreateTransaction()
.setScheduledTransaction(transactionToSchedule);
//Sign with the client operator key and submit the transaction to a Hedera network
TransactionResponse txResponse = transaction.execute(client);
//Request the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the schedule ID
ScheduleId scheduleId = receipt.scheduleId;
System.out.println("The schedule ID of the schedule transaction is " +scheduleId);
```
```javascript JavaScript theme={null}
//Create a schedule transaction
const transaction = new ScheduleCreateTransaction()
.setScheduledTransaction(transactionToSchedule);
//Sign with the client operator key and submit the transaction to a Hedera network
const txResponse = await transaction.execute(client);
//Request the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the schedule ID
const scheduleId = receipt.scheduleId;
console.log("The schedule ID of the schedule transaction is " +scheduleId);
```
```go Go theme={null}
//Create a schedule transaction
transaction, err := transactionToSchedule.Schedule()
if err != nil {
panic(err)
}
//Sign with the client operator key and submit the transaction to a Hedera network
txResponse, err := transaction.Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the schedule ID from the receipt
scheduleId := *receipt.ScheduleID
fmt.Printf("The new token ID is %v\n", scheduleId)
```
```rust Rust theme={null}
// Create a schedule transaction
let transaction = ScheduleCreateTransaction::new()
.scheduled_transaction(transaction_to_schedule);
// Sign with the client operator key and submit the transaction to a Hedera network
let tx_response = transaction.execute(&client).await?;
// Request the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the schedule ID
let schedule_id = receipt.schedule_id.unwrap();
println!("The schedule ID of the schedule transaction is {:?}", schedule_id);
// v0.34.0
```
# Delete a schedule transaction
Source: https://docs.hedera.com/native/scheduled/delete
A `ScheduleDeleteTransaction` is a consensus node transaction that removes a scheduled transaction from the network. A scheduled transaction can only be deleted if an admin key was set during its creation. If no admin key was set, any attempt to delete it will result in a `SCHEDULE_IS_IMMUTABLE` response from the network. Once successfully deleted, the scheduled transaction will be marked as deleted, and the consensus timestamp of the deletion will be recorded.
**Transaction Signing Requirements**
* The signature of the admin key
**Transaction Properties**
| Field | Description |
| --------------- | ---------------------------------- |
| **Schedule ID** | The ID of the schedule transaction |
## Methods
Method
Type
Requirement
setScheduleId(\)
ScheduleId
Required
```java Java theme={null}
//Create the transaction and sign with the admin key
ScheduleDeleteTransaction transaction = new ScheduleDeleteTransaction()
.setScheduleId(scheduleId)
.freezeWith(client)
.sign(adminKey);
//Sign with the operator key and submit to a Hedera network
TransactionResponse txResponse = transaction.execute(client);
//Get the transaction receipt
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
```
```javascript JavaScript theme={null}
//Create the transaction and sign with the admin key
const transaction = await new ScheduleDeleteTransaction()
.setScheduleId(scheduleId)
.freezeWith(client)
.sign(adminKey);
//Sign with the operator key and submit to a Hedera network
const txResponse = await transaction.execute(client);
//Get the transaction receipt
const receipt = await txResponse.getReceipt(client);
//Get the transaction status
const transactionStatus = receipt.status;
console.log("The transaction consensus status is " +transactionStatus);
```
```go Go theme={null}
//Create the transaction and freeze the unsigned transaction
transaction, err := hedera.NewScheduleDeleteTransaction()
SetScheduleID(scheduleId).
FreezeWith(client)
if err != nil {
panic(err)
}
//Sign with the admin key, sign with the client operator private key and submit the transaction to a Hedera network
txResponse, err := transaction.Sign(adminKey).Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction consensus status
status:= *receipt.Status
fmt.Printf("The transaction consensus status is %v\n", status)
```
```rust Rust theme={null}
// Create the transaction and sign with the admin key
let transaction = ScheduleDeleteTransaction::new()
.schedule_id(schedule_id)
.freeze_with(&client)?
.sign(admin_key);
// Sign with the operator key and submit to a Hedera network
let tx_response = transaction.execute(&client).await?;
// Get the transaction receipt
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
# Get schedule info
Source: https://docs.hedera.com/native/scheduled/get-info
`ScheduleInfoQuery` is a consensus node query that returns information about the current state of a schedule transaction on a Hedera network.
**Schedule Info Response**
| Field | Description |
| ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
| **Schedule ID** | The ID of the schedule transaction. |
| **Creator Account ID** | The Hedera account that created the schedule transaction in x.y.z format. |
| **Payer Account ID** | The Hedera account paying for the execution of the scheduled transaction in x.y.z format. |
| **Scheduled Transaction Body** | The transaction body of the transaction that is being scheduled by the schedule transaction. |
| **Signatories** | The public keys that have signed the transaction. |
| **Admin Key** | The key that can delete the schedule transaction, if set |
| **Expiration Time** | The date and time the schedule transaction will expire |
| **Executed Time** | The time the schedule transaction was executed. If the schedule transaction has not executed this field will be left null. |
| **Deletion Time** | The consensus time the schedule transaction was deleted. If the schedule transaction was not deleted, this field will be left null. |
| **Memo** | Publicly visible information about the Schedule entity, up to 100 bytes. No guarantee of uniqueness. |
#### **Recommend Using Mirror Node REST API**
For obtaining schedule information and historical data, consider using the Mirror Node REST API endpoint [**Get Schedule by ID**](https://docs.hedera.com/api-reference/schedules/get-schedule-by-id) which offers several advantages:
* **Cost-effective and scalable:** [Mirror node providers](/operators/mirror-node#mainnet) offer paid plans with a large number of queries included. The Hedera-hosted mirror node offers free queries with specific throttles for testing. While some SDK queries are currently free, these are subject to change in the future.
* **Performance:** Mirror nodes don't burden consensus nodes, allowing them to focus on processing transactions and providing efficient access to historical data without impacting network performance.
* **Historical data:** Mirror nodes store complete transaction history, records, and events - ideal for analytics, auditing, and monitoring past activity.
**Query Signing Requirements**
* The transaction fee payer account key is required to sign
### Methods
Method
Type
Requirement
setScheduleId(\)
ScheduleId
Required
\.scheduleId
ScheduleId
Optional
\.scheduledTransactionId
TransactionId
Optional
\.creatorAccountId
AccountId
Optional
\.payerAccountId
AccountId
Optional
\.adminKey
Key
Optional
\.signatories
Key
Optional
\.deletedAt
Instant
Optional
\.expirationAt
Instant
Optional
\.memo
String
Optional
\.waitForExpiry
boolean
Optional
```java Java theme={null}
//Create the query
ScheduleInfoQuery query = new ScheduleInfoQuery()
.setScheduleId(scheduleId);
//Sign with the client operator private key and submit the query request to a node in a Hedera network
ScheduleInfo info = query.execute(client);
```
```javascript JavaScript theme={null}
//Create the query
const query = new ScheduleInfoQuery()
.setScheduleId(scheduleId);
//Sign with the client operator private key and submit the query request to a node in a Hedera network
const info = await query.execute(client);
```
```go Go theme={null}
//Create the query
query := hedera.NewScheduleInfoQuery().
SetScheduleID(scheduleId)
//Sign with the client operator private key and submit to a Hedera network
scheduleInfo, err := query.Execute(client)
if err != nil {
panic(err)
}
```
```rust Rust theme={null}
// Create the query
let query = ScheduleInfoQuery::new()
.schedule_id(schedule_id);
// Sign with the client operator private key and submit the query request to a node in a Hedera network
let info = query.execute(&client).await?;
// Print the schedule info
println!("Schedule ID: {:?}", info.schedule_id);
println!("Creator Account ID: {:?}", info.creator_account_id);
println!("Payer Account ID: {:?}", info.payer_account_id);
println!("Admin Key: {:?}", info.admin_key);
println!("Signatories: {:?}", info.signatories);
println!("Expiration Time: {:?}", info.expiration_time);
println!("Executed Time: {:?}", info.executed_time);
println!("Deleted Time: {:?}", info.deleted_time);
println!("Memo: {:?}", info.memo);
println!("Wait For Expiry: {:?}", info.wait_for_expiry);
// v0.34.0
```
# Network Response Messages
Source: https://docs.hedera.com/native/scheduled/response-messages
Network Response
Description
INVALID\_SCHEDULE\_ID
The Scheduled entity does not exist; or has now expired, been deleted, or been executed
SCHEDULE\_IS\_IMMUTABLE
The Scheduled entity cannot be modified. Admin key was not set during the creation of the Scheduled entity.
INVALID\_SCHEDULE\_PAYER\_ID
The provided Scheduled Payer does not exist
INVALID\_SCHEDULE\_ACCOUNT\_ID
The Schedule Create Transaction TransactionID account does not exist
NO\_NEW\_VALID\_SIGNATURES
The provided sig map did not contain any new valid signatures from required signers of the scheduled transaction
UNRESOLVABLE\_REQUIRED\_SIGNERS
The required signers for a scheduled transaction cannot be resolved, for example because they do not exist or have been deleted
UNPARSEABLE\_SCHEDULED\_TRANSACTION
The bytes allegedly representing a transaction to be scheduled could not be parsed
UNSCHEDULABLE\_TRANSACTION
ScheduleCreate and ScheduleSign transactions cannot be scheduled
SOME\_SIGNATURES\_WERE\_INVALID
At least one of the signatures in the provided sig map did not represent a valid signature for any required signer
TRANSACTION\_ID\_FIELD\_NOT\_ALLOWED
The scheduled and nonce fields in the TransactionID may not be set in a top-level transaction
IDENTICAL\_SCHEDULE\_ALREADY\_CREATED
A schedule already exists with the same identifying fields of an attempted ScheduleCreate (that is, all fields other than scheduledPayerAccountID)
SCHEDULE\_ALREADY\_DELETED
A schedule being signed or deleted has already been deleted
SCHEDULE\_PENDING\_EXPIRATION
A schedule being signed or deleted has passed it's expiration date and is pending execution if needed and then expiration
SCHEDULE\_FUTURE\_GAS\_LIMIT\_EXCEEDED
The scheduled transaction could not be created because it would cause the gas limit to be violated on the specified expiration time
SCHEDULE\_FUTURE\_THROTTLE\_EXCEEDED
The scheduled transaction could not be created because it would cause throttles to be violated on the specified expiration time
The scheduled transaction could not be created because it's expiration\_time was less than or equal to the consensus time
SCHEDULE\_EXPIRATION\_TIME\_TOO\_FAR\_IN\_FUTURE
The scheduled transaction could not be created because it's expiration time was too far in the future
SCHEDULE\_EXPIRY\_MUST\_BE\_FUTURE
A scheduled transaction configured to wait for expiry to execute was given an expiry time not strictly after the time at which its creation reached consensus
SCHEDULE\_EXPIRY\_TOO\_LONG
A scheduled transaction configured to wait for expiry to execute was given an expiry time too far into the future after the time which its creation reached consensus
SCHEDULE\_EXPIRY\_IS\_BUSY
A scheduled transaction configured to wait for expiry to execute was given at a time which there is already too many transactions scheduled to expire; its creation must be retried with a different expiry time
# Schedule ID
Source: https://docs.hedera.com/native/scheduled/schedule-id
The entity ID of a schedule transaction.
A `ScheduleId` is composed of a \.\.\ (eg. 0.0.10).
* **shardNum** represents the shard number (`shardId`). It will default to 0 today, as Hedera only performs in one shard.
* **realmNum** represents the realm number (`realmId`). It will default to 0 today, as realms are not yet supported.
* **scheduleNum** represents the schedule number (`scheduleId`)
Together these values make up your `ScheduleId`. When a `ScheduleId` is requested in a field, be sure enter all three values.
Constructor
Type
Description
new ScheduleId(\,\,\)
long, long, long
Constructs a ScheduleId with 0 for shardNum and realmNum (e.g., 0.0.\)
### Example
```java theme={null}
ScheduleId scheduleID = new ScheduleId(0,0,10);
System.out.println(scheduleID)
```
```javascript theme={null}
const scheduleID = new ScheduleId(0,0,10);
console.log(scheduleID)
```
```rust theme={null}
// Create a schedule ID
let schedule_id = ScheduleId::new(0, 0, 10);
println!("{:?}", schedule_id);
// v0.34.0
```
# Sign a scheduled transaction
Source: https://docs.hedera.com/native/scheduled/sign
A `ScheduleSignTransaction` is a consensus node transaction that adds signatures to a scheduled transaction. When this transaction is successful:
* The signature is added to the scheduled transaction
* A record of the transaction is created
To view the signatures that have been added to a scheduled transaction, you can use a [`ScheduleInfoQuery`](/native/scheduled/get-info) to query the network. Once the scheduled transaction has received all the required signatures, it will execute immediately, unless it has been configured to execute at a specified expiration time.
**Transaction Signing Requirements**
* The signature of the account paying for the transaction fees
* The signature being applied to the scheduled transaction
**Transaction Properties**
| Field | Description |
| --------------- | -------------------------------------------------------------- |
| **Schedule ID** | The ID of the schedule transaction to submit the signature for |
## Methods
Method
Type
Requirement
setScheduleId(\)
ScheduleId
Required
```java Java Java theme={null}
//Create the transaction
ScheduleSignTransaction transaction = new ScheduleSignTransaction()
.setScheduleId(scheduleId)
.freezeWith(client)
.sign(privateKeySigner1);
//Sign with the client operator key to pay for the transaction and submit to a Hedera network
TransactionResponse txResponse = transaction.execute(client);
//Get the receipt of the transaction
TransactionReceipt receipt = txResponse.getReceipt(client);
//Get the transaction status
Status transactionStatus = receipt.status;
System.out.println("The transaction consensus status is " +transactionStatus);
```
```javascript JavaScript theme={null}
//Create the transaction
const transaction = await new ScheduleSignTransaction()
.setScheduleId(scheduleId)
.freezeWith(client)
.sign(privateKeySigner1);
//Sign with the client operator key to pay for the transaction and submit to a Hedera network
const txResponse = await transaction.execute(client);
//Get the receipt of the transaction
const receipt = await txResponse.getReceipt(client);
//Get the transaction status
const transactionStatus = receipt.status;
console.log("The transaction consensus status is " +transactionStatus);
```
```go Go theme={null}
//Create the transaction and freeze the unsigned transaction
transaction, err := hedera.NewScheduleSignTransaction().
SetScheduleID(scheduleId).
FreezeWith(client)
if err != nil {
panic(err)
}
//Sign with one of the required signatures, sign with the client operator private key and submit the transaction to a Hedera network
txResponse, err := transaction.Sign(privateKeySigner1).Execute(client)
if err != nil {
panic(err)
}
//Request the receipt of the transaction
receipt, err := txResponse.GetReceipt(client)
if err != nil {
panic(err)
}
//Get the transaction consensus status
status:= *receipt.Status
fmt.Printf("The transaction consensus status is %v\n", status)
```
```rust Rust theme={null}
// Create the transaction
let transaction = ScheduleSignTransaction::new()
.schedule_id(schedule_id)
.freeze_with(&client)?
.sign(private_key_signer1);
// Sign with the client operator key to pay for the transaction and submit to a Hedera network
let tx_response = transaction.execute(&client).await?;
// Get the receipt of the transaction
let receipt = tx_response.get_receipt(&client).await?;
// Get the transaction status
let status = receipt.status;
println!("The transaction consensus status is {:?}", status);
// v0.34.0
```
# Local Provider
Source: https://docs.hedera.com/native/signature-provider/local-provider
This feature is available in the [Hedera JavaScript SDK](https://github.com/hashgraph/hedera-sdk-js) only. (version >=2.14.0).
LocalProvider is a quality of life implementation that creates a provider using the `HEDERA_NETWORK` environment variable.
The `LocalProvider()` requires the following variable to be defined in the `.env` file. The `.env` file is located in the root directory of the project.
* `HEDERA_NETWORK`
* The network the wallet submits transactions to
```.env theme={null}
//Example .env file
HEDERA_NETWORK= previewnet/testnet/mainnet (select one network)
```
## class LocalProvider implements Wallet
### Constructor
#### new LocalProvider`()`
Instantiates the LocalProvider object. The local provider is built using `HEDERA_NETWORK` network specified in the `.env` file.
### Methods
#### **`.getAccountBalance()`** **-> Promise \**
Returns the account balance of the account in the local wallet.
#### **`.getAccountInfo()`** **-> Promise \**
Returns the account information of the account in the local wallet.
#### **`.getAccountRecords()`** **-> Promise \**
Returns the last transaction records for this account using `TransactionRecordQuery`.
#### **`.getLedgerId()>`** **LedgerId**
Returns the ledger ID (`previewnet`, `testnet`, or `mainnet`).
#### **`.getMirrorNetwork()>`** **string**
The mirror network the wallet is connected to.
#### **`.getNetwork()>`** **\[key: string]: string | AccountId**
Returns the network map information.
#### **`.getTransactionReceipt()>`** Promise\
Returns the transaction receipt.
#### **`.waitForReceipt()>`** Promise\
Wait for the receipt for a transaction response.
**`.call((request: Executable)>`** **`Promise