3A. 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 shift BTC to/from our Ethereum contract. If you haven't already, checkout 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 install --save @renproject/gateway-js

Import it at the top of your file with:

src/App.js
import GatewayJS from "@renproject/gateway-js";

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
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`),
}
],
});

We tell it what asset we are shifting 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

Shifting out looks similar, but accepts different parameters. When a token is being bridged from Ethereum to its native chain with RenVM, the Shifter contract will emit a ShiftOut 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 shift. To shift out, you can pass any of the three sets of parameters to GatewayJS:

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

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

  3. The shift-out ID which can be found in a specific Ethereum shift-out 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)) },
],
}).result();
this.log(`Withdrew ${amount} BTC to ${recipient}.`);

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-js";
import Web3 from "web3";
import './App.css';
import ABI from "./ABI.json";
// Replace with your contract's address.
const contractAddress = "0xb2731C04610C10f2eB6A26ad14E607d44309FC10";
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);
});
}
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 {
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`),
}
],
});
this.log(`Deposited ${amount} BTC.`);
} catch (error) {
// Handle error
console.error(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)) },
],
}).result();
this.log(`Withdrew ${amount} BTC to ${recipient}.`);
}
}
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.

GatewayJS popup

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.

RenVM will currently only see the first transaction to the gateway address. It must be the exact amount shown. Any other funds sent to the gateway address cannot be recovered until the next release of RenVM.

It can take a minute, or two, for RenVM to process the transfer. After completing all the steps, you should see the balance increase. You won't receive the full 0.001 BTC due to transfer fees.

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 (again, some transfer fees will be deducted).

You can find the site deployed here as well: https://renproject.github.io/renjs-tutorial