In this article, you will learn how to use a keyless Solidity smart contract to manage your Hedera tokens. Specifically, you will create a token that has a contract as the treasury account and auto-renew account.
Having a keyless contract ensures a transparent and trustless workflow, where the contract itself is responsible for managing various aspects of the token.
First, to simplify account creation, you can introduce this helper function:
async function accountCreator(privateKey, initialBalance, client) { const response = await new AccountCreateTransaction() .setInitialBalance(new Hbar(initialBalance)) .setKey(privateKey.publicKey) .execute(client); const receipt = await response.getReceipt(client); return receipt.accountId; }
So now to generate an admin account just call the accountCreator function like this:
// Admin account creation const adminKey = PrivateKey.generateED25519(); const adminId = await accountCreator(adminKey, 50, client);
The contract you will set as a token treasury in this example will be a simple empty contract as you won’t need any particular functionality.
Solidity Contract:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; contract TreasuryContract { // EMPTY CONTRACT }
You can get your contract bytecode directly from this Codesandbox or by compiling your contract on Remix IDE or using solc.
Now you can create the contract that will be the treasury of your fungible token using the ContractCreateFlow() method and specifying your adminKey. For now, you need to switch the operator using client.setOperator() to your admin account as you need to sign the transaction using the adminKey.
const bytecode = fs.readFileSync("./TreasuryContract_sol_TreasuryContract.bin"); // Switch operator to admin to sign transactions client.setOperator(adminId, adminKey); // Create contract with adminKey const contractCreate = new ContractCreateFlow() .setGas(100000) .setBytecode(bytecode) .setAdminKey(adminKey); const contractCreateTx = await contractCreate.execute(client); const contractCreateRx = await contractCreateTx.getReceipt(client); const contractId = contractCreateRx.contractId; console.log("Contract created with ID: " + contractId);
Console Output:
It’s time to create your fungible token, if you are not familiar with token creation you can check out this article.
// Create fungible token using contract as treasury const tokenCreate = new TokenCreateTransaction() .setTokenName("USD Bar") .setTokenSymbol("USDB") .setTreasuryAccountId(AccountId.fromString(contractId)) .setInitialSupply(10000) .setDecimals(2) .setAutoRenewAccountId(contractId) .setAutoRenewPeriod(7000000) .setMaxTransactionFee(new Hbar(30)) .freezeWith(client); const signedTokenCreate = await tokenCreate.sign(adminKey); const tokenCreateTx = await signedTokenCreate.execute(client); const tokenCreateRx = await tokenCreateTx.getReceipt(client); const tokenId = tokenCreateRx.tokenId; console.log("Token created with ID: " + tokenId);
After creating your token, you need to remove the admin key from your contract. To proceed you need to execute a ContractUpdateTransaction() and set as new admin key an empty KeyList().
const contractUpdate = await new ContractUpdateTransaction() .setContractId(contractId) .setAdminKey(new KeyList()) .execute(client); const contractUpdateRx = await contractUpdate.getReceipt(client); console.log("Contract update status: " + contractUpdateRx.status.toString());
Done! Now your keyless contract is successfully managing the HTS token.
Setting the contract as token treasury is straightforward when creating a token using a smart contract function.
Let’s start by creating a contract with a createFungible method responsible for token creation. Notice how inside this function, the token treasury and token auto-renew account is the contract itself ( address(this) ).
Make sure to include these files in your working directory by downloading them from the contracts folder here:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; import './HederaResponseCodes.sol'; import './IHederaTokenService.sol'; import './HederaTokenService.sol'; import './ExpiryHelper.sol'; contract TreasuryContract is ExpiryHelper{ function createFungible( string memory name, string memory symbol, uint initialSupply, uint decimals, uint32 autoRenewPeriod ) external payable returns (address createdTokenAddress) { IHederaTokenService.HederaToken memory token; token.name = name; token.symbol = symbol; token.treasury = address(this); // create the expiry schedule for the token using ExpiryHelper token.expiry = getAutoRenewExpiry(address(this), autoRenewPeriod); // call HTS precompiled contract, passing initial supply and decimals (int responseCode, address tokenAddress) = HederaTokenService.createFungibleToken(token, initialSupply, decimals); if (responseCode != HederaResponseCodes.SUCCESS) { revert (); } createdTokenAddress = tokenAddress; } }
If you want to compile your contract you can either use Remix IDE or solc. You can also get contract bytecode through this Codesandbox.
This time to create your contract you won’t need to set any admin key:
const bytecode = fs.readFileSync("./binaries/TreasuryContract_sol_TreasuryContract.bin"); const contractCreate = new ContractCreateFlow() .setGas(100000) .setBytecode(bytecode); const contractCreateTx = await contractCreate.execute(client); const contractCreateRx = await contractCreateTx.getReceipt(client); const contractId = contractCreateRx.contractId; console.log("Contract created with ID: " + contractId);
And to create your token with your contract as treasury you just need to execute your contract function.
// Create FT using precompile function const createToken = new ContractExecuteTransaction() .setContractId(contractId) .setGas(300000) // Increase if revert .setPayableAmount(20) // Increase if revert .setFunction("createFungible", new ContractFunctionParameters() .addString("USD Bar") // FT name .addString("USDB") // FT symbol .addUint256(10000) // FT initial supply .addUint256(2) // FT decimals .addUint32(7000000)); // auto renew period 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);
Done! You just learned a completely trustless way to manage Hedera tokens using a smart contract.
For further explanation on this article, please contact us on Hedera Discord Server.
Check out the example using SDK on Github
Check out the example using Solidity on Github
Hedera Service Solidity Libraries (Hedera Documentation)
Hedera Token Service (Hedera Documentation)
What are Smart Contracts? (Hedera Learning Center)