Connecting to public test networks

After you have written your contracts, and tried them out locally and tested them thoroughly, it’s time to move to a persistent public testing environment, where you and your beta users can start interacting with your application.

We will use public testing networks (aka testnets) for this, which are networks that operate similar to the main Ethereum network, but where Ether has no value and is free to acquire - making them ideal for testing your contracts at no cost.

In this guide, we will use our beloved Box contract, and deploy it to a testnet, while learning:

Remember that deploying to a public test network is a necessary step when developing an Ethereum project. They provide a safe environment for testing that closely mimics the main network - you don’t want to take out your project for a test drive in a network where mistakes will cost you and your users money!

Instructions are available for both Truffle and Hardhat. Choose your preference using this toggle!

Available testnets

There are two test networks available for you to choose, each with their own characteristics:

Sepolia

A proof-of-stake network. This means validators explicitly stake capital in the form of ETH into a smart contract, which acts as collateral to be destroyed upon misbehavior. (id=11155111)

Goerli

Also a proof-of-stake network, compatible with both Geth and OpenEthereum clients, with 15-second block times. (id=5)

Each network is identified by a numeric ID. Local networks usually have a large random value, while id=1 is reserved for the main Ethereum network.

Connecting a project to a public network

To connect our project to a public testnet, we will need to:

Accessing a testnet node

While you can spin up your own Geth or OpenEthereum node connected to a testnet, the easiest way to access a testnet is via a public node service such as Alchemy or Infura. Alchemy and Infura provide access to public nodes for all testnets and the main network, via both free and paid plans.

We say a node is public when it can be accessed by the general public, and manages no accounts. This means that it can reply to queries and relay signed transactions, but cannot sign transactions on its own.

In this guide we will use Alchemy, though you can use Infura, or another public node provider of your choice.

Head over to Alchemy (includes referral code), sign up, and jot down your assigned API key - we will use it later to connect to the network.

Creating a new account

To send transactions in a testnet, you will need a new Ethereum account. There are many ways to do this: here we will use the mnemonics package, which will output a fresh mnemonic (a set of 12 words) we will use to derive our accounts:

$ npx mnemonics
drama film snack motion ...
Make sure to keep your mnemonic secure. Do not commit secrets to version control. Even if it is just for testing purposes, there are still malicious users out there who will wreak havoc on your testnet deployment for fun!

Configuring the network

Since we are using public nodes, we will need to sign all our transactions locally. We will configure the network with our mnemonic and an Alchemy endpoint.

This part assumes you have already set up a project. If you haven’t, head over to the guide on Setting up a Solidity project.

Let’s start by installing the @truffle/hdwallet-provider:

$ npm install --save-dev @truffle/hdwallet-provider

We need to update our configuration file with a new network connection to the testnet. Here we will use Goerli, but you can use whichever you want:

// truffle-config.js
+const { alchemyApiKey, mnemonic } = require('./secrets.json');
+const HDWalletProvider = require('@truffle/hdwallet-provider');

 module.exports = {
   ...
   networks: {
     development: {
      ...
     },
+    goerli: {
+      provider: () => new HDWalletProvider(
+        mnemonic, `https://eth-goerli.alchemyapi.io/v2/${alchemyApiKey}`,
+      ),
+      network_id: 5,
+      gasPrice: 10e9,
+      skipDryRun: true,
+    },
   },
   ...
 };
See the HDWalletProvider documentation for information on configuration options.
// hardhat.config.js
+ const { alchemyApiKey, mnemonic } = require('./secrets.json');
...
  module.exports = {
+    networks: {
+     goerli: {
+       url: `https://eth-goerli.alchemyapi.io/v2/${alchemyApiKey}`,
+       accounts: { mnemonic: mnemonic },
+     },
+   },
...
};
See the Hardhat networks configuration documentation for information on configuration options.

Note in the first line that we are loading the project id and mnemonic from a secrets.json file, which should look like the following, but using your own values. Make sure to .gitignore it to ensure you don’t commit secrets to version control!

{
  "mnemonic": "drama film snack motion ...",
  "alchemyApiKey": "JPV2..."
}
Instead of a secrets.json file, you can use whatever secret-management solution you like for your project. A popular and simple option is to use dotenv for injecting secrets as environment variables.

We can now test out that this configuration is working by listing the accounts we have available for the goerli network. Remember that yours will be different, as they depend on the mnemonic you used.

$ npx truffle console --network goerli
truffle(goerli)> accounts
[ '0xEce6999C6c5BDA71d673090144b6d3bCD21d13d4',
  '0xC1310ade58A75E6d4fCb8238f9559188Ea3808f9',
... ]
$ npx hardhat console --network goerli
Welcome to Node.js v12.22.1.
Type ".help" for more information.
> accounts = await ethers.provider.listAccounts()
[
  '0xEce6999C6c5BDA71d673090144b6d3bCD21d13d4',
  '0xC1310ade58A75E6d4fCb8238f9559188Ea3808f9',
...
]

We can also test the connection to the node, by querying our account balance.

> await web3.eth.getBalance(accounts[0])
'0'
> (await ethers.provider.getBalance(accounts[0])).toString()
'0'

Empty! This points to our next task: getting testnet funds so that we can send transactions.

Funding the testnet account

Most public testnets have a faucet: a site that will provide you with a small amount of test Ether for free. If you are on goerli, head to the Alchemy’s free Goerli faucet to get free testETH. Alternatively, you can also use MetaMask’s faucet to ask for funds directly to your MetaMask accounts. If you need test Ether on Sepolia, you can use Infura’s free Sepolia faucet to get free testETH.

Armed with a funded account, let’s deploy our contracts to the testnet!

Working on a testnet

With a project configured to work on a public testnet, we can now finally deploy our Box contract. The command here is exactly the same as if you were on your local development network, though it will take a few seconds to run as new blocks are mined.

$ npx truffle migrate --network goerli
...
2_deploy.js
===========

   Deploying 'Box'
   ---------------
   > transaction hash:    0x56237c80fc5b562736d27dc12560241706849cebe01c46b7c080dad596a65f28
   > Blocks: 0            Seconds: 6
   > contract address:    0xA4D767f2Fba05242502ECEcb2Ae97232F7611353
...
$ npx hardhat run --network goerli scripts/deploy.js
Deploying Box...
Box deployed to: 0xD7fBC6865542846e5d7236821B5e045288259cf0

That’s it! Your Box contract instance will be forever stored in the testnet, and publicly accessible to anyone.

You can see your contract on a block explorer such as Etherscan. Remember to access the explorer on the testnet where you deployed your contract, such as goerli.etherscan.io for goerli.

You can check out the contract we deployed in the example above, along with all transactions sent to it, here.
You can check out the contract we deployed in the example above, along with all transactions sent to it, here.

You can also interact with your instance as you regularly would, either using the console, or programmatically.

$ npx truffle console --network goerli
truffle(goerli)> box = await Box.deployed()
undefined
truffle(goerli)> await box.store(42)
{ tx:
   '0x7d1a556b34fcb26855c6646669fc926738b05589591de6c7bb1f8773546817e7',
...
truffle(goerli)> (await box.retrieve()).toString()
'42'
$ npx hardhat console --network goerli
Welcome to Node.js v12.22.1.
Type ".help" for more information.
> const Box = await ethers.getContractFactory('Box');
undefined
> const box = await Box.attach('0xD7fBC6865542846e5d7236821B5e045288259cf0');
undefined
> await box.store(42);
{
  hash: '0x330e331d30ee83f96552d82b7fdfa6156f9f97d549a612eeef7283d18b31d107',
...
> (await box.retrieve()).toString()
'42'

Keep in mind that every transaction will cost some gas, so you will eventually need to top up your account with more funds.

Next steps

After thoroughly testing your application on a public testnet, you are ready for the last step on the development journey: deploying your application in production.