Deploy a Contract Using the Hedera Token Service
Summary
In this example, you will learn how to create a Solidity contract that interacts with the Hedera Token Service (HTS). The initial release of this feature supports token mint, burn, associate, dissociate, and transfer transactions.
The example does not cover the environment setup or creating certain variables that may be seen in the code blocks. The full coding example can be found at the end of the page.
Smart contract entity auto renewal and expiry will be enabled in a future release. Please check out HIP-16 for more information.
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. Create Your "HTS" Smart Contract
In this example, you will associate a token to an account and transfer tokens to the associated account by interacting with the HTS contract deployed to Hedera. The HTS contract has three functions that allow you to associate, transfer, and dissociate tokens from a Hedera account.
tokenAssociate
tokenTransfer
tokenDissociate
The HTS.sol will serve as a reference to the contract that was compiled. The HTS.json file contains the data.bytecode.object
field that will be used to store the contract bytecode in a file on the Hedera network.
To write a contract using HTS, you will need to add the HTS Solidity support libraries to your project and import them into your contract. Please see the HTS.sol example for reference. The IHederaTokenService.sol will need to be in the same directory as the other two files. An explanation of the functions can be found here.
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.6.12;
import "./HederaTokenService.sol";
import "./HederaResponseCodes.sol";
contract HTS is HederaTokenService {
function tokenAssociate(address sender, address tokenAddress) external {
int response = HederaTokenService.associateToken(sender, tokenAddress);
if (response != HederaResponseCodes.SUCCESS) {
revert ("Associate Failed");
}
}
function tokenTransfer(address tokenId, address fromAccountId , address toAccountId , int64 tokenAmount) external {
int response = HederaTokenService.transferToken(tokenId, fromAccountId, toAccountId, tokenAmount);
if (response != HederaResponseCodes.SUCCESS) {
revert ("Transfer Failed");
}
}
function tokenDissociate(address sender, address tokenAddress) external {
int response = HederaTokenService.dissociateToken(sender, tokenAddress);
if (response != HederaResponseCodes.SUCCESS) {
revert ("Dissociate Failed");
}
}
}
2. Store the Smart Contract Bytecode on Hedera
Create a file using the FileCreateTransaction()
API to store the hex-encoded byte code of the "HTS" contract. Once the file is created, you can obtain the file ID from the receipt of the transaction.
Note: The bytecode is required to be hex-encoded. It should not be the actual data the hex represents.
//Import the HTS.json file from the resources folder
ClassLoader cl = HTS.class.getClassLoader();
Gson gson = new Gson();
JsonObject jsonObject;
//Get the json file
InputStream jsonStream = cl.getResourceAsStream("HTS.json");
jsonObject = gson.fromJson(new InputStreamReader(jsonStream, StandardCharsets.UTF_8), JsonObject.class);
//Store the "object" field from the HTS.json file as hex-encoded bytecode
String object = jsonObject.getAsJsonObject("data").getAsJsonObject("bytecode").get("object").getAsString();
byte[] bytecode = object.getBytes(StandardCharsets.UTF_8);
//Create a file on Hedera and store the hex-encoded bytecode
FileCreateTransaction fileCreateTx = new FileCreateTransaction()
.setKeys(privateKeyTest)
.setContents(bytecode);
//Submit the file to the Hedera test network
TransactionResponse submitTx = fileCreateTx.execute(client);
//Get the receipt of the file create transaction
TransactionReceipt fileReceipt = submitTx.getReceipt(client);
//Get the file ID
FileId newFileId = fileReceipt.fileId;
//Log the file ID
System.out.println("The smart contract byte code file ID is " + newFileId);
//v2.6.0 Hedera Java SDK
3. Deploy a Hedera Smart Contract
Create the contract and set the file ID to the file that contains the hex-encoded bytecode from the previous step. You will need to set the gas high enough to deploy the contract. The gas should be estimated to be within 25% of the actual gas cost to avoid paying extra gas. You can read more about gas and fees here.
Note: You will need to set the gas value high enough to deploy the contract. 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.
//Deploy the contract
ContractCreateTransaction contractTx = new ContractCreateTransaction()
//The contract bytecode file
.setBytecodeFileId(newFileId)
//The max gas to reserve for this transaction
.setGas(2_000_000);
//Submit the transaction to the Hedera test network
TransactionResponse contractResponse = contractTx.execute(client);
//Get the receipt of the file create transaction
TransactionReceipt contractReceipt = contractResponse.getReceipt(client);
//Get the smart contract ID
ContractId newContractId = contractReceipt.contractId;
//Log the smart contract ID
System.out.println("The smart contract ID is " + newContractId);
//v2.6.0 Hedera Java SDK
4. Call the tokenAssociate
Contract Function
tokenAssociate
Contract FunctionThe tokenAssociate
function in the contract will associate a token that was created using the native Hedera Token Service. You can create a new token using HTS or using an existing token in this example. Use the ContractExecuteTransaction()
API to call the contract's tokenAssociate
function. You will pass the token ID and account ID in Solidity address
format to the contract function. The contract function parameters must be provided in the order expected by the function to execute successfully.
//Associate the token to an account using the HTS contract
ContractExecuteTransaction associateToken = new ContractExecuteTransaction()
//The contract to call
.setContractId(newContractId)
//The gas for the transaction
.setGas(2_000_000)
//The contract function to call and parameters to pass
.setFunction("tokenAssociate", new ContractFunctionParameters()
//The account ID to associate the token to
.addAddress(accountIdTest.toSolidityAddress())
//The token ID to associate to the account
.addAddress(tokenId.toSolidityAddress()));
//Sign with the account key to associate and submit to the Hedera network
TransactionResponse associateTokenResponse = associateToken.freezeWith(client).sign(privateKeyTest).execute(client);
System.out.println("The transaction status: " +associateTokenResponse.getReceipt(client).status);
5. Get the tokenAssociate
Transaction Record
tokenAssociate
Transaction RecordThe contract execute transaction that triggered a subsequent token associate transaction in the contract is an example of a nested transaction. The contract execute transaction is the parent transaction and the token associate transaction is referred to as the child transaction. Both parent and child transactions share the same payer account ID and transaction valid duration with the exception of the child transaction having a nonce value at the end. The nonce value increments for each subsequent child transaction.
Parent Transaction ID: 0.0.2252@1640119571.329880313
Child Transaction ID: 0.0.2252@1640119571.329880313/1
The parent transaction record, receipt, or response will only return the parent transaction information. If you would like to get the child transaction record, receipt, or response you will need to use the TransactionRecordQuery()
or TransactionReceiptQuery()
and set children equal to true. The child transaction record will also have a parentConsensusTimestamp
field populated with the consensus timestamp of the parent transaction.
To confirm the account was associated with the token, request the balance of the account. The account balance should show the ID of the token that was associated with a zero balance.
//Get the child token associate transaction record
TransactionRecord childRecords = new TransactionRecordQuery()
//Set the bool flag equal to true
.setIncludeChildren(true)
//The transaction ID of th parent contract execute transaction
.setTransactionId(associateTokenResponse.transactionId)
.execute(client);
System.out.println("The transaction record for the associate transaction" +childRecords.children.get(0));
//The balance of the account
AccountBalance accountBalance3 = new AccountBalanceQuery()
.setAccountId(accountIdTest)
.execute(client);
System.out.println("The " + tokenId + " should now be associated to my account: " + accountBalance3.tokens);
6. Call the tokenTransfer
Contract Function
tokenTransfer
Contract FunctionTransfer 100 units of the token to the account that was associated with the token. You will use the ContractExecuteTransaction()
API and set the contract function to tokenTransfer
. The contract function parameters must be provided in the order of the function expects to receive them.
The transaction must be signed by the account that is sending the tokens. In this case, it is the treasury account.
You can verify the transfer was successful by checking the account token balance!
//Transfer the new token to the account
//Contract function params need to be in the order of the paramters provided in the tokenTransfer contract function
ContractExecuteTransaction tokenTransfer = new ContractExecuteTransaction()
.setContractId(newContractId)
.setGas(2_000_000)
.setFunction("tokenTransfer", new ContractFunctionParameters()
//The ID of the token
.addAddress(tokenId.toSolidityAddress())
//The account to transfer the tokens from
.addAddress(treasuryAccountId.toSolidityAddress())
//The account to transfer the tokens to
.addAddress(accountIdTest.toSolidityAddress())
//The number of tokens to transfer
.addInt64(100));
//Sign the token transfer transaction with the treasury account to authorize the transfer and submit
ContractExecuteTransaction signTokenTransfer = tokenTransfer.freezeWith(client).sign(treasuryKey);
//Submit transfer transaction
TransactionResponse submitTransfer = signTokenTransfer.execute(client);
//Get transaction status
Status txStatus = submitTransfer.getReceipt(client).status;
//Verify your account received the 100 tokens
AccountBalance newAccountBalance = new AccountBalanceQuery()
.setAccountId(accountIdTest)
.execute(client);
System.out.println("My new account balance is " +newAccountBalance.tokens);
Congratulations 🎉! You have completed the following:
Created a smart contract that calls HTS transactions
Associated a HTS token by using the deployed contract
Requested a transaction record for a nested transaction
Transferred tokens using the deployed contract
Code Check ✅
Last updated
Was this helpful?