React UI

Part 2 of 3

Please note that when minting with RenVM on mainnet, users will often close the browser and come back at a later time when the initial source transaction is confirmed. Depending on the browser and/or device being used, this can cause transaction data held in local storage to be lost and a loss of user funds.

It is strongly recommended that developers back up user transactions to a more reliable location, such as a remote database, to prevent this scenario. The most important data to save for a user transaction is the nonce, which is used by the network to continue an in-progress minting transaction.

This chapter will setup a React app so that we can have a simple user interface for interacting with RenVM and the Ethereum contract that we deployed in Part 1. We will assume some level of knowledge about React, so head over to https://reactjs.org/tutorial/tutorial.html if you haven't used React before.

Create React App

Set up a React app with create-react-app. If you don't have npx, install node and npm.

npx create-react-app basic-app
cd basic-app

Install Web3:

npm
yarn
npm
npm install --save web3
yarn
yarn add web3

App.js

Replace src/App.js with the code below. It sets up the following:

  1. Connecting to Web3

  2. Showing the balance of the Basic contract

  3. Showing two buttons, Deposit and Withdraw

Replace the contractAddress with the contract you deployed in Part 1. The lines indicated with TODO will be filled out in Part 3.

src/App.js
import React from 'react';
import Web3 from "web3";
import './App.css';
import ABI from "./ABI.json";
// Replace with your contract's address.
const contractAddress = "0x3Aa969d343BD6AE66c4027Bb61A382DC96e88150";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
balance: 0,
message: "",
error: "",
}
}
componentDidMount = async () => {
let web3Provider;
// Initialize web3 (https://medium.com/coinmonks/web3-js-ethereum-javascript-api-72f7b22e2f0a)
// Modern dApp browsers...
if (window.ethereum) {
web3Provider = window.ethereum;
try {
// Request account access
await window.ethereum.enable();
} catch (error) {
// User denied account access...
this.logError("Please allow access to your Web3 wallet.");
return;
}
}
// Legacy dApp browsers...
else if (window.web3) {
web3Provider = window.web3.currentProvider;
}
// If no injected web3 instance is detected, fall back to Ganache
else {
this.logError("Please install MetaMask!");
return;
}
const web3 = new Web3(web3Provider);
const networkID = await web3.eth.net.getId();
if (networkID !== 42) {
this.logError("Please set your network to Kovan.");
return;
}
this.setState({ web3 }, () => {
// Update balances immediately and every 10 seconds
this.updateBalance();
setInterval(() => {
this.updateBalance();
}, 10 * 1000);
});
}
render = () => {
const { balance, message, error } = this.state;
return (
<div className="App">
<p>Balance: {balance} BTC</p>
<p><button onClick={() => this.deposit().catch(this.logError)}>Deposit 0.001 BTC</button></p>
<p><button onClick={() => this.withdraw().catch(this.logError)}>Withdraw {balance} BTC</button></p>
<p>{message}</p>
{error ? <p style={{ color: "red" }}>{error}</p> : null}
</div>
);
}
updateBalance = async () => {
const { web3 } = this.state;
const contract = new web3.eth.Contract(ABI, contractAddress);
const balance = await contract.methods.balance().call();
this.setState({ balance: parseInt(balance.toString()) / 10 ** 8 });
}
logError = (error) => {
console.error(error);
this.setState({ error: String((error || {}).message || error) });
}
log = (message) => {
this.setState({ message });
}
deposit = async () => {
this.logError("");
// TODO
}
withdraw = async () => {
this.logError("");
// TODO
}
}
export default App;

ABI.json

In order to talk to our Ethereum contract, we need to have its ABI. You can find this in Remix by going to the "Solidity Compiler" tab, clicking "Compilation Details" and copying the "ABI" section. Paste this into src/ABI.json:

src/ABI.json
[{
"constant": false,
"inputs": [{
"name": "_msg",
"type": "bytes"
}, {
"name": "_amount",
"type": "uint256"
}, {
"name": "_nHash",
"type": "bytes32"
}, {
"name": "_sig",
"type": "bytes"
}],
"name": "deposit",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}, {
"constant": false,
"inputs": [{
"name": "_msg",
"type": "bytes"
}, {
"name": "_to",
"type": "bytes"
}, {
"name": "_amount",
"type": "uint256"
}],
"name": "withdraw",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
}, {
"constant": true,
"inputs": [],
"name": "registry",
"outputs": [{
"name": "",
"type": "address"
}],
"payable": false,
"stateMutability": "view",
"type": "function"
}, {
"constant": true,
"inputs": [],
"name": "balance",
"outputs": [{
"name": "",
"type": "uint256"
}],
"payable": false,
"stateMutability": "view",
"type": "function"
}, {
"inputs": [{
"name": "_registry",
"type": "address"
}],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
}, {
"anonymous": false,
"inputs": [{
"indexed": false,
"name": "_amount",
"type": "uint256"
}, {
"indexed": false,
"name": "_msg",
"type": "bytes"
}],
"name": "Deposit",
"type": "event"
}, {
"anonymous": false,
"inputs": [{
"indexed": false,
"name": "_to",
"type": "bytes"
}, {
"indexed": false,
"name": "_amount",
"type": "uint256"
}, {
"indexed": false,
"name": "_msg",
"type": "bytes"
}],
"name": "Withdrawal",
"type": "event"
}]

Run a local server

Start a local development server by running:

npm run start

You can now access the React app at http://localhost:3000. The buttons shouldn't do anything yet, but we'll be fixing that in the next part of the tutorial.

Your brand new React app!

From here, you can choose to integrate with RenVM using GatewayJS - a high-level library which provides a complete user experience - or RenJS - a lower level SDK for integrating seamlessly with your dApp.