@near-eth/client
– the Rainbow Bridge client library 🌈🌉
![@near-eth/client Version](https://img.shields.io/npm/v/@near-eth/client)
Do you want to allow your users to send assets between Ethereum & NEAR over
the Rainbow Bridge?
Do you want to easily send assets between the two blockchains using your
command line?
Did you build a custom Rainbow Bridge Connector and now you want to figure
out how to build a client library for it, so other people can actually use it?
If you answered "Yes" to any of the above questions, this is the library for you! (Ok, ok, the CLI interface is not yet supported!)
Read on to find out how to:
To contribute to this library, see github.com/aurora-is-near/rainbow-bridge-client.
Add it to your browser app
Let's say you want to allow users to send ERC20 tokens from Ethereum to NEAR,
where they'll become NEP141 tokens.
Step 0: Add Dependencies
You'll need to add two dependencies to your app:
npm install --save @near-eth/client @near-eth/nep141-erc20
Or, if using yarn:
yarn add @near-eth/client @near-eth/nep141-erc20
What is @near-eth/nep141-erc20
?
The Rainbow Bridge between Ethereum and NEAR has many pieces. One piece is Connector contracts. The connector code for converting ERC20 tokens in Ethereum to NEP141 tokens in NEAR lives at github.com/aurora-is-near/rainbow-token-connector.
The code for using a given connector from an app has its own library. The one for the connector above is @near-eth/nep141-erc20
.
Anyone can make connector contracts, and anyone can make client libraries for these contracts. If they follow the format of @near-eth/nep141-erc20
, these client libraries will work automatically with the core Rainbow Bridge transfer library at @near-eth/client
.
Generally, each connector client library, like @near-eth/nep141-erc20
, will export four main interfaces, which can be used to:
- Go from a "natural" Ethereum token to a "bridged" NEAR equivalent
- Go from a "bridged" NEAR token, meaning a token that started its life in Ethereum but which now lives in NEAR, back to Ethereum
- Go from a natural NEAR token to a bridged Ethereum equivalent
- Go from a bridged Ethereum token back to NEAR
For @near-eth/nep141-erc20
, these main exports are:
naturalErc20
– example: go from DAI (a popular ERC20 token) to nDAIbridgedNep141
– example: convert nDAI back to DAInaturalNep141
– example: go from a natural NEAR token, such as BNNA Tokens in berryclub.io, to eBNNA in EthereumbridgedErc20
– example: convert eBNNA back to BNNA
You can have multiple connector libraries in your app, some which may be maintained by NEAR and are in the @near-eth
organization, and some which are not. An example package.json
might end up looking something like:
"dependencies": {
"@near-eth/client": "*",
"@near-eth/nep141-erc20": "*",
"@near-eth/nep4-erc721": "*",
"rainbow-bridge-erc20-with-rebase-and-nep21": "*",
}
(Note: @near-eth/nep4-erc721
and rainbow-bridge-erc20-with-rebase-and-nep21
do NOT currently exist, and are only shown to illustrate how this could work. As an aside, the current ERC20 connector does NOT support tokens which use the rebase
feature like AMPL & BASE, which is why a hypothetical community-contributed "erc20-with-rebase" connector library is shown.)
Step 1: Authenticate user with both NEAR & Ethereum
A full transfer will make multiple calls to both the NEAR & Ethereum blockchains, and you'll need to make sure the user has an account/wallet on both chains.
NEAR Authentication
setNearConnection
Your app needs to call setNearConnection
and pass it a WalletConnection
instance from near-api-js
. Example:
import { keyStores, Near, WalletConnection } from 'near-api-js'
import { setNearConnection } from '@near-eth/client'
window.nearConnection = new WalletConnection(
new Near({
keyStore: new keyStores.BrowserLocalStorageKeyStore(),
networkId: process.env.nearNetworkId,
nodeUrl: process.env.nearNodeUrl,
helperUrl: process.env.nearHelperUrl,
walletUrl: process.env.nearWalletUrl
})
)
setNearConnection(window.nearConnection)
If you don't know what to put for the settings passed to new Near
, check out the cheat sheet.
requestSignIn()
Additionally, you'll probably want to verify that a user has a NEAR account before they get started. Given a "Sign in with NEAR" button:
<button id="authNear">Sign in with NEAR</button>
You can add this handler:
document.querySelector('#authNear').onclick = () => {
window.nearConnection.requestSignIn()
}
Ethereum Authentication
Your app needs to call setEthProvider
. Given a "Connect to Ethereum" button:
<button id="authEthereum">Connect to Ethereum</button>
You can use web3modal to add this handler:
import Web3Modal from 'web3modal'
import { setEthProvider } from '@near-eth/client'
const web3Modal = new Web3Modal({ cacheProvider: true })
async function loadWeb3Modal () {
window.ethProvider = await web3Modal.connect()
setEthProvider(window.ethProvider)
}
document.querySelector('#authEthereum').onclick = loadWeb3Modal
if (web3Modal.cachedProvider) loadWeb3Modal()
Step 2: Initiate a transfer
Great, now your user is authenticated with both NEAR & Ethereum. Now let's say you have a form.
<form id="sendErc20ToNear">
<input id="erc20Address" />
<input id="amount" />
</form>
Here's some JavaScript to make this work:
import { naturalErc20 } from '@near-eth/nep141-erc20'
document.querySelector('#sendErc20ToNear').onsubmit = async e => {
e.preventDefault()
const [sender] = await window.ethProvider.request({method: 'eth_requestAccounts'})
const recipient = window.nearConnection.getAccountId()
const { erc20Address, amount } = e.target.elements
naturalErc20.sendToNear({
sender,
recipient,
erc20Address: erc20Address.value,
amount: amount.value
})
}
Step 3: List in-progress transfers
For the rest of the lifetime of the transfer you just initiated, you will use
exports from @near-eth/client
, rather than the connector-specific library.
Let's say you want to list in-progress transfers in this ol
:
<ol id="transfers-go-here"></ol>
Here's code to render the list of transfers:
import { get, onChange } from '@near-eth/client'
function renderTransfers () {
const transfers = get({ filter: t => t.status === 'in-progress' })
document.querySelector('#transfers-go-here').innerHTML =
transfers.map(renderTransfer).join('')
}
onChange(renderTransfers)
renderTransfers()
If using React, you'd want something like:
import React, { useState, useEffect } from 'react';
import { get, onChange } from '@near-eth/client'
import Transfer from './Transfer'
function useTransfers(filter) {
const [transfers, setTransfers] = useState([])
useEffect(() => {
get({ filter }).then(setTransfers)
onChange(() => get({ filter }).then(setTransfers))
}, [])
return transfers
}
export function Transfers () {
const transfers = useTransfers(t => t.status === 'in-progress')
return (
<ol>
{transfers.map(transfer =>
<Transfer key={transfer.id} transfer={transfer} />
)}
</ol>
)
}
And here's what renderTransfer
might look like, using vanilla JS:
import { act, decorate } from '@near-eth/client'
function renderTransfer (transfer) {
transfer = decorate(transfer, { locale: 'en_US' })
return `
<li class="transfer" id="${transfer.id}">
${transfer.amount}
${transfer.sourceTokenName} from
${transfer.sender} to
${transfer.recipient}
${!transfer.callToAction ? '' : `
<button class="act-on-transfer">
${transfer.callToAction}
</button>
`}
</li>
`
}
document.querySelector('body').addEventListener('click', event => {
const callToAction = event.target.closest('.act-on-transfer')
if (callToAction) {
const transferId = callToAction.closest('.transfer').id
act(transferId)
}
})
And an equivalent Transfer
component, if using React:
import { act, decorate } from '@near-eth/client'
export function Transfer (transfer) {
transfer = decorate(transfer, { locale: 'en_US' })
return (
<li>
{transfer.amount}
{transfer.sourceTokenName} from
{transfer.sender} to
{transfer.recipient}
{transfer.callToAction &&
<button onClick={() => act(transfer.id)}>
{transfer.callToAction}
</button>
}
</li>
)
}
Here's some docs about act, and two example connector-specific behaviors.
Here's some docs about decorate.
Here's the attributes for two kinds of raw transfers, prior to being decorated.
Step 4: check & update status of in-progress transfers
Wait for the Ethereum & NEAR authentications described in Step 1 to complete, then call this:
import { checkStatusAll } from '@near-eth/client'
checkStatusAll({ loop: 15000 })
What's it do?
This library is designed to be non-blocking, which means a user can start
multiple transfers at once, and the library won't pause to wait for blocks to
be mined in Ethereum, finalized in NEAR, or synced between the two.
This means that with only the code from Steps 1-3, nothing else will happen. A
user will have sent an initial transaction to the Ethereum or NEAR blockchain,
but neither your app nor any other service will ever check to see if that
transaction completes successfully. Nor will any app or service prompt the user
to complete the next transaction in the process (any transfer requires multiple
steps & multiple on-chain transactions to complete).
checkStatusAll
will loop as frequently as you tell it to. It will check to
see if transactions have been mined, synced, or finalized, and update transfers
in localStorage accordingly. When transfers are updated, the onChange
function in Step 3 will trigger a UI update.
Step 5: there is no step 5!
That's it! You successfully integrated cross-chain transfers into your app in
just four steps. 🌈🌉🎉
To make it more beautiful, check out the API docs and example
code (implemented in vanilla/no-framework JavaScript).
Author a custom connector library
- Copy the code in the
@near-eth/nep141-erc20
library - Adjust for your needs
- Send a Pull Request to add your client to the
getTransferType
lookup logic in @near-eth/client