Integrations

HardHat

You can also check out our complete article on the topic of integrating HardHat with Tenderly here.

How to setup HardHat

First things first, we need to add Hardhat into our project. You can follow along in an empty directory (we’ll be using yarn):
1
yarn add hardhat
Copied!
Next up, we’ll initiate our project by using the npx hardhat command:
1
888 888 888 888 888
2
888 888 888 888 888
3
888 888 888 888 888
4
8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
5
888 888 "88b 888P" d88" 888 888 "88b "88b 888
6
888 888 .d888888 888 888 888 888 888 .d888888 888
7
888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
8
888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
9
10
👷 Welcome to Hardhat v2.0.0-rc.1 👷‍
11
12
✔ What do you want to do? · Create a sample project
13
✔ Hardhat project root: · /src/super-awesome-project
14
✔ Do you want to add a .gitignore? (Y/n) · y
15
✔ Help us improve Hardhat with anonymous crash reports & basic usage data? (Y/n) · true
16
✔ Do you want to install the sample project's dependencies with yarn (@nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers)? (Y/n) · y
Copied!
And we have a sample project set up! You can run npx hardhat test to check if everything is working.

Integrating Tenderly with HardHat

Now that we have the Hardhat project all set up, it’s time to add Tenderly into the mix. We’ll be installing the hardhat-tenderly plugin and the Tenderly CLI, which we’ll be using later:
1
yarn add @tenderly/hardhat-tenderly
2
brew tap tenderly/tenderly
3
brew install tenderly
Copied!
As you can see, we used brew to install Tenderly. You can find the alternative installation steps here.
To let know Hardhat to load the Tenderly plugin, add the following line to your hardhat.config.js:
1
require("@tenderly/hardhat-tenderly")
Copied!
Next up, we can use the CLI to authenticate our development machine with our Tenderly dashboard (if you don’t already have an account, you can register here):
1
tenderly login
2
✔ Email
3
✔ Enter your email: [email protected]
4
✔ Password: ************
Copied!
To do the final configuration step, you’ll need to tell Hardhat which Tenderly project to connect with. You can do that by adding the following snippet of code into the hardhat.config.js module.exports:
1
{
2
tenderly: {
3
username: "MyAwesomeUsername",
4
project: "super-awesome-project"
5
}
6
}
Copied!
You can find the username and project by opening up https://dashboard.tenderly.co, and copying from the URL https://dashboard.tenderly.co/{USERNAME}/{PROJECT}. Alternatively, you can run tenderly whoami to get the needed information.
The Tenderly plugin is now good to go!

Deploying your contracts via ethers.js

When you initialized your Hardhat project, there were a couple of default files generated in your project. Put your attention towards the ./contracts/Greeter.sol and ./scripts/sample-script.js files.
  • Greeter.sol: Contains a straightforward Smart Contract we’ll be using for this walkthrough
  • sample-scripts.js: Contains a deployment script for the Greeter Smart Contract
To deploy the Greeter, you should use the npx hardhat run command, which takes the script as the argument:
1
npx hardhat run ./scripts/sample-script.js
2
All contracts have already been compiled, skipping compilation.
3
Deploying a Greeter with greeting: Hello, Hardhat!
4
Greeter deployed to: 0x7c2C195CD6D34B8F845992d380aADB2730bB9C6F
Copied!
This will successfully deploy the Greeter Smart Contract to the local in-memory Hardhat node. Keeping that in mind, let’s see how to set a more permanent solution so we can play around.
Open another terminal window and type in npx hardhat node. This will start a node against which we can run transactions. Now, edit your hardhat.config.js file and add the following to the module.exports:
1
{
2
networks: {
3
local: {
4
url: 'http://127.0.0.1:8545'
5
}
6
}
7
}
Copied!
To use the newly defined network, run the script with the following command:
1
npx hardhat run --network local scripts/sample-script.js
Copied!
You’ll see logs popping up on the terminal which is running the Hardhat node.

Debug and profile local transactions with tenderly export

Let’s make a small change to the sample script:
1
// We require the Hardhat Runtime Environment explicitly here. This is optional
2
// but useful for running the script in a standalone fashion through `node <script>`.
3
// When running the script with `hardhat run <script>` you'll find the Hardhat
4
// Runtime Environment's members available in the global scope.
5
const hre = require("@nomiclabs/hardhat");
6
7
async function main() {
8
// Hardhat always runs the compile task when running scripts through it.
9
// If this runs in a standalone fashion you may want to call compile manually
10
// to make sure everything is compiled
11
// await hre.run('compile');
12
13
// We get the contract to deploy
14
const Greeter = await hre.ethers.getContractFactory("Greeter");
15
const greeter = await Greeter.deploy("Hello, Hardhat!");
16
17
await greeter.deployed();
18
19
await hre.tenderly.persistArtifacts({
20
name: "Greeter",
21
address:greeter.address
22
});
23
24
console.log("Greeter deployed to:", greeter.address);
25
26
console.log("Changing greeting");
27
28
await greeter.setGreeting("Zdravo, Graditelju!");
29
30
console.log("Greeting changed!");
31
}
32
33
// We recommend this pattern to be able to use async/await everywhere
34
// and properly handle errors.
35
main()
36
.then(() => process.exit(0))
37
.catch(error => {
38
console.error(error);
39
process.exit(1);
40
});
Copied!
As you can see, we added lines 21 through 26, where we’re changing the default greeting. Let’s re-run the script: npx hardhat run --network local scripts/sample-script.js. You’ll notice the new transaction appears on the hardhat node terminal.
It’s time to export some transactions! Firstly, run the export init command to set it up:
1
tenderly export init
2
✔ Choose the name for the exported network: hardhat
3
✔ Super Awesome Project
4
✔ Enter rpc address (default: 127.0.0.1:8545):
5
✔ None
Copied!
Finally, run tenderly export with the transaction hash and see the magic happen:
1
tenderly export 0x0c8cab68f5d84dfdc02c903ef8c9f26c957a8e788e8b398e462f459ae1f6da47
2
Collecting network information...
3
4
Collecting transaction information...
5
6
Collecting contracts...
7
Successfully exported transaction with hash 0x0c8cab68f5d84dfdc02c903ef8c9f26c957a8e788e8b398e462f459ae1f6da47
8
You can view your transaction at <https://dashboard.tenderly.co/MyAwesomeUsername/super-awesome-project/local-transactions/69d3194e-6cca-47d4-b875-41d11fea5701>
Copied!
By clicking on the provided link, you’ll have access to all of the Tenderly tools you know and love for that local transaction!

Push and verify contracts with hardhat-tenderly tasks

Let’s say that we’re happy with our Greeter and want to test it out on a testnet. I’ll be using the Kovan testnet. To set it up, we’ll once again edit our hardhat.config.js file and add a new entry to the networks map:
1
{
2
networks: {
3
local: {
4
url: 'http://127.0.0.1:8545'
5
},
6
kovan: {
7
url: '<link-to-node>',
8
mnemonic: '<mnemonic-phrase>'
9
}
10
}
11
}
Copied!
To deploy the Smart Contract, we will run the script again, but with a minor change:
1
npx hardhat run --network kovan scripts/sample-script.js
Copied!
As you can see, we need to set kovan as the target network for the script. Once the deployment is done, we will copy the outputted address and use it with the verify command:
1
npx hardhat --network kovan tenderly:verify Greeter=0x6f49F890B6B10AA49AF925625662439db6E8aFb8
Copied!
If we navigate to the Smart Contracts page, we’ll see we’ve successfully done the verification! Note that you can provide multiple Contract=Address pairs separated by a space character.
The push and verify commands are similar at first glance, but they’re quite different:
  • tenderly:verify: Publicly verifies the contracts for all Tenderly users. This means that everyone has access to the source code of this Smart Contract.
  • tenderly:push: Privately adds the contracts to the project defined in hardhat.config.js. This means no one except the people with access to the project can see the source code of this Smart Contract

Push and verify contracts directly through code

You might be thinking to yourself: “Verifying and pushing Smart Contracts after each deployment is quite repetitive and prone to errors!”. You aren’t far from the truth. That’s why you can verify and push contracts directly inside of your Hardhat scripts!
1
// We require the Hardhat Runtime Environment explicitly here. This is optional
2
// but useful for running the script in a standalone fashion through `node <script>`.
3
// When running the script with `hardhat run <script>` you'll find the Hardhat
4
// Runtime Environment's members available in the global scope.
5
const hre = require("hardhat");
6
7
async function main() {
8
// Hardhat always runs the compile task when running scripts through it.
9
// If this runs in a standalone fashion you may want to call compile manually
10
// to make sure everything is compiled
11
// await hre.run('compile');
12
13
// We get the contract to deploy
14
const Greeter = await hre.ethers.getContractFactory("Greeter");
15
const greeter = await Greeter.deploy("Hello, Hardhat!");
16
17
await greeter.deployed();
18
19
console.log("Greeter deployed to:", greeter.address);
20
21
await hre.tenderly.persistArtifacts({
22
name: "Greeter",
23
address: greeter.address
24
});
25
26
await hre.tenderly.verify({
27
name: "Greeter",
28
address: greeter.address,
29
})
30
31
console.log("Changing greeting");
32
33
await greeter.setGreeting("Zdravo, Hardhetu!");
34
35
console.log("Greeting changed!");
36
}
37
38
// We recommend this pattern to be able to use async/await everywhere
39
// and properly handle errors.
40
main()
41
.then(() => process.exit(0))
42
.catch(error => {
43
console.error(error);
44
process.exit(1);
45
});
Copied!
If you look at line 26, you’ll see that with a simple tenderly.verify() or tenderly.push() call we can verify/push (multiple) Smart Contracts!

How to monitor and debug your BCS contracts

To start things out, we need a project with a contract we want to deploy. I will be using the default Hardhat example for this. To do this, we will run the following:
1
yarn add hardhat
2
3
...
4
5
npx hardhat
Copied!
Next up we'll install the @tenderly/hardhat-tenderly plugin in order to verify the contract we deploy:
1
yarn add @tenderly/hardhat-tenderly
Copied!
After this, we must edit our hardhat.config.js to include information about the BSC testnet:
1
require("@nomiclabs/hardhat-waffle");
2
require("@tenderly/hardhat-tenderly")
3
4
module.exports = {
5
solidity: "0.7.3",
6
networks: {
7
mumbai: {
8
url: '<https://data-seed-prebsc-1-s1.binance.org:8545>',
9
accounts: {
10
mnemonic: {{env.mnemonic}},
11
},
12
},
13
},
14
};
Copied!
Finally all we need to is modify the deployment script in order to add the verify step and an example transaction:
1
const hre = require("hardhat");
2
3
async function main() {
4
const Greeter = await hre.ethers.getContractFactory("Greeter");
5
const greeter = await Greeter.deploy("Hello, Hardhat!");
6
7
await greeter.deployed();
8
9
await greeter.setGreeting("Hello, BSC!")
10
11
await hre.tenderly.verify({
12
name: "Greeter",
13
address: greeter.address
14
})
15
16
console.log("Greeter deployed to:", greeter.address);
17
}
18
19
main()
20
.then(() => process.exit(0))
21
.catch(error => {
22
console.error(error);
23
process.exit(1);
24
});
Copied!
This will output the URL to the verified contract.

Truffle

By the end of this guide, you will be all set up with a development environment that uses Truffle and Tenderly to speed up your Smart Contract development process.
Note: The proxy command is now deprecated in favor of the more powerful and versatile export command. You can read more about the export command here.
We are going to make a simple calculator and use it to debug any problems we might encounter. If you already have Truffle installed and configured, you can skip to the Contract section below.
To install the Truffle framework just run the following command:
1
$ npm install -g truffle
Copied!
Now let’s create the directory for our calculator project and initialize truffle:
1
$ mkdir calculator
2
$ cd calculator
3
$ truffle init
4
Downloading...
5
Unpacking...
6
Setting up...
7
Unbox successful. Sweet!
8
9
Commands:
10
11
Compile: truffle compile
12
Migrate: truffle migrate
13
Test contracts: truffle test
Copied!

The Contract

Our calculator supports just addition, subtraction, multiplication and division.
1
pragma solidity ^0.4.24;
2
3
contract Calculator {
4
uint c;
5
6
function add(uint a, uint b) public {
7
c = a + b;
8
}
9
10
function sub(uint a, uint b) public {
11
c = a - b;
12
}
13
14
function mul(uint a, uint b) public {
15
c = a * b;
16
}
17
18
function div(uint a, uint b) public {
19
require(b > 0, "The second parameter should be larger than 0");
20
21
c = a / b;
22
}
23
24
function getResult() public view returns (uint x) {
25
return c;
26
}
27
}
Copied!
We can check if our code is correct by running the compile Truffle command:
1
$ truffle compile
2
Compiling ./contracts/Calculator.sol...
3
Compiling ./contracts/Migrations.sol...
4
Writing artifacts to ./build/contracts
Copied!

Deploying the Calculator Contract

We have no use of our contract if it just lays there on our filesystem! Let’s deploy it to a local Ganache node and test it out! To install Ganache run the following command:
1
$ npm install -g ganache-cli
Copied!
Now that we have Ganache installed, we can set up our network configuration in truffle.js:
1
module.exports = {
2
networks: {
3
ganache: {
4
host: "127.0.0.1",
5
port: 8545,
6
network_id: "*",
7
},
8
tenderly: {
9
host: "127.0.0.1",
10
port: 9545,
11
network_id: "*",
12
gasPrice: 0
13
}
14
},
15
};
Copied!
Before we deploy our Smart Contract we have to start Ganache by running:
1
$ ganache-cli
2
3
Ganache CLI v6.2.3 (ganache-core: 2.3.1)
4
5
Available Accounts
6
==================
7
(0) 0xa439c978fab0e2b13a874dc6c48dbc79c6f6655e (~100 ETH)
8
9
...
Copied!
Then we make a new migration under ./migrations/2_deploy_calculator.js:
1
var Calculator = artifacts.require('Calculator');
2
3
module.exports = function (deployer) {
4
deployer.deploy(Calculator);
5
};
Copied!
And finally to deploy the Calculator Smart Contract we can run:
1
$ truffle migrate --network ganache
2
Using network 'ganache'.
3
4
Running migration: 1_initial_migration.js
5
Deploying Migrations...
6
... 0x92039ee2c2f057dfb2b180bb532fb767626d4e1092a98125209f8f51c300818b
7
Migrations: 0x492649777fbe2e25f0470834f7ab50291c329391
8
Saving successful migration to network...
9
... 0xd57f3537cc224426ee86c0d0ada172c0a5c7a4f40921b1bb8d5997d5e8ffde01
10
Saving artifacts...
11
Running migration: 2_deploy_calculator.js
12
Deploying Calculator...
Copied!

Interacting with the Smart Contract

Later on, we are going to write some tests, but for now, let’s interact with our contract directly:
1
$ truffle console --network ganache
2
truffle(ganache)> var calculatorInstance;
3
truffle(ganache)> Calculator.deployed().then(instance => calculatorInstance = instance)
4
truffle(ganache)> calculatorInstance.mul(10,5)
5
truffle(ganache)> calculatorInstance.getResult()
6
BigNumber { s: 1, e: 1, c: [ 50 ] }
Copied!
As we can see we got the correct result! It’s an expensive calculator, but damn it it’s a distributed one!
However, what happens if we call the contract with invalid arguments? What if we maybe divide by zero?
1
$ truffle console --network ganache
2
truffle(ganache)> var calculatorInstance;
3
truffle(ganache)> Calculator.deployed().then(instance => calculatorInstance = instance)
4
truffle(ganache)> calculatorInstance.div(10,0)
5
Error: VM Exception while processing transaction: invalid opcode
6
at XMLHttpRequest._onHttpResponseEnd (/Users/user/.config/yarn/global/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:509:1)
7
at XMLHttpRequest._setReadyState (/Users/user/.config/yarn/global/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:354:1)
8
...
Copied!
If you look at the error we received you will see that there isn’t much helpful information. In the next section, we will write tests for the div function and use Tenderly to see how it helps with cases like this one.

Installing Tenderly

The Tenderly CLI is a Go program, so we compile just a single binary that you can download and use. The basic installation process would be to download the latest binary from the releases page and move it somewhere in your $PATH so you can use it globally.
For everyone’s convenience, we have simplified the process explained above so you can install the Tenderly CLI just by running the following commands.

Installing on macOS

1
$ brew tap tenderly/tenderly
2
$ brew install tenderly
Copied!
Alternatively, via cURL (the command downloads the latest release and moves it to /usr/local/bin):
1
$ curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-macos.sh | sh
Copied!

Installing on Linux

The command downloads the latest release and moves it to/usr/local/bin:
1
$ curl https://raw.githubusercontent.com/Tenderly/tenderly-cli/master/scripts/install-linux.sh | sh
Copied!

Installing on Windows

Go to the tenderly-cli release page, download the latest version and put it somewhere in your $PATH.

Using Tenderly for debugging

Now that we have the CLI all set up we can easily find why the smart contract is failing. First, we start the CLI with the proxy command which proxies requests to an Ethereum node and turns unusable errors which we saw into stack traces that we can use for easier debugging.
For the sake of speed, I’m going to use Ganache locally, but you can use Kovan or any other testnet if you want. You can see which options are supported by the CLI by running tenderly proxy --help:
1
$ tenderly proxy --help
2
Creates a server that proxies rpc requests to Ethereum node and builds a stacktrace in case error occurs during the execution time
3
4
Usage:
5
tenderly proxy [flags]
6
7
Flags:
8
-h, --help help for proxy
9
--path string Path to the project build folder. (default ".")
10
--proxy-host string Call host. (default "127.0.0.1")
11
--proxy-port string Call port. (default "9545")
12
--target-host string Blockchain rpc host. (default "127.0.0.1")
13
--target-port string Blockchain rpc port. (default "8545")
14
--target-schema string Blockchain rpc schema. (default "http")
Copied!
Because we're using Ganache with the default options, we won’t pass any arguments, and will just run the proxy command from the root of the project:
1
$ tenderly proxy
2
server will run on 127.0.0.1:9545
3
redirecting to 127.0.0.1:8545
Copied!
Now that we have everything set up let’s go back to the console and try out our contract again, but this time we pass--network tenderly when starting the console:
1
$ truffle console --network tenderly
2
truffle(tenderly)> var calculatorInstance;
3
truffle(tenderly)> Calculator.deployed().then((instance) => calculatorInstance = instance)
4
truffle(tenderly)> calculatorInstance.div(10, 0)
5
Error: 0x0 Error: INVALID OPCODE, execution stopped
6
at a / b
7
in Calculator:19
8
9
at XMLHttpRequest._onHttpResponseEnd (/Users/user/.config/yarn/global/node_modules/truffle/build/webpack:/~/xhr2/lib/xhr2.js:509:1)
10
...
Copied!
This the the error we get when we don’t proxy our requests through the Tenderly CLI
And this is the human readable stack trace we get when we do proxy our requests thru the Tenderly CLI

Writing the tests and fixing the contract

Because we now know precisely on which line the problem is, we can write a test case and then fix the issue. So let’s make the ./test/calculator.js file and write out the test:
1
var Calculator = artifacts.require("./Calculator.sol");
2
3
contract('Calculator', function (accounts) {
4
it("shouldn't allow division by zero", async function () {
5
const fail = await Calculator.new();
6
await fail.div(5, 0);
7
});
8
});
Copied!
We can run the truffle test --network tenderly command to see if we’ve set up everything properly:
You can see that we get a stack trace that gives us enough information to fix the issue.
Let’s add the require statement as the first line of the div function to fix our division by zero edge case:
1
require(b > 0, "The second parameter should be larger than 0");
Copied!
And let’s update our test with the assertions that assure we are seeing expected behaviour:
1
var Calculator = artifacts.require("./Calculator.sol");
2
3
contract('Calculator', function (accounts) {
4
it("shouldn't allow division by zero", async function () {
5
let error;
6
const fail = await Calculator.new();
7
try {
8
await fail.div(5, 0);
9
} catch (e) {
10
error = e;
11
}
12
13
assert.isDefined(error, "No exception thrown during divison");
14
assert.isTrue(error.message.search("revert") >= 0, "Expected transaction revert");
15
});
16
});
Copied!
If we run our tests we should see everything is working as it should:
1
truffle test --network ganache
2
Using network 'ganache'.
3
4
Compiling ./contracts/Calculator.sol...
5
6
7
Contract: Calculator
8
✓ shouldn't allow division by zero (91ms)
9
10
11
1 passing (131ms)
Copied!

Remix

As Remix has a concept for plugins, we will go through everything that is needed to use it with Tenderly.
Using the file structure plugin we navigate to the Storage.sol contract which we will be deploying.
We will first compile the contract:
We also have the deployment plugin which we will use to deploy this contract to Kovan:
Next, we can install the Tenderly plugin found here:
In order to connect with Tenderly you will need an access token which you can get by going to your Tenderly dashboard and in the top right clicking on Settings, and then going to the Authorizations tab where you will generate a new access token for Remix:
Now you just need to paste the access token into Remix:
You will now be able to choose whether to add the contract to an existing project by choosing from a list or create a new one. Now, in order to verify and add the contract into your project, you will need to click on the verify tab in Remix, choose the network and contract, as well as copy the address of the deployed contract. In this case we will privately verify a contract on Tenderly:
You are also able to import contracts into Remix from your Tenderly project:
You can also add new contracts to your project and see them in Remix at any point. Let's say we want to add this Dai contract into our project in Tenderly. We will copy it's address, go back to the project we want to add it to and go through the flow:
Then if we go back in to Remix and refresh the contract list we will see the newly added contract. We can import it and now use it in Remix:
You can also use Remix with Visual Studio by using their VSCode extension, which supports the Tenderly plugin as well. You can find it here.
Last modified 12d ago