[UPDATE] This tutorial uses OpenZeppelin Network.js which is deprecated. We are no longer actively developing new features nor addressing issues.
Introduction
The Solidity Hot Loader brings the web developer experience to Solidity to help you develop your smart contracts and dapps faster.
In this tutorial, we will use Solidity Hot Loader, to build a simple dapp.
We will create a Counter dapp, which displays the value of a Counter that can be increased. We will update the code in the smart contract to show the power of the Solidity Hot Loader.
The Solidity Hot Loader watches your smart contract .sol
files you’re using. Once there’s a change, it compiles them, updates the local blockchain using the OpenZeppelin SDK, and refreshes your app in the browser while preserving your blockchain state. Compare this against the existing Solidity development cycle.
The Solidity Hot Loader development cycle:
- Make a change to your smart contract
.sol
file and save it - Wait a bit
- Done
The existing (slow) Solidity development cycle:
- Make a change to your smart contract
.sol
file and save it - Compile your contract
- Deploy your contract
- Restore the state
- Restart dapp to get new contract
Video
If you enjoy video , watch @ylv-io’s Devcon presentation: Devcon Delightful Developer Experience With Solidity Hot Loader .
Setting up the environment
We will use the create-react-app
package to bootstrap a React application.
First we need a local blockchain to develop on.
We use ganache-cli
, so go ahead and install (if you haven’t already) and then run a local blockchain using ganache-cli
$ npm install -g ganache-cli
$ ganache-cli --deterministic
Next we need a project directory to develop our project in. In a separate terminal create the project directory and create package.json
with npm init
.
mkdir myproject && cd myproject
npm init -y
We use the OpenZeppelin CLI for compiling and deploying our contract, so go ahead and install OpenZeppelin CLI
npm install @openzeppelin/cli
Next, initialize the project with OpenZeppelin. This will create directories and config for our project.
$ npx openzeppelin init
? Welcome to the OpenZeppelin SDK! Choose a name for your project myproject
? Initial project version 1.0.0
Project initialized. Write a new contract in the contracts folder and run 'openzeppelin create' to deploy it.
Counter contract
Our Counter smart contract consists of an integer value
and a function to increase it by one.
In the contracts
directory save Counter.sol
with the following contents:
pragma solidity ^0.6.0;
contract Counter {
uint256 public value;
function increase() public {
value = value + 1;
}
}
Next we deploy the Counter contract to our local blockchain.
Run npx oz deploy
and use the interactive commands to deploy the Counter contract to the development network.
Choose kind: upgradeable.
There is no function that we need to call to initialize
.
$ npx oz deploy
✓ Compiled contracts with solc 0.6.12 (commit.27d51765)
? Choose the kind of deployment upgradeable
? Pick a network development
? Pick a contract to deploy Counter
✓ Added contract Counter
✓ Contract Counter deployed
All implementations have been deployed
? Call a function to initialize the instance after creating it? No
✓ Setting everything up to create contract instances
✓ Instance created at 0xCfEB869F69431e42cdB54A4F4f105C19C080A601
To upgrade this instance run 'oz upgrade'
0xCfEB869F69431e42cdB54A4F4f105C19C080A601
Creating the dapp
We will create our dapp using the create-react-app
package, which bootstraps a simple client-side application using React.
npx create-react-app client
Setup Solidity Hot Loader
Now that the directories for our dapp have been created, we can install Solidity Hot Loader.
In the client
directory install Solidity Hot Loader
npm install @openzeppelin/solidity-loader
The Solidity Hot Loader uses json-loader
so we should go ahead and install that too.
npm install json-loader
Next up, we configure Solidity Hot Loader and set it to enabled.
To stop the Solidity Hot Loader automagically updating our contracts, set disabled to true.
In the client
directory create a config
directory. In the config
directory create a webpack.js
file with the following code, notice that we are starting with the Solidity Hot Loader enabled (disabled: false
):
// client/config/webpack.js
const solidityLoaderOptions = {
network: 'development',
// you can stop loader from automatic compile/push/upgrade
// action by setting disabled flag to true, but it will still
// serve .json files from file system
disabled: false,
};
module.exports = {
solidityLoader: {
test: /\.sol$/,
use: [
{ loader: 'json-loader' },
{
loader: '@openzeppelin/solidity-loader',
options: solidityLoaderOptions,
},
],
},
solidityLoaderOptions,
};
We now add in the Solidity Hot Loader to webpack.
In the client
directory create a config-overrides.js
file with the following code:
// client/config-overrides.js
const { solidityLoader } = require('./config/webpack');
module.exports = function override(config, env) {
//do stuff with the webpack config...
// allow importing from outside of app/src folder, ModuleScopePlugin prevents this.
const scope = config.resolve.plugins.findIndex(o => o.constructor.name === 'ModuleScopePlugin');
if (scope > -1) {
config.resolve.plugins.splice(scope, 1);
}
// add solidity loader support
// have to insert before last loader, because CRA user 'file-loader' as default one
config.module.rules.splice(config.module.rules - 2, 0, solidityLoader);
return config;
};
We use react-app-rewired
to override create-react-app
webpack configs to add in the Solidity Hot Loader without ejecting.
In the client
directory, install react-app-rewired
$ npm i react-app-rewired
To use react-app-rewired
, we now need to change the package.json
scripts.
In package.json
in the client
directory, update the scripts to use react-app-rewired
so that they looks as follows:
"scripts": {
"start": "react-app-rewired start",
"build": "react-app-rewired build",
"test": "react-app-rewired test",
"eject": "react-scripts eject"
},
Add the dapp code
We will use OpenZeppelin Network.js to access the Web3 provider. OpenZeppelin Network.js is an easy to use and reliable library that provides one line access to the web3 API. It can be used in both React and in (vanilla) JavaScript. It also supports the Gas Station Network.
First we change to the client
directory
cd client
Then, install OpenZeppelin Network.js
This project is deprecated. We are no longer actively developing new features nor addressing issues.
npm install @openzeppelin/network
Our dapp will display a Counter that we can increase the value of, consisting of displaying the value and a button to increase the counter.
In the code below, first we import useWeb3Injected
from the React implementation of OpenZeppelin Network.js (@openzeppelin/network/react
). We then get a web3Context
using useWeb3Injected
.
The dapp loads the Counter json artifact.
The display of the Counter is handled in a component.
To start, on the client/src/App.js
file, replace the placeholder code in App.js
in our react project with the following code:
// client/src/App.js
import React, { useState } from 'react';
import './App.css';
import { useWeb3Network } from '@openzeppelin/network/react';
import Counter from './components/Counter.js';
function App() {
const web3Context = useWeb3Network('http://127.0.0.1:8545');
// load Counter json artifact
let counterJSON = undefined;
try {
// see https://github.com/OpenZeppelin/solidity-loader
counterJSON = require('../../contracts/Counter.sol');
} catch (e) {
console.log(e);
}
// load Counter instance
const [counterInstance, setCounterInstance] = useState(undefined);
let deployedNetwork = undefined;
if (!counterInstance && web3Context && counterJSON && counterJSON.networks && web3Context.networkId) {
deployedNetwork = counterJSON.networks[web3Context.networkId.toString()];
if (deployedNetwork) {
setCounterInstance(new web3Context.lib.eth.Contract(counterJSON.abi, deployedNetwork.address));
}
}
return (
<div className="App">
<div>
<h1>OpenZeppelin Solidity Hot Loader</h1>
<div>
<Counter {...web3Context} JSON={counterJSON} instance={counterInstance} deployedNetwork={deployedNetwork} />
</div>
</div>
</div>
);
}
export default App;
Add a Counter component to the dapp
The Counter component displays the current value of the counter and has a button to increase the value of the counter.
In the client/src
directory create a components
directory. In the components
directory create a Counter.js
file with the following code:
// client/src/components/Counter.js
import React, { useState, useEffect, useCallback } from 'react';
export default function Counter(props) {
const { instance, accounts, lib } = props;
const { _address } = instance || {};
const [count, setCount] = useState(0);
const getCount = useCallback(async () => {
if (instance) {
// Get the value from the contract to prove it worked.
const response = await instance.methods.value().call();
// Update state with the result.
setCount(response);
}
}, [instance]);
useEffect(() => {
getCount();
}, [getCount, instance]);
const [sending, setSending] = useState(false);
const increase = async number => {
try {
if (!sending) {
setSending(true);
await instance.methods.increase().send({ from: accounts[0] });
getCount();
setSending(false);
}
} catch (e) {
setSending(false);
console.log(e);
}
};
return (
<div>
<h3>Counter Instance</h3>
{lib && instance && (
<React.Fragment>
<div>
<div>Instance address: {_address}</div>
</div>
<div>
<div>Counter Value: {count}</div>
</div>
<React.Fragment>
<div>
<h4>Counter Actions</h4>
</div>
<div>
<button onClick={() => increase()} size="small">
{sending ? <span>Sending ...</span> : <span> Increase Counter</span>}
</button>
</div>
</React.Fragment>
</React.Fragment>
)}
</div>
);
}
Run the dapp
We can now fire up our application running npm start
from within the client
directory.
$ npm start
Our dapp that we created will display in the browser https://localhost:3000
With our dapp loaded in the browser, we can increase the value of the counter.
Solidity Hot Loader in action
We can now make changes to our contract and this will be reflected in the dapp using the Solidity Hot Loader.
In Counter.sol
change the value that the counter is increased by from 1 to 3.
function increase() public {
value = value + 3;
}
Save the change in Counter.sol
and Solidity Hot Loader will compile the contract, upgrade it and refresh the dapp.
Press the Increase Counter
button and once the transaction in confirmed the counter will have increased by 3.
Let’s take a moment and go over the Solidity Hot Loader development cycle again:
- Make a change to your smart contract
.sol
file and save it - Wait a bit
- Done
We didn’t have to manually do any of the following:
- Compile your contract
- Deploy your contract
- Restore the state
- Restart dapp to get new contract
This is a big improvement to the developer experience, and allows us to develop faster using the Solidity Hot Loader.
We can keep developing our smart contract and Solidity Hot Loader will update our dapp.
Please note, that Solidity Hot Loader under the covers uses OpenZeppelin Upgrades, so is limited to contracts that can be upgradeable, e.g. no constructors: https://docs.openzeppelin.com/upgrades/2.8/writing-upgradeable