HIP-32 introduced the ability to auto-create accounts when sending HBAR to an alias that does not exist on the network. When HBAR is sent to an alias that does not exist on the network, the account creation fee is deducted from the HBAR sent and the account is auto-created. The new account's initial balance is (sent HBAR - account creation fee). This new method of account creation allowed wallet providers to create free "accounts" to users. However, if a user sends fungible tokens or NFT's to an alias, it would result in an INVALID_ACCOUNT_ID error because the alias does not exist on the network. The auto-account creation flow could not deduct the account creation fee from an HTS token; the account creation fee must be paid in HBAR.
HIP-542 provides a solution to allow sending HTS tokens to an alias that does not exist on the network. This is achieved by charging the account creation fee to the transfer transaction payer. In addition, there will be one auto-association slot included in the transaction for the new account to associate with the HTS token. You won't have to first create the account, complete a token association, and then finally do a token transfer.
Furthermore, this change also applies to sending HBAR to an alias. Instead of deducting the creation fee from the sent HBAR, it will be deducted from the payer of the transfer transaction. The new account balance will receive the sent HBAR amount in full. Learn more here.
The figure below highlights the transaction flow before HIP-542 and after.
In this tutorial, a treasury account will be created to transfer HTS tokens to Bob's ECDSA public key alias, involving a transfer of 10 FT and 1 NFT. The tutorial will also cover creating fungible tokens and an NFT collection (1000 FT / 5 NFT), creating Bob's ECDSA public key alias, and returning Bob's new account ID while confirming their ownership of the transferred tokens and NFT.
We create the treasury account which will be the holder of the fungible and non-fungible tokens. The treasury account will be created with an initial balance of 100 HBAR.
We will create the functions necessary to create a new account, create fungible tokens, and create a new NFT collection. Use the tabs to see the helper functions.
exportconstcreateFungibleToken=async ( client:Client, treasureyAccId:string|AccountId, supplyKey:PrivateKey, treasuryAccPvKey:PrivateKey, initialSupply:number, tokenName:string, tokenSymbol:string,):Promise<TokenId> => {/* * Create a transaction with token type fungible * Returns Fungible Token Id */constcreateTokenTxn=awaitnewTokenCreateTransaction().setTokenName(tokenName).setTokenSymbol(tokenSymbol).setTokenType(TokenType.FungibleCommon).setInitialSupply(initialSupply).setTreasuryAccountId(treasureyAccId).setSupplyKey(supplyKey).setMaxTransactionFee(newHbar(30)).freezeWith(client); //freeze tx from from any further mods.constcreateTokenTxnSigned=awaitcreateTokenTxn.sign(treasuryAccPvKey);// submit txn to heder networkconsttxnResponse=awaitcreateTokenTxnSigned.execute(client);// request receipt of txnconsttxnRx=awaittxnResponse.getReceipt(client);consttxnStatus=txnRx.status.toString();consttokenId=txnRx.tokenId;if (tokenId ===null) {thrownewError("Somehow tokenId is null"); }console.log(`Token Type Creation was a ${txnStatus} and was created with token id: ${tokenId}` );return tokenId;};
exportconstcreateNonFungibleToken=async ( client:Client, treasureyAccId:string|AccountId, supplyKey:PrivateKey, treasuryAccPvKey:PrivateKey, initialSupply:number, tokenName:string, tokenSymbol:string,):Promise<[TokenId|null,string]> => {/* * Create a transaction with token type fungible * Returns Fungible Token Id and Token Id in solidity format */constcreateTokenTxn=awaitnewTokenCreateTransaction().setTokenName(tokenName).setTokenSymbol(tokenSymbol).setTokenType(TokenType.NonFungibleUnique).setDecimals(0).setInitialSupply(initialSupply).setTreasuryAccountId(treasureyAccId).setSupplyKey(supplyKey).setAdminKey(treasuryAccPvKey).setMaxTransactionFee(newHbar(30)).freezeWith(client); //freeze tx from from any further mods.constcreateTokenTxnSigned=awaitcreateTokenTxn.sign(treasuryAccPvKey);// submit txn to hedera networkconsttxnResponse=awaitcreateTokenTxnSigned.execute(client);// request receipt of txnconsttxnRx=awaittxnResponse.getReceipt(client);consttxnStatus=txnRx.status.toString();consttokenId=txnRx.tokenId;if (tokenId ===null) { thrownewError("Somehow tokenId is null."); }consttokenIdInSolidityFormat=tokenId.toSolidityAddress();console.log(`Token Type Creation was a ${txnStatus} and was created with token id: ${tokenId}` );return [tokenId, tokenIdInSolidityFormat];};
constmintTokenTxn=newTokenMintTransaction().setTokenId(tokenId).setMetadata(metadatas).freezeWith(client);constmintTokenTxnSigned=awaitmintTokenTxn.sign(supplyKey);// submit txn to hedera networkconsttxnResponse=awaitmintTokenTxnSigned.execute(client);constmintTokenRx=awaittxnResponse.getReceipt(client);constmintTokenStatus=mintTokenRx.status.toString();console.log(`Token mint was a ${mintTokenStatus}`);};exportconstcreateNewNftCollection=async ( client:Client, tokenName:string, tokenSymbol:string, metadataIPFSUrls:Buffer[],// already uploaded ipfs metadata json files treasuryAccountId:string|AccountId, treasuryAccountPrivateKey:PrivateKey,): Promise<{ tokenId:TokenId, supplyKey:PrivateKey,}> => {// generate supply keyconstsupplyKey=PrivateKey.generateECDSA();const [tokenId,] =awaitcreateNonFungibleToken(client, treasuryAccountId, supplyKey, treasuryAccountPrivateKey,0, tokenName, tokenSymbol);if (tokenId ===null|| tokenId ===undefined) {thrownewError("Somehow tokenId is null"); }constmetadatas:Uint8Array[] =metadataIPFSUrls.map(url =>Buffer.from(url));// mint tokenawaitmintToken(client, tokenId, metadatas, supplyKey);return { tokenId: tokenId, supplyKey: supplyKey, };}
Create FTs and Create an NFT Collection
Leverage the createFungibleToken helper function defined above to create 10000 "Hip-542 example" fungible tokens. Use the code tab switch on the upper left of the code block to see how we use createNewNftCollection to create our new NFT collection consisting of 5 NFTs.
// IPFS content identifiers for the NFT metadataconstmetadataIPFSUrls:Buffer[] = [Buffer.from("ipfs://bafkreiap62fsqxmo4hy45bmwiqolqqtkhtehghqauixvv5mcq7uofdpvt4"),Buffer.from("ipfs://bafkreibvluvlf36lilrqoaum54ga3nlumms34m4kab2x67f5piofmo5fsa"),Buffer.from("ipfs://bafkreidrqy67amvygjnvgr2mgdgqg2alaowoy34ljubot6qwf6bcf4yma4"),Buffer.from("ipfs://bafkreicoorrcx3d4foreggz72aedxhosuk3cjgumglstokuhw2cmz22n7u"),Buffer.from("ipfs://bafkreidv7k5vfn6gnj5mhahnrvhxep4okw75dwbt6o4r3rhe3ktraddf5a"), ];/** * Step 2 * Create nft collection */constnftCreateTxnResponse=awaitcreateNewNftCollection(client,'HIP-542 Example Collection','HIP-542', metadataIPFSUrls, treasuryAccId, treasuryAccPvKey);
Create Bob's ECDSA Public Key Alias
An alias is an initial public key that will convert into a Hedera account through auto-account creation. An alias consists of <shard>.<realm>.<bytes>.
To learn more about accounts created via an account alias go here.
constprivateKey=PrivateKey.generateECDSA();constpublicKey=privateKey.publicKey;// Assuming that the target shard and realm are known.// For now they are virtually always 0 and 0.constaliasAccountId=publicKey.toAccountId(0,0);console.log(`- New account ID: ${aliasAccountId.toString()}`);if (aliasAccountId.aliasKey ===null) { thrownewError('alias key is empty') }console.log(`- Just the aliasKey: ${aliasAccountId.aliasKey.toString()}\n`);
Set up helper functions for transferring HTS tokens
Once we have our treasury account with FT and a new NFT collection created, our next step is to transfer them to Bob using their alias. We'll create the sendToken helper function to send fungible tokens and create transferNft to send a single NFT.
A quick reminder to use the tab on the left of the code block to switch between the two helper functions.
exportconstsendToken=async (client:Client, tokenId:TokenId, owner:AccountId, aliasAccountId:AccountId, sendBalance:number, treasuryAccPvKey:PrivateKey) => {consttokenTransferTx=newTransferTransaction().addTokenTransfer(tokenId, owner,-sendBalance).addTokenTransfer(tokenId, aliasAccountId, sendBalance).freezeWith(client);// Sign the transaction with the operator keylet tokenTransferTxSign =awaittokenTransferTx.sign(treasuryAccPvKey);// Submit the transaction to the Hedera networklet tokenTransferSubmit =awaittokenTransferTxSign.execute(client);// Get transaction receipt informationawaittokenTransferSubmit.getReceipt(client);}
exportconsttransferNft=async (client:Client, nftTokenId:TokenId, nftId:number, treasuryAccId:AccountId, treasuryAccPvKey:PrivateKey, aliasAccountId:AccountId) => {constnftTransferTx=newTransferTransaction().addNftTransfer(nftTokenId, nftId, treasuryAccId, aliasAccountId).freezeWith(client);// Sign the transaction with the treasury account private keyconstnftTransferTxSign=awaitnftTransferTx.sign(treasuryAccPvKey);// Submit the transaction to the Hedera networkconstnftTransferSubmit=awaitnftTransferTxSign.execute(client);// Get transaction receipt information hereawaitnftTransferSubmit.getReceipt(client);}
Transfer FT and an NFT to Bob using their alias
Transfer 10 fungible tokens to Bob using their alias and the helper function sendToken.
Transfer the NFT with serial number 1 to Bob using the helper function transfertNFT.
Next we call getAccountIdByAlias and pass in our client and Bob's alias as the arguments.
constaccountId=awaitgetAccountIdByAlias(client, aliasAccountId);console.log(`The normal account ID of the given alias: ${accountId}`);
Show Bob's new account owns the 10 FT tokens
Complete an AccountBalanceQuery to show that Bob's new account owns the 10 fungible tokens the treasury account sent.
constaccountBalances=awaitnewAccountBalanceQuery().setAccountId(aliasAccountId).execute(client);if (!accountBalances.tokens ||!accountBalances.tokens._map) {thrownewError('account balance shows no tokens.') }consttokenBalanceAccountId=accountBalances.tokens._map.get(tokenId.toString());if (!tokenBalanceAccountId) {thrownewError(`account balance does not have tokens for token id: ${tokenId}.`); }tokenBalanceAccountId.toInt() ===10?console.log(`Account is created successfully using HTS 'TransferTransaction'` ):console.log("Creating account with HTS using public key alias failed" );client.close();
Show Bob's new account owns the NFT
First create a helper function that creates a TokenNftInfoQuery transaction and returns the account id of the nft owner for a specific nft serial number.
exportconstgetNftOwnerByNftId=async (client:Client, nftTokenId:TokenId, exampleNftId:number) => {constnftInfo=awaitnewTokenNftInfoQuery().setNftId(newNftId(nftTokenId, exampleNftId)).execute(client);if (nftInfo ===null) { thrownewError('nftInfo is null.') }constnftOwnerAccountId= nftInfo[0].accountId.toString();console.log(`- Current owner account id: ${nftOwnerAccountId} for NFT with serial number: ${exampleNftId}`);return nftOwnerAccountId;}
Then call getNftOwnerByNft and do a simple check to ensure the account id returned matches the account id created when we sent the NFT to Bob's alias.
constnftOwnerAccountId=awaitgetNftOwnerByNftId(client, nftTokenId, exampleNftId); nftOwnerAccountId === accountId?console.log(`The NFT owner accountId matches the accountId created with the HTS\n` ):console.log(`The two account IDs does not match\n`);client.close();
Console Output ✅
And that's a wrap! 🎬 You've completed sending HTS tokens to an alias and triggering an auto-account creation! As well as learned that the account creation fee is paid by the payer of the transfer transaction.
Join and collaborate with Hedera Developers on the Hedera Discord Server!
Happy Building! 🛠️