This project is built using the loom-js Transfer Gateway API, and demonstrates how to transfer ERC721/ERC20 tokens between Ethereum and Loom DAppChains via a browser frontend built with React.
- node >= 8
- wget (used to download Loom DAppChain binary)
- nc (used to check required ports aren't in use)
- MetaMask browser extension
After cloning this repository install all the necessary dependencies use the transfer_gateway
shell script to automatically download & install all the required dependencies:
./transfer_gateway setup
After everything has been installed use the transfer_gateway
script to spin up all the required
services:
./transfer_gateway start
When you're done playing around with the example you should stop all the running services using:
./transfer_gateway stop
To check which services are currently running:
./transfer_gateway status
If you want to start with a clean slate you can remove all the node_modules
directories, and the
loom binary by running:
./transfer_gateway cleanup
After ./transfer_gateway start
and starts the necessary services you can access the web frontend
at localhost:8080
.
This example requires MetaMask to be installed, you must also set MetaMask to use the
Private Network
at localhost:8545
.
You will also need to import the private key 0xbb63b692f9d8f21f0b978b596dc2b8611899f053d68aec6c1c20d1df4f5b6ee2
generated by Ganache (account 0x5194b63f10691e46635b27925100cfc0a5ceca62
) into MetaMask.
- If you stop the example and start it up again you may encounter a MetaMask error like this:
"rpc error with payload {…nonce. account has nonce of: 0 tx has nonce of: 5"
This happens because MetaMask keeps track of the last nonce that was used to send a transaction with a particular account, and when all the chains are reset the nonce needs to be reset back to zero. You can force MetaMask to reset the nonce by reseting the imported account.
-
Restart the entire service can help to clean cache also
./transfer_gateway restart
, after restart you should clean MetaMask cache. -
Sometimes when run
./transfer_gateway start
and a message likeGanache port 8545 is already in use
appears it's because the serviceganache
is running (obvious), however if you never ran./transfer_gateway start
then it's because another agent startedganache
and this example starts it selfganache
version. -
In order to
stop all
services (used by cards-gateway-example) you should run./transfer_gateway stop
, however if services likeganache
orwebpack
didn't halt then you need to stop then by the process id orpid
.
The cards-gateway-example
directory contains five sub directories:
├── dappchain
├── truffle-ethereum
├── truffle-dappchain
├── transfer-gateway-scripts
└── webclient
In the following sections we'll explain what's in each of these directories.
NOTE: In this doc we use the term Mainnet to refer to an Ethereum-like network, as a matter of convenience. In this example the only Ethereum network we actually interact with is Ganache, but you could also hook everything up to use the Ethereum Mainnet, or the Rinkeby Testnet by changing a few configuration settings.
This directory contains the loom
binary (after setup), DAppChain config files, and some key pairs.
The keys have been generated specifically for this example, don't reuse them anywhere else. If you wish to use this project as a starting point for your own please don't forget to replace all the keys with your own.
Let's take a closer look at loom.yaml
, this is a configuration file that can be used to configure
both the DAppChain node and the Transfer Gateway Oracle. You can find documentation on the various
settings in this config file in the Loom SDK docs,
but we'll highlight some of the settings here that are relevant to this example.
# This is a unique identifier for the DAppChain used in this example
ChainID: "default"
# This enables the latest version of the contract registry maintained by the DAppChain, which is
# required in order for the Transfer Gateway to be able verify DAppChain contract ownership.
# This setting must not be changed after the DAppChain starts for the first time!
RegistryVersion: 2
# Transfer Gateway Settings
TransferGateway:
# Enables the Gateway Go contract (DAppChain Gateway), must be the same on all nodes.
ContractEnabled: true
# Enables the in-process Gateway Oracle.
OracleEnabled: true
# Address of the Ganache network where the Mainnet Solidity contracts are deployed
# (take a look at the truffle-ethereum directory).
EthereumURI: "http://127.0.0.1:8545"
# Address of the Mainnet Gateway contract deployed to the Ganache network above.
MainnetContractHexAddress: "0xf5cad0db6415a71a5bc67403c87b56b629b4ddaa"
# Private key that will be used by the Gateway Oracle to sign pending withdrawals.
MainnetPrivateKeyPath: "oracle_eth_priv.key"
# Private key that will be used by the Gateway Oracle to send txs to the DAppChain Gateway.
DAppChainPrivateKeyPath: "oracle_priv.key"
# Address of DAppChain node the Gateway Oracle should interact with.
DAppChainReadURI: "http://localhost:46658/query"
DAppChainWriteURI: "http://localhost:46658/rpc"
# These control how frequently the Gateway Oracle will poll the DAppChain and Ganache.
DAppChainPollInterval: 1 # seconds
MainnetPollInterval: 1 # seconds
# Number of seconds to wait before starting the Gateway Oracle, this allows the DAppChain node
# to spin up before the Gateway Oracle tries to connect to it.
OracleStartupDelay: 5
# Number of seconds the Gateway Oracle should wait between reconnection attempts if it encounters
# a network error of some kind.
OracleReconnectInterval: 5
Now let's take a look at the genesis.json
file (or genesis.example.json
if you haven't setup the
example yet), this file specifies which Go contracts should be deployed to the DAppChain when the
DAppChain starts up for the first time. You can read more about the coin
and dpos
contracts in the
Built-in Contracts docs, but these two contracts are not relevant to this particular example.
The two contracts we're interested in are the built-in addressmapper
, and gateway
contracts.
{
"contracts": [
{
"vm": "plugin",
"format": "plugin",
"name": "addressmapper",
"location": "addressmapper:0.1.0",
"init": null
},
{
"vm": "plugin",
"format": "plugin",
"name": "gateway",
"location": "gateway:0.1.0",
"init": {
"owner": {
"chain_id": "default",
"local": "c/IFoEFkm4+D3wdqLmFU9F3t3Sk="
},
"oracles": [
{
"chain_id": "default",
"local": "22nuyPPZ53/qAqFnhwD2EpNu9ss="
}
]
}
}
]
}
The Address Mapper contract is responsible for mapping owned DAppChain accounts to Mainnet accounts, and vice versa, you can read more about its purpose in the Transfer Gateway docs.
The Gateway contract is responsible for contract mapping, and token transfers to and from the DAppChain. It relies on the Gateway Oracle (though there may be more than one) to transfer relevant data between the DAppChain and Mainnet.
This directory contains the contracts that are deployed to Mainnet, primarily the Gateway
contract
(Mainnet Gateway), and the CryptoCards/GameToken
ERC721/ERC20 token contract.
The CryptoCards/GameToken
ERC721/ERC20 contract implements a convenient function for depositing tokens into the
Gateway
contract:
function depositToGateway(uint tokenId) public {
safeTransferFrom(msg.sender, gateway, tokenId);
}
The Gateway
contract implements the withdrawERC721
or withdrawERC20
function, which should be called by a user
who wishes to withdraw a token from the Mainnet Gateway back to their own Mainnet account.
NOTE: The Mainnet Gateway will only allow a user to withdraw a token if they can provide proof that it's no longer in circulation on the DAppChain, you can read more about it in the Transfer Gateway docs.
This directory contains the CryptoCardsDAppChain/GameTokenDAppChain
ERC721/ERC20 contract that's deployed to the DAppChain,
this contract will keep track of any tokens sent from the CryptoCards/GameToken
ERC721/ERC20 contract on Mainnet
to the DAppChain via the Transfer Gateway.
When a user deposits their CryptoCards/GameToken
tokens into the Mainnet Gateway contract the DAppChain
Gateway contract will mint
the corresponding tokens in the CryptoCardsDAppChain/GameTokenDAppChain
contract.
Note that the DAppChain Gateway must be authorized to mint tokens in the CryptoCardsDAppChain/GameTokenDAppChain
contract, otherwise token transfers from Mainnet to the DAppChain won't work.
function mint(uint256 _uid) public {
require(msg.sender == gateway);
_mint(gateway, _uid);
}
This directory contains a single index.js
script file which is responsible for mapping the
CryptoCards/GameToken
ERC721/ERC20 contract on Mainnet to the CryptoCardsDAppChain/GameTokenDAppChain
ERC721/ERC20 contract on the
DAppChain. Without this contract mapping the Transfer Gateway won't know what to do when it receives
ERC721/ERC20 tokens from either contract. You can read more about contract mapping in the
Transfer Gateway docs.
This directory contain the web frontend, which requires MetaMask
to be installed in a compatible
browser (Chrome / Firefox). The web frontend can be reached at http://localhost:8080
after running
the ./transfer_gateway start
.
The frontend consists of four pages:
Home
- where you must map your Mainnet account to a DAppChain account by signing a message viaMetaMask
with your Mainnet account. This must be done before attempting to transfer any cards.Owned Cards
- where you can see the cards currently owned by you on Mainnet (yeah we just gave 5 cards to you 😉).Cards on Gateway
- where you can see the cards in the Mainnet Gateway that are waiting to be withdrawn by you to your Mainnet account.Cards on DAppChain
- where you can see the cards that have been transfered to the DAppChain from your Mainnet account.
BSD 3-Clause License