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.
Set up a React app with create-react-app
. If you don't have npx
, install node and npm.
npx create-react-app basic-appcd basic-app
yarn create react-app basic-appcd basic-app
Install Web3:
npm install --save web3
yarn add web3
Replace src/App.js
with the code below. It sets up the following:
Connecting to Web3
Showing the balance of the Basic
contract
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.jsimport 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;}} else if (window.web3) { // Legacy dApp browsers.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.003 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(""); // Reset error.// TODO}​withdraw = async () => {this.logError(""); // Reset error.// TODO}}​export default App;
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"}]
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.
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.