Using Gas Station Network starter kit on a local network

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 and call initialize(num: uint256) with a value e.g. 42.

$ npx openzeppelin create
Nothing to compile, all contracts are up to date.
? Pick a contract to instantiate Counter
? Pick a network development
✓ Added contract Counter
✓ 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): 42
✓ Setting everything up to create contract instances
✓ Instance created at 0xb4fFe5983B0B748124577Af4d16953bd096b6897
0xb4fFe5983B0B748124577Af4d16953bd096b6897

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.

3 Likes

This is amazing! Love it!

1 Like

Thanks for making this @abcoathup! In the constructor, shouldn’t you comment the 2nd assignment for _owner?

That is, keep only this:

_owner = _msgSender();

Instead of this:

_owner = _msgSender();
_owner = msg.sender;

?

1 Like

Great spot @PaulRBerg
Thank you. :pray: I have updated.