Deploying and Interacting with Smart Contracts

Unlike most software, smart contracts don’t run on your computer or somebody’s server: they live on the Ethereum network itself. This means that interacting with them is a bit different from more traditional applications.

This guide will cover all you need to know to get you started using your contracts, including:

Setting up a Local Blockchain

Before we begin, we first need an environment where to deploy our contracts. The Ethereum blockchain (often called "mainnet", for "main network") requires spending real money to use it, in the form of Ether (its native currency). This makes it a poor choice when trying out new ideas or tools.

To solve this, a number of "testnets" (for "test networks") exist: these include the Ropsten, Rinkeby, and Kovan blockchains. They work very similarly to mainnet, with one difference: you can get Ether for these networks for free, so that using them doesn’t cost you a single cent. However, you will still need to deal with private key management, blocktimes in the range of 5 to 20 seconds, and actually getting this free Ether.

During development, it is a better idea to instead use a local blockchain. It runs on your machine, requires no Internet access, provides you with all the Ether that you need, and mines blocks instantly. These reasons also make local blockchains a great fit for automated tests.

If you want to learn how to deploy and use contracts on a public blockchain, like the Ethereum testnets, head to our Connecting to Public Test Networks guide.

The most popular local blockchain is Ganache. To install it on your project, run:

$ npm install --save-dev ganache-cli

Upon startup, Ganache will create a random set of unlocked accounts and give them Ether. In order to get the same addresses that will be used in this guide, you can start Ganache in deterministic mode:

$ npx ganache-cli --deterministic

Ganache will print out a list of available accounts and their private keys, along with some blockchain configuration values. Most importantly, it will display its address, which we’ll use to connect to it. By default, this will be 127.0.0.1:8545.

Keep in mind that every time you run Ganache, it will create a brand new local blockchain - the state of previous runs is not preserved. This is fine for short-lived experiments, but it means that you will need to have a window open running Ganache for the duration of these guides. Alternatively, you can run Ganache with the --db option, providing a directory to store its data inbetween runs.

As an alternative to Ganache, you can also run an actual Ethereum node in development mode. These are a bit more complex to set up, and not as flexible for testing and development, but are more representative of the real network than Ganache.

Getting Started with the OpenZeppelin CLI

The low-level details of deploying and interacting with contracts are quite involved, but you won’t need to worry about them: the OpenZeppelin command-line interface (CLI) will take care of these processes for you.

You may have used the CLI already to compile your contracts - if not, install it now:

$ npm install --save-dev @openzeppelin/cli

In order to manage your deployed contracts, you need to create a new CLI project. Do so now, and provide it with a name and version number for your project when prompted:

$ npx openzeppelin init
You can call the OpenZeppelin CLI with both openzeppelin and oz. For brevity, from here on we’ll use the latter.

Two things will happen during initialization. First, an .openzeppelin directory will be created, holding project-specific information. This directory will be managed by the CLI: you won’t need to manually edit anything. You should however commit some of these files to Git.

Second, the CLI will store network configuration in a file called networks.js. For convenience, it is already populated with an entry called development, with configuration matching Ganache's default.

Let’s use our first CLI commands to get acquainted with it. We’ll begin by querying Ganache for all unlocked accounts:

$ npx oz accounts

Unlike init and compile, the accounts command requires a network to run on: the CLI will pick up the entries on the networks.js file and prompt you to select one from that list.

Once you choose development, the entry for Ganache, it will print out all available accounts there:

$ npx oz accounts
? Pick a network development
Accounts for dev-1576250059363:
Default: 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
All:
- 0: 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
- 1: 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0
...

We can also query the balance of these accounts by using the balance command:

$ npx oz balance
? Enter an address to query its balance 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
? Pick a network development
Balance: 100 ETH

This shows why using a local blockchain is so convenient: Ganache has prefunded all of our accounts with 100 Ether, which means we’ll be able to send plenty of transactions!

Deploying a Smart Contract

With the CLI setup complete, we’re now ready to deploy a contract. We’ll be deploying Box, from the Writing Smart Contracts guide. Make sure you have a copy in contracts/Box.sol:

// contracts/Box.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;


contract Box {
    uint256 private value;

    // Emitted when the stored value changes
    event ValueChanged(uint256 newValue);

    // Stores a new value in the contract
    function store(uint256 newValue) public {
        value = newValue;
        emit ValueChanged(newValue);
    }

    // Reads the last stored value
    function retrieve() public view returns (uint256) {
        return value;
    }
}

The OpenZeppelin CLI will guide you through the deployment process, asking for information as needed. Using the deploy command, deploy the Box contract to the development network (Ganache), choosing regular when prompted with the kind of deployment:

$ npx oz deploy
✓ Compiled contracts with solc 0.6.7 (commit.b8d736ae)
? Choose the kind of deployment regular
? Pick a network development
? Pick a contract to deploy Box
✓ Deployed instance of Box
0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab

All done! On a real network this process would’ve taken a couple seconds, but it is instant on local blockchains.

If you got a connection error, make sure you are running Ganache in another terminal.

The CLI will keep track of your deployed contracts, but it also displays their addresses after deployment (in our example, 0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab). These values will be useful when interacting with them programatically.

Remember that local blockchains do not persist their state throughout multiple runs! If you close your Ganache process, you’ll have to re-deploy your contracts.

Interacting From the Command Line

With our Box contract deployed, we can start using it right away from the CLI.

Sending Transactions

Box 's first function, store, receives an integer value and stores it in the contract storage. Because this function modifies the blockchain state, we need to send a transaction to the contract to execute it.

This can be achieved with the CLI’s send-tx command. Pick your Box contract when prompted by the CLI, select the store function, and choose a numeric value to send to it:

$ npx oz send-tx
? Pick a network development
? Pick an instance Box at 0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab
? Select which function store(newValue: uint256)
? newValue: uint256: 5
✓ Transaction successful. Transaction hash: 0xd40664c0a80215e964975ab3cea7f27a453c802f01c15cd754aca2c7bd6bd6c1
Events emitted:
 - ValueChanged(5)

Note how send-tx also detected that Box emitted a ValueChanged event with our newly stored value.

Querying State

Box 's other function is called retrieve, and it returns the integer value stored in the contract. This is a query of blockchain state, so we don’t need to send a transaction: a static call will suffice.

You may have noticed send-tx didn’t include retrieve in the list of functions to run: this is because queries are run using the call command instead:

$ npx oz call
? Pick a network development
? Pick an instance Box at 0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab
? Select which function retrieve()
✓ Method 'retrieve()' returned: 5
5

Because call doesn’t send a transaction, there is no transaction hash to report. This also means that using call doesn’t cost any Ether, and can be used for free on any network.

To learn more about send-tx and call, check out the CLI API reference.

Interacting Programmatically

The command-line is useful for protyping and running one-off commands. However, eventually you will want to interact with your contracts from your own application, which means calling or sending transaction in code.

In this section, we’ll see how to use web3.js to interact with our contracts from JavaScript, and the OpenZeppelin Contract Loader to load them. Let’s begin by installing them:

$ npm install web3 @openzeppelin/contract-loader
Keep in mind that there are many other JavaScript libraries available, and you can use whichever you like the most. Once a contract is deployed, you can interact with it through any library!

Connecting to the Network

Let’s start coding in a new src/index.js file, where we’ll be writing our JavaScript script, beginning with some boilerplate for loading the libraries and writing async code.

// src/index.js
const Web3 = require('web3');
const { setupLoader } = require('@openzeppelin/contract-loader');

async function main() {
  // Our code will go here
}

main();

We’ll first initialize a new web3 instance. This is an object that holds a connection to a blockchain node, and allows us to send transactions and queries. We’ll create one connecting to the local development network we started earlier, which is running on localhost port 8545.

We can test if the connection works by asking something to the local node, such as the list of enabled accounts:

// Set up web3 object, connected to the local development network
const web3 = new Web3('http://localhost:8545');

// Retrieve accounts from the local node
const accounts = await web3.eth.getAccounts();
console.log(accounts);
We won’t be repeating the boilerplate code on every snippet, but make sure to always code inside the main function we defined above!

Run the code above using node, and check that you are getting a list of available accounts in response.

$ node src/index.js
[ '0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1',
  '0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0',
  ... ]

These accounts should match the ones you got when you ran oz accounts earlier. Now that we have our first code snippet for getting data out of a blockchain, let’s start working with our contract.

Getting a Contract Instance

In order to interact with the Box contract we deployed using the CLI, we’ll create a new web3 contract instance using the OpenZeppelin Contract Loader.

A web3 contract instance is a JavaScript object that represents our contract on the blockchain, which we can use to interact with our contract. To create one we need to provide the Contract Loader with the contract name and the address where it was deployed, which the CLI returned when we ran oz create.

// Set up web3 object, connected to the local development network, and a contract loader
const web3 = new Web3('http://localhost:8545');
const loader = setupLoader({ provider: web3 }).web3;

// Set up a web3 contract, representing our deployed Box instance, using the contract loader
const address = '0xe78A0F7E598Cc8b0Bb87894B0F60dD2a88d6a8Ab';
const box = loader.fromArtifact('Box', address);
Make sure to replace the address with the one you got when deploying the contract, which may be different to the one shown here.

We can now use this JavaScript object to interact with our contract.

Calling the Contract

Let’s start by displaying the current value of the Box contract. We’ll need to call into the retrieve() public method of the contract, and await the response:

// Call the retrieve() function of the deployed Box contract
const value = await box.methods.retrieve().call();
console.log("Box value is", value);

This snippet is equivalent to the oz call we ran earlier from the CLI. Now, make sure everything is running smoothly by running the script again and checking the printed value:

$ node src/index.js
Box value is 5

If you restarted ganache at any point, this script may fail. Restarting ganache clears all local blockchain state, so the Box contract instance won’t be at the expected address.

If this happens, simply start ganache and redeploy the Box contract.

Sending a Transaction

We’ll now send a transaction to store a new value in our Box. Remember that sending a transaction is not as straightforward as making a call: we need to specify who the sender will be, the gas limit, and the gas price we are going to use. To keep this example simple, we’ll use a hardcoded value for both gas and gas price, and send the transaction from the first available account on the node.

In a real-world application, you may want to estimate the gas of your transactions, and check a gas price oracle to know the optimal values to use on every transaction.

Let’s store a value of 20 in our Box, and then use the code we had written before to display the updated value:

// Retrieve accounts from the local node, we'll use the first one to send the transaction
const accounts = await web3.eth.getAccounts();

// Send a transaction to store() a new value in the Box
await box.methods.store(20)
  .send({ from: accounts[0], gas: 50000, gasPrice: 1e6 });

// Call the retrieve() function of the deployed Box contract
const value = await box.methods.retrieve().call();
console.log("Box value is", value);

We can now run the snippet, and check that the box’s value is updated!

$ node src/index.js
Box value is 20

Next Steps

Now that you know how to set up a local blockchain, deploy contracts and interact with them both manually and programmatically, you will need to learn about testing environments, public test networks and going to production: