How to Use Hethers.js to Deploy Smart Contracts on Hedera
Headshot
Apr 11, 2022
by Ed Marquez
Developer Evangelist

Deploying and interacting with smart contracts on the Hedera network is easier than ever before thanks to new tools and libraries like Hethers.js, Buidler Labs’ Strato JS, and Dovu’s Hardhat tooling. This article is focused on Hethers.js and it shows how you can start using this library to deploy and interact with smart contracts on Hedera.

The Hethers.js library is an adaptation of The Ethers Project. It implements the same program interface as the ethers.js library but with some changes to support working on Hedera with JavaScript. So, Hedera + ethers.js = Hethers.js.

You can find the official GitHub repository for Hethers.js here.

Try It Yourself

What Can I Do with Hethers.js and Why Should I Use It?

  • Use a Provider / Signer architecture for your Hedera application. This is similar to how things are done in Ethereum and can help you migrate existing applications to Hedera more easily
  • Do more with less code:
    • Deploy smart contracts without writing a lot of code. Deploying contracts more easily means having more time to work on key functionality in your application and perform more testing
    • Connect and interact with already-deployed contracts with a single line of code, reducing the complexity in your application and improving the experience of your developers
    • Sign messages, query, and listen to events in your contracts

Now let’s see a three-step example using Hethers.js to deploy an ERC20 contract on Hedera and interact with it by calling its functions and listening to events.

How to Use Hethers.js

Install and import the library

The first step is to install Hethers.js as you prepare your development environment.

Code Snippet Background
npm install --save @hashgraph/hethers

Once Hethers.js is installed in your project, its various Classes and Functions are available to import manually from sub-packages under @hethers, but for most projects the whole package is the easiest way to start. Below is the Node JS import. For more information on web browser import, check out the getting started documentation.

Code Snippet Background
const { hethers } = require("@hashgraph/hethers");

Define the Hedera Accounts

In this example, there are two relevant Hedera accounts: Signer and Alice. The Hedera testnet credentials for these accounts, like account IDs and private keys, are defined in a .env file present in the working directory. Remember that the AccountId and PrivateKey modules are imported from the Hedera Javascript SDK.

At the time of this writing, Hethers.js only supports ECDSA private keys, thus the private key for the signer must be of that type. ED25519 keys will be supported soon.

Code Snippet Background
const signerId = AccountId.fromString(process.env.SIGNER_ID);
const signerKey = PrivateKey.fromString(process.env.SIGNER_PVKEY); // TO WORK WITH HETHERS, IT MUST BE ECDSA KEY (FOR NOW)
const aliceId = AccountId.fromString(process.env.ALICE_ID);

Since addresses are used in the smart contract of this example, use the Hethers utilities to convert the Signer’s and Alice’s Hedera account IDs to Solidity addresses.

Code Snippet Background
const walletAddress = hethers.utils.getAddressFromAccount(signerId);
const aliceAddress = hethers.utils.getAddressFromAccount(aliceId);

Step 1.1: Connect to Hedera Provider

A Provider is a class that provides an abstraction for a connection to the Hedera network. Providers give you a concise and consistent interface to Hedera consensus node and mirror node functionality. For this example, use the default provider to connect to the Hedera testnet. Keep in mind that with Hethers.js you can also have a more custom configuration if needed.

Code Snippet Background
// STEP 1 - INITIALIZE A PROVIDER AND WALLET
console.log(`\n- STEP 1 ===================================`);

const provider = hethers.providers.getDefaultProvider("testnet");

Step 1.2: Instantiate Wallet (Signer)

A Wallet is a class that enables you to sign transactions and messages using an account ID and private key. The Wallet class inherits from the Signer class. This inheritance is what enables a wallet to sign transactions and authorize the network to charge hbar to an account as operations are performed.

The Signer class is abstract and cannot be directly instantiated. Instead, always use the Wallet class.

For this example, start by defining an externally owned account (EOA) containing the account ID and private key of the wallet (eoaAccount variable). Remember that Hethers.js only supports ECDSA private keys at the time of this writing. In addition, you must convert the private key to short format using the .toStringRaw() method. Finally, remember to have an hbar balance in the Hedera account that you wish to use as the wallet to be able to pay for transactions.

Notice that the eoaAccount and provider variables are passed to the hethers.Wallet class in order to instantiate the wallet. In this case the wallet is fully functional because both a private key and account ID were provided (via eoaAccount). You can also instantiate a wallet with just a private key, but that wallet would not be fully functional. Learn more about the differences in the Hethers.js documentation.

Now, perform your first query with the wallet.getBalance() method.

Code Snippet Background
const eoaAccount = {
        account: signerId,
        privateKey: `0x${signerKey.toStringRaw()}`, // Convert private key to short format using .toStringRaw()
    };
const wallet = new hethers.Wallet(eoaAccount, provider);
console.log(`\n- Alice's address: ${aliceAddress}`);
console.log(`\n- Wallet address: ${wallet.address}`);
console.log(`\n- Wallet public key: ${wallet.publicKey}`);

const balance = await wallet.getBalance(walletAddress);
console.log(`\n- Wallet address balance: ${hethers.utils.formatHbar(balance.toString())} hbar`);

Console Output:

- STEP 1 ===================================

- Wallet address: 0x000000000000000000000000000000000208d84e

- Wallet public key: 0x04fd91f3f41e197445c87b9e77d7bee2b3d0ec254cc6b14595c8e3b69356e635db28336a3966199e952d4408d10515b51da7033f7518e124e5c281a4482fb8f70d199e952d4408d10515b51da7033f7518e124e5c281a448

- Wallet address balance: 6135.22068806 hbar

Step 2: Deploy a Contract and Interact with It

In step 2, deploy the smart contract on Hedera using the contract’s application binary interface (ABI), the contract bytecode, and the wallet you instantiated in the previous step.

Start by instantiating a ContractFactory as the factory variable. Next, use the deploy() method of the contract factory to deploy the contract. Be sure to pass any arguments required by the constructor function and specify the gas limit. This approach simplifies the process of deploying a smart contract on Hedera by executing certain transactions (FileCreateTransaction, FileAppendTransaction, ContractCreateTransaction) “under the hood”, thus reducing the complexity and amount of code needed for your application.

After deployment, you can get transaction, receipt, and contract address information.

Code Snippet Background
// STEP 2 - DEPLOY THE CONTRACT
console.log(`\n- STEP 2 ===================================`);

// Define the contract's properties
const bytecode = fs.readFileSync("./contractBytecode.bin").toString();
const abi = [
	"constructor(uint totalSupply)",

	// Read-Only Functions
	"function balanceOf(address owner) view returns (uint256)",
	"function decimals() view returns (uint8)",
	"function symbol() view returns (string)",

	// Authenticated Functions
	"function transfer(address to, uint amount) returns (bool)",

	// Events
	"event Transfer(address indexed from, address indexed to, uint amount)",
];

// Create a ContractFactory object
const factory = new hethers.ContractFactory(abi, bytecode, wallet);

// Deploy the contract
const contract = await factory.deploy(100, { gasLimit: 300000 });

// Transaction sent by the wallet (signer) for deployment - for info
const contractDeployTx = contract.deployTransaction;

// Wait until the transaction reaches consensus (i.e. contract is deployed)
//  - returns the receipt
//  - throws on failure (the reciept is on the error)
const contractDeployWait = await contract.deployTransaction.wait();
console.log(`\n- Contract deployment status: ${contractDeployWait.status.toString()}`);

// Get the address of the deployed contract
contractAddress = contract.address;
console.log(`\n- Contract address: ${contractAddress}`);

Console Output:

- STEP 2 ===================================

- Contract deployment status: 1

- Contract address: 0x0000000000000000000000000000000002098478

Step 3: Interact with the Deployed Contract

In step 3, interact with the contract you just deployed by calling its functions and listening to events.

Start by setting up a filter to know when the wallet address sends tokens to another address. Notice from the ABI (step 2), that the contract you deployed has an event called Transfer, which indexes the from and to addresses when the function transfer is called. Also, setup an event listener that uses the filter to output a message to the console with the event information once a transfer happens.

Finally, call the contract’s functions symbol, transfer, and balanceOf to get information about the token, transfer 25 tokens from the wallet to Alice, and to check address balances of the token, respectively. Notice that after the transfer takes place, the event information is also captured.

Code Snippet Background
// STEP 3 - INTERACT WITH THE DEPLOYED CONTRACT
console.log(`\n- STEP 3 ===================================`);

// Setup a filter and event listener to know when an address receives/sends tokens
const filter = contract.filters.Transfer(walletAddress, null);

contract.once(filter, (from, to, amount, event) => {
	console.log(`\n- Event: ${from} sent ${amount} tokens to ${to}`);
});

// Call contract functions
const ercSymbol = await contract.symbol({ gasLimit: 300000 });
console.log(`\n- ERC20 token symbol: ${ercSymbol}`);

const ercTransfer = await contract.transfer(aliceAddress, 25, { gasLimit: 300000 });
console.log(`\n- Transaction ID for ERC20 transfer: ${ercTransfer.transactionId}`);

const wBalance = await contract.balanceOf(walletAddress, { gasLimit: 300000 });
const aBalance = await contract.balanceOf(aliceAddress, { gasLimit: 300000 });
console.log(`\n- Wallet ERC20 token (${ercSymbol}) balance: ${wBalance.toString()}`);
console.log(`\n- Alice's ERC20 token (${ercSymbol}) balance: ${aBalance.toString()}`);

console.log(`\n- DONE ===================================`);

Console Output:

- STEP 3 ===================================

- ERC20 token symbol: MyToken

- Transaction ID for ERC20 transfer: 0.0.34134094-1649674634-768316411

- Wallet ERC20 token (MyToken) balance: 75

- Alice's ERC20 token (MyToken) balance: 25

- DONE ===================================

- Event: 0x000000000000000000000000000000000208D84e sent 25 tokens to 0x000000000000000000000000000000000028Ad48

For this example, you accessed the contract in step 2 immediately after deployment. However, you can also connect to a different contract that is already deployed using the hethers.Contract class.

And that’s it! Now you know how to use Hether.js to start deploying and interacting with smart contracts on Hedera without writing a lot of code.

For feedback on this article or future articles you would like to see, let us know via the Hedera Discord server.

Check Out the Code and Try It

Continue Learning about Smart Contracts