Hedera SDK JS
Hedera SDK JS tutorial - HSCS workshop. Learn how to enable custom logic & processing on Hedera through smart contracts.
Video
Hedera SDK JS
The Hedera network offers multiple services:
Hedera Smart Contract Service (HSCS)
Hedera File Service (HFS)
Hedera Token Service (HTS)
Hedera Consensus Service (HCS)
Each service defines a number of different ways you can interact with it as a developer, and these comprise the Hedera Application Programming Interfaces (HAPIs). However, HAPIs are very close to the metal, and a developer needs to handle gRPCs and protocol buffers (among other things) to work with them successfully. Thankfully there are Hedera SDKs, which abstract these low-level complexities away. These SDKs allow you to interact with the various Hedera services via APIs exposed in a variety of different programming languages.
In this tutorial, you will be using Hedera SDK JS to interact with HSCS. Specifically, you will use it to deploy a smart contract, query its state by invoking functions, and modify its state by invoking other functions.
Prerequisites
✅ Complete the Introduction section of this same tutorial.
Set up the project
To follow along, enter the hederasdkjs
directory within the accompanying tutorial GitHub repository, which you should already have cloned in the Intro section earlier.
Compiling smart contracts
Step D1: Copy smart contract
We have already written the smart contract in the Intro section of this tutorial. Let's copy that into this directory so that we may continue working on it.
Using solc
to compile smart contracts
solc
to compile smart contractsLet's install the Solidity compiler, solc
from npm.
You can verify that it has installed successfully by asking it to output its version. Note that while the package name on npm is solc
, the executable present on PATH
is spelled slightly differently: solcjs
.
If it does not error, and outputs its version, you know it has installed successfully.
Let's explore its command line interface:
There are relatively few flags and options. In this tutorial, you will only be using --bin
, and --abi
.
Let's compile the Solidity file.
Those flags instruct solcjs
to output both EVM bytecode and ABI. The ls
command lists the files that are in the directory, and the following files should be present.
Examine the EVM bytecode output
The binary file contains the EVM bytecode: trogdor_sol_Trogdor.bin
. This is not intended to be human-readable.
Nothing much we can glean by looking at this really!
This bytecode is used to deploy the smart contract onto the Hedera network.
Examine the ABI output
Open trogdor_sol_Trogdor.abi
If you are using a POSIX-compliant shell, and have jq
installed, you can view the ABI output like so.
The ABI essentially tells any user/ developer who wishes to interact with the EVM bytecode, what the exposed interface is. In fact ABI stands for Application Binary Interface. This interface will include any functions and events, which are needed by any clients (e.g. DApps), or other smart contracts, to be able to interact with it.
This is extremely useful, because by examining the bytecode, which is what is deployed onto the Hedera network, you are likely to have no idea what it does, or how to interact with it. If you have the corresponding ABI in hand, however, you will have a very good idea of how you can interact with this smart contract, and perhaps can infer what it does as well.
Deploying smart contracts
Let's edit the deploy-sc.js
file. In this script, you'll use Hedera SDK JS to deploy your smart contract onto Hedera Testnet.
Step E1: Initialise operator account
This script has already been set up to read in environment variables from the .env
file that you have set up in the Intro section of this tutorial via the dotenv
npm package, and they are now accessible using process.env
.
We will use the OPERATOR_ID
and OPERATOR_KEY
environment variables to initialise an operator account, connect to Hedera Testnet.
Step E2: Read the EVM bytecode from file
One of the outputs from running solc
earlier was the binary file, which contains EVM bytecode. Let's read this from disk into memory.
Step E3: Use HFS to store EVM bytecode on network
Next, write the EVM bytecode onto Hedera Testnet using HFS. In order to do so, you will need to use FileCreateTransaction
.
Note that you can use FileCreateTransaction
for any type of file that is up to 1024KB in size. You are not restricted to only EVM bytecode.
In the final line above, obtain the file ID from the FileCreateTransaction
's receipt, as fileId
- you will need it later.
Step E4: Deploy a smart contract on HSCS by referencing the bytecode on HFS
Now we're finally able to deploy the smart contract onto HSCS. In order to do so, you will need to use ContractCreateTransaction
.
The fileId
that you obtained in the previous step references the EVM bytecode stored on HFS. The ContractCreateTransaction
references this file on HFS during the deployment process.
In the final line above, obtain the smart contract ID from the ContractCreateTransaction
's receipt, as scId
- you will need it later.
The smart contract is now deployed, and ready to be interacted with.
Run the script.
You should see output similar to the following, which contains:
HFS FileCreateTransaction TransactionReceipt
HSCS ContractCreateTransaction TransactionReceipt
Deployed to
For both of the TransactionReceipt
check that their status
is { _code: 22 }
, which means that the transaction was successful.
The Deployed to
outputs the account ID of the smart contract that you have just deployed.
Check smart contract deployment using Hashscan
Copy the output smart contract account ID, e.g.
0.0.15388539
.Visit Hashscan
Paste the copied account ID into the search box.
You should get redirected to a "Contract" page, e.g.
https://hashscan.io/testnet/contract/0.0.15388539
.In it you can see the EVM address, e.g.
0x0000000000000000000000000000000000eacf7b
.Under "Contract Bytecode", you can see "Runtime Bytecode".
Interacting with smart contacts
Let's edit the interact-sc.js
file. In this script, you'll use Hedera SDK JS to interact with your smart contract on Hedera Testnet.
Step F1: Specify deployed contract ID
Copy the smart contract ID, obtained during the previous step, and add paste this into this file, where the main
function is invoked (at the bottom of the file).
Step F2: Initialise operator account
Similar to what we did in the deployment script, we will use the OPERATOR_ID
and OPERATOR_KEY
environment variables to initialise an operator account, connect to Hedera Testnet.
Step F3: Invoke payable function with zero value
The burninate
function in this smart contract is public
and payable
. This means that the function may be invoked with a transaction that has a value (HBAR) attached to it - accessible as msg.value
in Solidity. The value will be added to this smart contracts balance if this function is executed successfully.
Recall that when you implemented the burninate
function in the Intro section of this tutorial, that there is this require statement:
This essentially specifies that the function will error, and therefore not execute successfully, when the value sent with the transaction is anything less than 100 tinybar (MIN_FEE
).
Now we're going to invoke this function with a zero value transaction, i.e. Invoke burninate
with msg.value = 0
. This is done on purpose, to trip up this require statement, so that we can witness the rejection.
To do so, use ContractExecuteTransaction
.
This will send a transaction to Hedera Testnet, which contains a request to HSCS to (potentially) modify the state of this smart contract.
When this is run, we expect the transaction to fail, with a CONTRACT_REVERT_EXECUTED
error. The reason for this is the require
statement in this function, as described above - we need to send some HBAR!
Step F4: Invoke payable function with non-zero value
Next invoke the same burninate
function once again, with only one change: the transaction will contain a value of 123 tinybars, i.e. msg.value = 123
.
This time, the function invocation will succeed, as it passes that require
statement.
Step F5: Invoke view function with no parameters
The burninate
function is one that can (and does) modify the persisted state of the smart contract. However there are other functions which do not do so, and instead merely read (query) the currently persisted state of the smart contract. These functions have the view
modifier.
There are also other functions which neither read the currently nor modify the persisted state of the smart contract. These functions have the pure
modifier.
These are typically used as utility functions, intended to be invoked by other functions within a smart contract.
The totalBurnt
is a view
function, and to invoke that, let's use ContractCallQuery
.
ContractExecuteTransaction
: Use for modifying stateContractCallQuery
: Use for reading state
Once the ContractCallQuery
is executed, extract the its return value using the getter function with the appropriate type. Since the totalBurnt
function specifies returns(uint256)
in its signature, use getUint256()
to extract that return value.
The ContractCallQuery
has setQueryPayment
, which is to pay for the costs of querying the data. Note that this is different from other EVM-compatible networks, which allow you to query smart contract state without paying any fee.\
Step F6: Convert account ID to EVM address
In the subsequent step, we will use the operator account as an input parameter in a function invocation. However, we need to convert this from an Account ID format, which looks like 0.0.3996280
, to an EVM address format, which looks like 0x7394111093687e9710b7a7aeba3ba0f417c54474
. This is because the EVM (and by extension Solidity), does not understand Hedera-native accounts. Instead it only understands EVM accounts.
To do so, we start with the private key of the operator account, from that we derive its public key, and finally from that we derive its EVM account. Thankfully Hedera SDK JS has utility functions for these, and the conversion can be performed quite easily.
Step F7: Invoke auto-generated view function with parameters
In this smart contract amounts
is a view
function, and to invoke that, let's use ContractCallQuery
. There are a couple of key differences though:
The
amounts
function requires an input parameter, or typeaddress
The
amounts
function was not written using Solidity code, But instead was auto-generated by the Solidity compiler for thepublic
state variable with the same name.
Let's send a ContractCallQuery
to the amounts
function. Use the operatorEvmAddress
obtained in the previous step as the input parameter.
There is a ContractFunctionParameters
, which we've used in the previous smart contract invocations, but it was always "empty", in the sense that there were no parameters. Since amounts
requires a single parameter of type address
, use addAddress()
to specify its value.
Once the ContractCallQuery
is executed, extract the its return value using the getter function with the appropriate type. The amounts
mapping specifies uint256
as its value type, this is equivalent to a function specifying returns(uint256)
in its signature. Use getUint256()
to extract that return value.
Run the script.
You should get output similar to the following:
ContractExecuteTransaction #1 ReceiptStatusError
ContractExecuteTransaction #2 TransactionReceipt
ContractCallQuery #1 ContractFunctionResult
return value
Note that the first ContractExecuteTransaction
fails, and this is expected. On the other hand, the second ContractExecuteTransaction
passes, because this time we sent the payable
the required number of HBAR.
The ContractFunctionResult
has queried the data, and return value
simply extracts the relevant value from it.
Check smart contract interactions using Hashscan
Visit the "Contract" page for your previously deployed smart contract, e.g.
https://hashscan.io/testnet/contract/0.0.15388539
Scroll down to the "Recent Contract Calls" section
If you see "REFRESH PAUSED" at the top right of this section, press the "play" button next to it to unpause (otherwise it does not load new transactions)
You should see a list of transactions, with most recent at the top
There should be a failed transaction, denoted by an exclamation mark in a red triangle, e.g.
https://hashscan.io/testnet/transaction/1689235951.444001003
Click on the row for that failed transaction to navigate to its "Transaction" page
Scroll down to the "Contract Result" section
You should see "Result" as
CONTRACT_REVERT_EXECUTED
You should also see "Error Message" as
pay at least minimum fee
Go back to the "Contract" page
Scroll down to the "Recent Contract Calls" section
There should be a successful transaction, denoted by the absence of an exclamation mark in a red triangle, e.g.
https://hashscan.io/testnet/transaction/1689235952.436013392
Scroll down to the "Contract Result" section
You should see "Result" as
SUCCESS
You should also see "Error Message" as
None
Scroll down to the "Logs" section
You should see a single log entry (address, data, index, and topics)
The "Address" field matches that of the smart contract
The "Index" field should be
0
since there was only a single event that was emittedThe "Topics" field corresponds to the hash of the signature of the event that was emitted, e.g.
Burnination(address,uint256)
The "Data" field corresponds to the values of the event parameters, e.g.
0x00000000000000000000000000000000000000000000000000000000000004a2000000000000000000000000000000000000000000000000000000000000007b
is:0x00000000000000000000000000000000000004a2
(your address) and0x007b
is the amount (123
when converted to decimal)
Last updated