How to Send and Receive Hedera Tokens Using Smart Contracts - Part 1: SDKs
Screen Shot 2022 01 27 at 9 52 08 PM
Aug 05, 2022
by Francesco Coacci
Developer Evangelist

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.

Account2 Contract

This example shows just one scenario. If you’d like, you can check the other scenarios on GitHub directly.

Try It Yourself

  • Get a Hedera testnet account
  • Use this Codesandbox to try the following example
    • Fork the sandbox
    • Remember to provide credentials in the .env file
    • Open a new terminal to execute index.js
  • Get the complete code of this example on GitHub

Workspace Structure

Your workspace should look like this:

  • index.js
  • utils.js
  • TokenReceiver.sol
  • .env

1. Set Up Helper Functions

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.

Code Snippet Background
// 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;
}

2. Define Hedera Accounts

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.

Code Snippet Background
// 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);
Code Snippet Background
// Create token with Alice as treasury
const tokenId = await tokenCreator(aliceId, aliceKey, client);
 
console.log("The new token ID is " + tokenId);

Console Output:

1

3. Create and Store your Contract on the Hedera Network

Your Hedera Token receiver will be an empty contract called TokenReceiver.sol because you won’t need any particular functionality.

Solidity Contract:

Code Snippet Background
// 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.

Code Snippet Background
// 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);

Console Output:

2

4. Associate Token and Remove the Admin Key

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.

Code Snippet Background
// 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());

Console Output:

3

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.

Code Snippet Background
// 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());

Console Output:

4

5. Transfer Hedera Token to your Contract

Now you can transfer the fungible token to your contract using the TransferTransaction() and by specifying the amount.

Code Snippet Background
// 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());

Console Output:

5

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()!

Code Snippet Background
//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);

Console Output:

6

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 the Code

Check out this code example on GitHub.

Continue Learning

Hedera Token Service (Hedera documentation)

What are Smart Contracts? (Hedera Learning Center)