GatewayJS is being deprecated in favor of tightly integrated user flows using RenJS v2. In order to use all assets supported by Multichain, please start migration as soon as possible.
In this chapter, we will be integrating GatewayJS into our user interface so that users can interactively transfer BTC to/from our Ethereum contract. If you haven't already, check out Part 1 and Part 2 of this tutorial.
GatewayJS allows you to integrate with RenVM with only a few lines of code. It provides a full user interface, as seen in the screenshot below:
Install GatewayJS using npm
:
npm install --save @renproject/gateway
yarn add @renproject/gateway
Import it at the top of your file with:
src/App.jsimport GatewayJS from "@renproject/gateway";
Initialize it and add it to your App.js
state (line 17
, after message: "",
) like so:
gatewayJS: new GatewayJS("testnet"),
We initialize it using the network "testnet"
. This will tell it to talk to RenVM Testnet and to use Ethereum Kovan.
GatewayJS's main method is open
, which is used to create a new instance of its user interface.
In the body of deposit
, we pass in to open
the deposit's details:
const { web3, gatewayJS } = this.state;const amount = 0.003; // BTC​await gatewayJS.open({// Send BTC from the Bitcoin blockchain to the Ethereum blockchain.sendToken: GatewayJS.Tokens.BTC.Btc2Eth,​// Amount of BTC we are sending (in Satoshis)suggestedAmount: Math.floor(amount * (10 ** 8)), // Convert to Satoshis​// The contract we want to interact withsendTo: contractAddress,​// The name of the function we want to callcontractFn: "deposit",// The nonce is used to guarantee a unique deposit address.nonce: GatewayJS.utils.randomNonce(),​// Arguments expected for calling `deposit`contractParams: [{name: "_msg",type: "bytes",value: web3.utils.fromAscii(`Depositing ${amount} BTC`),}],// Web3 provider for submitting mint to Ethereumweb3Provider: web3.currentProvider,}).result();
We tell it what asset we are transferring and give it the details it needs to talk to our Ethereum contract.
We provide the amount as suggestedAmount
because it will accept the user's first deposit, regardless of value. If you change it to requiredAmount
it will only accept a deposit with at least that value. If you include this parameter, there's currently no way of handling incorrect deposits.
The open
method returns a Gateway
object, which exposes the following functions:
.result()
- returns a Promise to the result of the deposit and event emitter, which in turn has:
.then(result => console.log(result))
- alternatively await
syntax can be used.
.catch(error => console.error(error)
- if using await
, surround with a try
/catch
.
.on("status", status => console.log(status))
- for knowing where the shift is up to.
.pause()
- will minimize the window to the top-right corner of the window.
.resume()
for resuming the popup after pausing it.
Using await
, we complete the deposit
function like so:
try {await gatewayJS.open({// ... same as above}).result();this.log(`Deposited ${amount} BTC.`);} catch (error) {// Handle errorconsole.error(error);}
Burning & releasing looks similar, but accepts different parameters. When a token is being bridged from Ethereum to its native chain with RenVM, the Gateway
contract will emit a Burn
event. The darknodes will automatically see this event and complete the shift on the native chain, but we can query them to watch the progress of the transaction. To burn & release, you can pass any of the three sets of parameters to GatewayJS:
A smart contract function to be called which will call burn for the ERC20 tokens (see below).
The hash of a transaction on Ethereum which has already burned the tokens on Ethereum (as ethTxHash
).
The burn ID which can be found in a specific Ethereum burn log (as burnReference
).
const { web3, gatewayJS, balance } = this.state;​const amount = balance;const recipient = prompt("Enter BTC recipient:");​// You can surround shiftOut with a try/catch to handle errors.​await gatewayJS.open({// Send BTC from the Ethereum blockchain to the Bitcoin blockchain.// This is the reverse of shitIn.sendToken: GatewayJS.Tokens.BTC.Eth2Btc,​// The contract we want to interact withsendTo: contractAddress,​// The name of the function we want to callcontractFn: "withdraw",// Arguments expected for calling `deposit`contractParams: [{ name: "_msg", type: "bytes", value: web3.utils.fromAscii(`Withdrawing ${amount} BTC`) },{ name: "_to", type: "bytes", value: "0x" + Buffer.from(recipient).toString("hex") },{ name: "_amount", type: "uint256", value: Math.floor(amount * (10 ** 8)) },],// Web3 provider for submitting burn to Ethereumweb3Provider: web3.currentProvider,}).result();​this.log(`Withdrew ${amount} BTC to ${recipient}.`);
If the user reloads the page during a transfer, it can be resumed with getGateways()
and recoverTransfer
. This uses the browser's local storage, so it should be used in conjunction with a more permanent storage method to ensure that transfers aren't lost. Create a recoverTransfers
function:
const { web3, gatewayJS } = this.state;// Load previous transfers from local storageconst previousGateways = await gatewayJS.getGateways();// Resume each transferfor (const transfer of Array.from(previousGateways.values())) {gatewayJS.recoverTransfer(web3.currentProvider, transfer).pause().result().catch(logError);}
It should be called at the end of the componentDidMount
method.
Your App.js
file should now look like this (don't forget to replace the contract address):
src/App.jsimport React from 'react';import GatewayJS from "@renproject/gateway";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: "",gatewayJS: new GatewayJS("testnet"),}}​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 accessawait 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 Ganacheelse {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 secondsthis.updateBalance();setInterval(() => {this.updateBalance();}, 10 * 1000);});​this.recoverTransfers().catch(this.logError);}​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 () => {const { web3, gatewayJS } = this.state;const amount = 0.003; // BTC​try {await gatewayJS.open({// Send BTC from the Bitcoin blockchain to the Ethereum blockchain.sendToken: GatewayJS.Tokens.BTC.Btc2Eth,​// Amount of BTC we are sending (in Satoshis)suggestedAmount: Math.floor(amount * (10 ** 8)), // Convert to Satoshis​// The contract we want to interact withsendTo: contractAddress,​// The name of the function we want to callcontractFn: "deposit",​// The nonce is used to guarantee a unique deposit addressnonce: GatewayJS.utils.randomNonce(),​// Arguments expected for calling `deposit`contractParams: [{name: "_msg",type: "bytes",value: web3.utils.fromAscii(`Depositing ${amount} BTC`),}],​// Web3 provider for submitting mint to Ethereumweb3Provider: web3.currentProvider,}).result();this.log(`Deposited ${amount} BTC.`);} catch (error) {// Handle errorthis.logError(error);}}​withdraw = async () => {const { web3, gatewayJS, balance } = this.state;​const amount = balance;const recipient = prompt("Enter BTC recipient:");​// You can surround shiftOut with a try/catch to handle errors.​await gatewayJS.open({// Send BTC from the Ethereum blockchain to the Bitcoin blockchain.// This is the reverse of shitIn.sendToken: GatewayJS.Tokens.BTC.Eth2Btc,​// The contract we want to interact withsendTo: contractAddress,​// The name of the function we want to callcontractFn: "withdraw",​// Arguments expected for calling `deposit`contractParams: [{ name: "_msg", type: "bytes", value: web3.utils.fromAscii(`Withdrawing ${amount} BTC`) },{ name: "_to", type: "bytes", value: "0x" + Buffer.from(recipient).toString("hex") },{ name: "_amount", type: "uint256", value: Math.floor(amount * (10 ** 8)) },],​// Web3 provider for submitting burn to Ethereumweb3Provider: web3.currentProvider,}).result();​this.log(`Withdrew ${amount} BTC to ${recipient}.`);}​recoverTransfers = async () => {const { web3, gatewayJS } = this.state;// Load previous transfers from local storageconst previousGateways = await gatewayJS.getGateways();// Resume each transferfor (const transfer of Array.from(previousGateways.values())) {gatewayJS.recoverTransfer(web3.currentProvider, transfer).pause().result().catch(this.logError);}}}​export default App;
If you click "Deposit 0.003 BTC", it should open a GatewayJS popup will will lead you through depositing the BTC.
If you don't already have Testnet BTC, you'll need to go to a Bitcoin Testnet faucet (we recommend https://testnet-faucet.mempool.co).
Send 0.003 BTC
to the Bitcoin gateway address to continue. GatewayJS will wait for two confirmations before continuing, which may take around 15-20 minutes.
After completing all the steps, you should see the balance increase by 0.003 BTC
, minus fees (currently a0.001 BTC
transfer fee and a 0.25% RenVM fee).
Test withdrawing as well. If you don't have a Testnet BTC address to give it, the faucet lists a return address you can use. You should see the balance go back to 0 and the Bitcoin address you entered receive the BTC.
You can find the site deployed here as well: https://renproject.github.io/renjs-tutorial​