This series of articles will show you how to send and receive Hedera Tokens using Smart Contracts. In this first article, you will learn how to send a fungible token from an account to a contract using the JS SDK.
This example shows just one scenario. If you’d like, you can check the other scenarios on GitHub directly.
Your workspace should look like this:
Let’s start by creating some helper functions in utils.js. You need to make an accountCreator function that will be responsible for account creation by providing a private key, an initial balance, and your client. You must also define a tokenCreator function that deals with fungible token creation. Keep in mind that you can change parameters if you want to.
If you are not familiar with token creation, I suggest you check out this previous article.
// Creates a new account 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; } // Creates a new Fungible Token (change parameters if needed) async function tokenCreator(treasuryId, treasuryKey, client) { //Create the transaction and freeze for manual signing const createToken = await new TokenCreateTransaction() .setTokenName("USD Bar") // Name .setTokenSymbol("USDB") // Symbol .setTreasuryAccountId(treasuryId) // Treasury account .setInitialSupply(10000) // Initial supply .setSupplyKey(treasuryKey) // Supply key .setDecimals(2) // Decimals .setMaxTransactionFee(new Hbar(20)) // Change the default max transaction fee .freezeWith(client); // Sign with treasuryKey const createTokenTx = await createToken.sign(treasuryKey); const createTokenRx = await createTokenTx.execute(client); const createTokenReceipt = await createTokenRx.getReceipt(client); const tokenId = createTokenReceipt.tokenId; return tokenId; }
Now you can use the previously created functions to initialize in your index.js file a token sender account (Alice), an admin account for your contract, and a fungible token.
// Create admin key for contract const adminKey = PrivateKey.generateED25519(); const adminId = await accountCreator(adminKey, 10, client); // Alice is the sender const aliceKey = PrivateKey.generateED25519(); const aliceId = await accountCreator(aliceKey, 10, client);
// Create token with Alice as treasury const tokenId = await tokenCreator(aliceId, aliceKey, client); console.log("The new token ID is " + tokenId);
Console Output:
Your Hedera Token receiver will be an empty contract called TokenReceiver.sol because you won’t need any particular functionality.
Solidity Contract:
// SPDX-License-Identifier: GPL-3.0 pragma solidity >=0.6.0 <0.9.0; contract TokenReceiver { // EMPTY CONTRACT }
You can now compile this contract to get bytecode either using Remix IDE or solc. You can also find this contract's bytecode in this Codesandbox.
Next, you have to create your contract using ContractCreateFlow() and set an admin key. (Keep hold of this key, as you'll need it in the following steps!). For now, you need to switch operators (for the client) before executing the transaction, as you need to sign using the contract's admin key.
// Load bytecode const bytecode = fs.readFileSync('TokenReceiver_sol_TokenReceiver.bin'); // Switch operator to sign transaction client.setOperator(adminId, adminKey); // Create contract with adminKey const createContract = new ContractCreateFlow() .setGas(100000) .setBytecode(bytecode) .setAdminKey(adminKey) const createSubmit = await createContract.execute(client); const createRx = await createSubmit.getReceipt(client); const contractId = createRx.contractId; console.log("The new contract ID is " + contractId); // Switch operator back to operator account client.setOperator(operatorId, operatorKey);
So now that your contract is on the Hedera Network, you can use the admin key to associate your token to your contract, so the token can be received.
// Associate token to contract (sign using adminKey) const associateToken = await new TokenAssociateTransaction() .setAccountId(AccountId.fromString(contractId)) .setTokenIds([tokenId]) .freezeWith(client) .sign(adminKey); const associateTokenTx = await associateToken.execute(client); const associateTokenRx = await associateTokenTx.getReceipt(client); const associateTokenStatus = associateTokenRx.status; console.log("The associate transaction status: " + associateTokenStatus.toString());
It's time to remove the contract's admin key, as a keyless contract ensures a trustless workflow. You can use a ContractUpdateTransaction() and specify an empty KeyList.
// Update contract and remove contract's admin key const contractUpdate = await new ContractUpdateTransaction() .setContractId(contractId) .setAdminKey(new KeyList()) .freezeWith(client) .sign(adminKey); const contractUpdateTx = await contractUpdate.execute(client); const contractUpdateRx = await contractUpdateTx.getReceipt(client); console.log("Contract update status: " + contractUpdateRx.status.toString());
Now you can transfer the fungible token to your contract using the TransferTransaction() and by specifying the amount.
// Transfer token from Alice to the contract using SDK const transferToken = new TransferTransaction() .addTokenTransfer(tokenId, aliceId, -1000) // -10 USDB .addTokenTransfer(tokenId, contractId, 1000) // +10 USDB .freezeWith(client); const transferTokenTx = await transferToken.sign(aliceKey); const transferTokenSubmit = await transferTokenTx.execute(client); const transferTokenRx = await transferTokenSubmit.getReceipt(client); const transferTokenStatus = transferTokenRx.status; console.log("The transfer transaction status: " + transferTokenStatus.toString());
Awesome! Now your contract has received the fungible token.
To get more details about your token transfer, you can search for your transaction on HashScan or get the contract's balance. Let's try to execute a ContractInfoQuery()!
//Check contract's balance const query = new ContractInfoQuery() .setContractId(contractId); const info = await query.execute(client); const balance = info.tokenRelationships.get(tokenId).balance / 100; console.log("The contract balance for token " + tokenId + " is: " + balance);
Mission accomplished! As you can see, your contract now owns a token amount of 10.
Check out Part 2 to learn more about transferring a token.
Feel free to contact us on our Hedera Discord Server if you have questions about this article.
Check out this code example on GitHub.
Hedera Token Service (Hedera documentation)
What are Smart Contracts? (Hedera Learning Center)