GatewayJS

Part 3 of 3 - high level option

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:

Initializing GatewayJS

Install GatewayJS using npm:

npm
yarn
npm
npm install --save @renproject/gateway
yarn
yarn add @renproject/gateway

Import it at the top of your file with:

src/App.js
import 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.

Depositing funds

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.001; // 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 with
sendTo: contractAddress,
// The name of the function we want to call
contractFn: "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 Ethereum
web3Provider: 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 error
console.error(error);
}

Withdrawing funds

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:

  1. A smart contract function to be called which will call burn for the ERC20 tokens (see below).

  2. The hash of a transaction on Ethereum which has already burned the tokens on Ethereum (as ethTxHash).

  3. 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 with
sendTo: contractAddress,
// The name of the function we want to call
contractFn: "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 Ethereum
web3Provider: web3.currentProvider,
}).result();
this.log(`Withdrew ${amount} BTC to ${recipient}.`);

Resuming transfers on reload

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 storage
const previousGateways = await gatewayJS.getGateways();
// Resume each transfer
for (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.

Complete App.js

Your App.js file should now look like this (don't forget to replace the contract address):

src/App.js
import 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 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);
});
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.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 () => {
const { web3, gatewayJS } = this.state;
const amount = 0.001; // 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 with
sendTo: contractAddress,
// The name of the function we want to call
contractFn: "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 Ethereum
web3Provider: web3.currentProvider,
}).result();
this.log(`Deposited ${amount} BTC.`);
} catch (error) {
// Handle error
this.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 with
sendTo: contractAddress,
// The name of the function we want to call
contractFn: "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 Ethereum
web3Provider: web3.currentProvider,
}).result();
this.log(`Withdrew ${amount} BTC to ${recipient}.`);
}
recoverTransfers = async () => {
const { web3, gatewayJS } = this.state;
// Load previous transfers from local storage
const previousGateways = await gatewayJS.getGateways();
// Resume each transfer
for (const transfer of Array.from(previousGateways.values())) {
gatewayJS
.recoverTransfer(web3.currentProvider, transfer)
.pause()
.result()
.catch(this.logError);
}
}
}
export default App;

Testing it

If you click "Deposit 0.001 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.001 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.001 BTC, minus fees (0.00005 BTC transfer fee and 0.1% RenVM fee).

After depositing some BTC into our Ethereum contract

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