Guide to use the Gas Station Network starter kit on a local network.
Setup
Install and run a local blockchain using ganache-cli
$ npm install -g ganache-cli
$ ganache-cli --deterministic
In a separate terminal create the project folder.
$ mkdir myproject
$ cd myproject
Unpack the Gas Station Network starter kit (this could take a little while so maybe make a cup of tea)
$ npx @openzeppelin/cli@next unpack gsn
Initialize the project
$ npx openzeppelin init
? Welcome to the OpenZeppelin SDK! Choose a name for your project myproject
? Initial project version 1.0.2
Project initialized. Write a new contract in the contracts folder and run 'openzeppelin create' to deploy it.
App
Start the app
$ cd client
$ npm run start
The app will open in a browser on http://localhost:3000/
Scroll down and the app will show your Web3 provider and also show that your Counter Instance hasn’t been deployed.
Press Request Access button, then in MetaMask press Connect button (assuming you are using MetaMask).
In MetaMask change your network to Localhost:8545 to connect to ganache-cli
.
Counter contract
Deploy the Counter contract.
In a new terminal from your project folder run npx openzeppelin create
and use the interactive commands to deploy the Counter contract to the development network.
$ npx openzeppelin create
Nothing to compile, all contracts are up to date.
? Pick a contract to instantiate Counter
? Pick a network development
All contracts are up to date
? Do you want to call a function on the instance after creating it? Yes
? Select which function * initialize(num: uint256)
? num (uint256): 23
✓ Instance created at 0x4bf749ec68270027C5910220CEAB30Cc284c7BA2
0x4bf749ec68270027C5910220CEAB30Cc284c7BA2
In the app, press Increase Counter by 1 button.
Confirm the transaction in MetaMask and once the transaction is confirmed the app will have the updated counter value.
Add GSN support to Counter contract
In an editor, modify the Counter contract
Uncomment the import of GSNRecipient.sol
Change the contract to inherit from GSNRecipient by uncommenting the contract declaration and commenting the existing declaration.
Comment _owner = msg.sender;
in the initialize
function
Uncomment the lines in the initialize
function
Uncomment acceptRelayedCall
function
Uncomment setRelayHubAddress
function
Uncomment getRecipientBalance
function
The contract should look as follows:
pragma solidity ^0.5.0;
import "@openzeppelin/contracts-ethereum-package/contracts/GSN/GSNRecipient.sol";
import "@openzeppelin/upgrades/contracts/Initializable.sol";
contract Counter is Initializable, GSNRecipient {
//contract Counter is Initializable {
//it keeps a count to demonstrate stage changes
uint private count;
address private _owner;
function initialize(uint num) public initializer {
GSNRecipient.initialize();
_owner = _msgSender();
//_owner = msg.sender;
count = num;
}
// accept all requests
function acceptRelayedCall(
address,
address,
bytes calldata,
uint256,
uint256,
uint256,
uint256,
bytes calldata,
uint256
) external view returns (uint256, bytes memory) {
return _approveRelayedCall();
}
function owner() public view returns (address) {
return _owner;
}
// getter
function getCounter() public view returns (uint) {
return count;
}
//and it can add to a count
function increaseCounter(uint256 amount) public {
count = count + amount;
}
//We'll upgrade the contract with this function after deploying it
//Function to decrease the counter
function decreaseCounter(uint256 amount) public returns (bool) {
require(count > amount, "Cannot be lower than 0");
count = count - amount;
return true;
}
function setRelayHubAddress() public {
if(getHubAddr() == address(0)) {
_upgradeRelayHub(0xD216153c06E857cD7f72665E0aF1d7D82172F494);
}
}
function getRecipientBalance() public view returns (uint) {
return IRelayHub(getHubAddr()).balanceOf(address(this));
}
}
Deploy a new version using npx openzeppelin create
to deploy Counter to the development network and call initialize(num: uint256)
function.
$ npx openzeppelin create
✓ Compiled contracts with solc 0.5.11 (commit.c082d0b4)
? Pick a contract to instantiate Counter
? Pick a network development
✓ Contract Counter deployed
All contracts have been deployed
? Do you want to call a function on the instance after creating it? Yes
? Select which function * initialize(num: uint256)
? num (uint256): 23
✓ Instance created at 0x6f84742680311CEF5ba42bc10A71a4708b4561d1
0x6f84742680311CEF5ba42bc10A71a4708b4561d1
In the app, press Increase Counter by 1 button.
Confirm the transaction in MetaMask and once the transaction is confirmed the app will have the updated counter value.
Update the app to use GSN
In an editor, modify App.js (in ‘client/src’ directory).
Uncomment context
to use useWeb3Network
for http://127.0.0.1:8545
Comment context
with useWeb3Injected
The App.js should now look as follows:
import React, { useState } from 'react';
import { useWeb3Network, useEphemeralKey, useWeb3Injected } from '@openzeppelin/network';
import Header from './components/Header/index.js';
import Footer from './components/Footer/index.js';
import Hero from './components/Hero/index.js';
import Web3Info from './components/Web3Info/index.js';
import Counter from './components/Counter/index.js';
import styles from './App.module.scss';
// const infuraToken = process.env.REACT_APP_INFURA_TOKEN;
const infuraToken = '95202223388e49f48b423ea50a70e336';
function App() {
// get ephemeralKey
const signKey = useEphemeralKey();
// get GSN web3
// const context = useWeb3Network(`wss://rinkeby.infura.io/ws/v3/${infuraToken}`, {
// pollInterval: 15 * 1000,
// gsn: {
// signKey,
// },
// });
const context = useWeb3Network('http://127.0.0.1:8545', {
gsn: {
dev: true,
signKey,
},
});
// const context = useWeb3Injected();
// load Counter json artifact
let counterJSON = undefined;
try {
counterJSON = require('../../contracts/Counter.sol');
} catch (e) {
console.log(e);
}
// load Counter instance
const [counterInstance, setCounterInstance] = useState(undefined);
let deployedNetwork = undefined;
if (!counterInstance && context && counterJSON && counterJSON.networks && context.networkId) {
deployedNetwork = counterJSON.networks[context.networkId.toString()];
if (deployedNetwork) {
setCounterInstance(new context.lib.eth.Contract(counterJSON.abi, deployedNetwork.address));
}
}
function renderNoWeb3() {
return (
<div className={styles.loader}>
<h3>Web3 Provider Not Found</h3>
<p>Please, install and run Ganache.</p>
</div>
);
}
return (
<div className={styles.App}>
<Header />
<Hero />
<div className={styles.wrapper}>
{!context.lib && renderNoWeb3()}
<div className={styles.contracts}>
<h1>BUIDL with GSN Kit!</h1>
<div className={styles.widgets}>
<Web3Info title="Web3 Provider" context={context} />
<Counter {...context} JSON={counterJSON} instance={counterInstance} deployedNetwork={deployedNetwork} />
</div>
</div>
</div>
<Footer />
</div>
);
}
export default App;
Fund Recipient
The app will update and show that the Provider is GSN and that the recipient has no funds with a command to run to fun the recipient.
Fund using the command shown in the app from your project directory.
npx oz-gsn fund-recipient --recipient 0x6f84742680311CEF5ba42bc10A71a4708b4561d1
GSN powered app
Refresh the app.
In the app, press Increase Counter by 1 button.
The Gas Station Network will be used for the transaction.