Native Token Bridge

Cross chain transfer of native tokens between Ropsten and Sokol.

Before we continue, please ensure that you have had a look at our Supported Ethereum Chains, followed the steps in Install Etherspot SDK and how to Bootstrap Etherspot SDK. We're assuming that you have completed these steps before going forward.

In this example, we're going to show you how to transfer native tokens from Ropsten Testnet to Sokol Testnet.

๐Ÿ›‘ Before we continue...

We're going to be using four Etherspot SDK instances here:

  • A ropsten Etherspot SDK for our user wallet

  • A ropsten Etherspot SDK for the Payment Hub on this network

โ†—๏ธ We will use this instance to send our DAI from and ETH to pay the gas fees.

  • A sokol Etherspot SDK for our user wallet

  • A sokol Etherspot SDK for the Payment Hub on this network

โ†˜๏ธ We will use this instance to receive our xDai on the xDai chain.

โš ๏ธ Make sure you've checked out Supported Ethereum Chains before you continue as we also show you the code to instantiate ropsten and sokol versions of the SDK. Remember to use the same private key or authentication for both SDK instances to get the same Ethereum address on both Ropsten Testnet and Sokol Testnet.

Getting started

We're going to use two of the four instances of the Etherspot SDK as "Payment Hubs".

Payment Hubs are nothing more than instances of the Etherspot SDK, but exist purely to hold funds for cross-chain transfer.

For this guide, we're going to transfer ETH from the Ropsten network to the Sokol network. To achieve this, we're going to send ETH from our user wallet to the Ropsten Payment Hub (which itself is an instance of the Etherspot SDK), and the Sokol Payment Hub (which, again, is another instance of the Etherspot SDK) will send the transferred amount back to the user's wallet on the Sokol network.

We're going to assume that we're working with the following definitions:

/**
* Payment Hubs
* - Network: Ropsten
* - const ropstenEtherspotPaymentHub
*
* - Network: Sokol
* - const sokolEtherspotPaymentHub
*
* User Wallets
* - Network: Ropsten
* - const ropstenEtherspotUser
*
* - Network: Sokol
* - const sokolEtherspotUser
*/

Ensure Payment Hubs are funded

For this process to work, we need to make sure that the Payment Hubs have enough liquidity in them to facilitate the bridge transfer. We're going to be working with the p2pDepositAddress from the Payment Hub Etherspot SDKs. We can get this address as follows:

const { p2pDepositAddress } = ropstenEtherspotPaymentHub.state;

The p2pDepositAddress exists purely to provide liquidity for other operations. Your address is unaffected.

We now need to add Test ETH from the Ropsten Faucet. Please follow the instructions on the Ropsten Faucet website to receive Test ETH to the p2pDepositAddress.

Once you have received the Test ETH to the p2pDepositAddress in the ropstenEtherspotPaymentHub instance, we need to perform the same set of operations for the Sokol Payment Hub Etherspot SDK.

const { p2pDepositAddress } = sokolEtherspotPaymentHub.state;

We now need to add Test ETH from the Sokol Faucet. Please follow the instructions on the Sokol Faucet website to receive Test ETH to the p2pDepositAddress.

Once both p2pDepositAddress from Sokol and Ropsten are funded with Test ETH from their respective faucets, we're ready to move on.

Add liquidity to the Payment Hubs

Now that our Payment Hubs are funded with Test ETH to their respective p2pDepositAddress, the next step is to update the Payment Hubs with the amount of liquidity we wish to provide from our respective p2pDepositAddress.

/**
* We're going to add 1 ETH of liquidity to the
* Ropsten Payment Hub which is taken from the
* p2pDepositAddress.
*/ 
await ropstenHubSdk.updatePaymentHub({
  liquidity: ethers.utils.parseEther(1), // ETH amount
})
.catch(console.error);

/**
* We're going to add 1 ETH of liquidity to the
* Sokol Payment Hub which is taken from the
* p2pDepositAddress.
*/ 
await sokolHubSdk.updatePaymentHub({
  liquidity: ethers.utils.parseEther(1), // ETH amount
})
.catch(console.error);

Please ensure that the p2pDepositAddress has received enough native tokens (in this case, ETH) before adding liquidity using the code above.

Once we've completed the liquidity addition operation, we're ready to move on.

Activate the Payment Hub Bridge

In order to allow transfer from one chain to another, we need to activate the Payment Hub Bridge using the destination Payment Hub SDK instance. Here's how to achieve this:

import { NetworkNames } from 'etherspot';

await sokolEtherspotPaymentHub
 .activatePaymentHubBridge({
   acceptedNetworkName: "ropsten" as NetworkNames,
 })
 .catch(console.error);

Once the above step is completed, the destination Sokol Payment Hub bridged with the Ropsten network name and you're ready to move on to the next step.

Exchanging with the Payment Hubs

From our Ropsten user wallet, we first need to transfer the amount of ETH that we want to exchange to ETH on Sokol to our Ropsten user wallet's p2pDepositAddress. We will do this in the normal way that we usually send Transactions.

Before we continue, let's clear the Ropsten Etherspot SDK Transaction Batch queue. We're keeping the house clean ๐Ÿงน

await ropstenEtherspotSdk.clearGatewayBatch();

We're going to perform a series of steps to:

  1. Add the transaction to the batch

  2. Estimate the gas required to perform this transaction

  3. Send the batch to Etherspot to be processed

/**
* Step 1: Add the transaction (which instructs the DAI
* contract to perform a transfer to the Token Bridge
* contract address) to a clean "batch" of transactions.
*
* Note: You can batch many transactions together and
* submit them as one request for a more gas-efficient
* operation. Here, we're just adding 1 transaction to
* this batch.
*/
const batchResponse = await ropstenEtherspotSdk
  .batchExecuteAccountTransaction({
    to: ropstenEtherspotSdk.state.p2pPaymentDepositAddress,  // your wallet linked p2pDepositAdrress
    ethers.utils.parseEther(value), // value that you wish to transfer.
  })
  .catch(console.error);

/**
* Step 2: Estimate the gas required to perform this
* operation. This is useful for presenting to users
* and allowing them to make a final decision.
*/
const estimateResponse = await ropstenEtherspotSdk
  .estimateGatewayBatch()
  .catch(console.error);

/**
* Step 3: Finally, send this batch to Etherspot for
* processing. We'll manage the transaction, queuing,
* retries and endevour to do whatever it takes to
* get this transaction on the chosen blockchain.
*/
const submissionResponse = await ropstenEtherspotSdk
  .submitGatewayBatch()
  .catch(console.error);

Make sure to transfer native tokens less than the available liquidity on the Payment Hubs.

Once the above batch transaction has been confirmed, we need to perform two steps:

  1. Call the updatePaymentHubDeposit method on our users Ropsten Etherspot SDK with the reference to the Ropsten Payment Hub, and the amount we wish to make available from our p2pDepositAddress to the Ropsten Payment Hub

  2. Call the transferPaymentHubDeposit method on our users Ropsten Etherspot SDK, which will instruct the Payment Hub to move the funds from one Payment Hub to the destination Payment Hub.

const exchangeAmount = ethers.utils.parseEther(valueInEth);

await ropstenEtherspotSdk.updatePaymentHubDeposit({
    hub: ropstenEtherspotPaymentHub.state.accountAddress,
    totalAmount: exchangeAmount
}).catch(console.error);

await ropstenEtherspotSdk.transferPaymentHubDeposit({
    hub: ropstenEtherspotPaymentHub.state.accountAddress,
    targetHub: sokolEtherspotPaymentHub.state.accountAddress,
    targetNetworkName: "sokol" as NetworkNames,
    value: exchangeAmount,
}).catch(console.error);

Once the above has been completed, the internal ledger operations of the Payment Hub mechanisms will move the available liquidity from the source Payment Hub on Ropsten to the destination Payment Hub on Sokol. We're now ready to withdraw the funds on the destination Payment Hub on Sokol.

Withdraw from the destination Payment Hub

To be able to withdraw the above exchangeAmount from the destination Sokol Payment Hub, we need to perform a few final steps.

We're now working primarily with the Sokol Payment Hub Etherspot SDK and the Sokol User Etherspot SDK.

First, we need to instruct the Sokol User Etherspot SDK instance that we are going to make a withdrawal by calling the updatePaymentHubDeposit method on the Sokol Etherspot User's SDK instance.

/**
* Note: Setting `totalAmount` to 0 instructs the
* PaymentHub that we want to make a withdrawal.
*/
await sokolEtherspotUser.updatePaymentHubDeposit({
  hub: ropstenEtherspotPaymentHub.state.accountAddress,
  totalAmount: 0, // See note above.
}).catch(console.error);

Internally a new hash has been created, which we will later find and sign to allow the withdrawal to take place. This process allows Etherspot to make the necessary liquidity checks before allowing the withdrawal to take place.

The next step is to find the transaction hash which needs to be signed from the Sokol Payment Hub. We'll first retrieve a list of uncommitted Payment Hub transaction items.

const uncommittedPaymentChannels = await sokolEtherspotPaymentHub
  .getP2PPaymentChannels({
    uncommittedOnly: true, // Filter to return uncommitted Payment channels by the receiver
  })
  .catch(console.error);

We then need to find the transaction hash to be signed. For the purposes of this guide, we're going to assume that there is just one uncommitted Payment Channel returned from the getP2PPaymentChannels method call.

const paymentHubChannel = uncommittedPaymentChannels.items[0];

Once we have this information, we're going to perform a basic validation check to check two things:

  1. That the paymentHubChannel.state is "Opened"

  2. That the sokolEtherspotUser.state.accountAddress and the paymentHubChannel.recipient are the same

let paymentChannelHash = null;

if (
  paymentHubChannel.state == "Opened" &&
  sokolEtherspotUser.state.accountAddress === paymentHubChannel.recipient
) {
  paymentChannelHash = paymentHubChannel.hash;
}

Providing that the two above validation points are true, we can sign the Payment Channel hash and commit the Payment Channel.

await sokolEtherspotPaymentHub
  .signP2PPaymentChannel({
    paymentChannelHash,
  })
  .catch(console.error);
    
// `paymentChannelHash` has now been signed by the Payment Hub

Committing the Payment Channel with with the signed hash from the previous code example moves the exchangeAmount to the final destination address.

/*
* Remember to clear your batch and keep the house clean!
*/
await sokolEtherspotUser.clearGatewayBatch();

/**
* Next, commit the Payment Channel. The 
* batchCommitP2PPaymentChannel takes an object with two
* properties:
* - paymentChannelHash: the previously signed Payment channel hash
* - deposit: 
* - - true: the exchange amount is transferred to the p2pDepositAddress.
* - - false: the exchange amount is transferred to the accountAddress
*/
await sokolEtherspotUser.batchCommitP2PPaymentChannel({
  paymentChannelHash,
  deposit: false, // See notes above
}).catch(console.error);

/**
* Next, we estimate the cost of the transaction...
*/
await sokolEtherspotUser
 .estimateGatewayBatch();

/**
* And finally we submit this to the ETherspot Gateway.
*/
await sokolEtherspotUser
  .submitGatewayBatch();

Once you have finished making one or more transactions against the hubs, we are going to commit the Payment Channels that was created by the sender Payment Hub. This is the last step to be completed with the Payment Hub transactions as there could be many transfers between the Etherspot User SDK and the Payment Hub. The next step will total-up the amount transferred so that just a single, minimal gas fee is paid.

To commit the Payment Channel, we need to get the hash generated for the Payment Channel that was previously created. This can be obtained by calling getP2PPaymentChannels on the Etherspot SDK.

Firstly, retrieve a list of uncommitted Payment Hub transaction items.

/**
* uncommittedOnly: true - Filter to return uncommitted 
* Payment Channels.
*/
const uncommittedPaymentChannels = await ropstenEtherspotPaymentHub
  .getP2PPaymentChannels({
    uncommittedOnly: true, // See note above.
  })
  .catch(console.error);

For the purposes of this guide, we're going to assume that there is just one uncommitted Payment Channel returned from the getP2PPaymentChannels method call.

const paymentHubChannel = uncommittedPaymentChannels.items[0];

Once we have this information, we're going to perform a basic validation check to check two things:

  1. That the paymentHubChannel.state is "Signed"

  2. That the ropstenEtherspotUser.state.accountAddress and the paymentHubChannel.recipient are the same

let paymentChannelHash = null;

if (
  paymentHubChannel.state == "Signed" &&
  ropstenEtherspotUser.state.accountAddress === paymentHubChannel.recipient
) {
  paymentChannelHash = paymentHubChannel.hash;
}

Providing that the two above validation points are true, we can commit the Payment Channel to receive the total amount of funds transferred from your Etherspot SDK p2pDepositAddress to sender's Payment Hub p2pDepositAddress.

/*
* Remember to clear your batch and keep the house clean!
*/
await ropstenEtherspotUser.clearGatewayBatch();

/**
* Next, commit the Payment Channel. The 
* batchCommitP2PPaymentChannel takes an object with two
* properties:
* - paymentChannelHash: the previously signed Payment channel hash
* - deposit: 
* - - true: the exchange amount is transferred to the p2pDepositAddress.
* - - false: the exchange amount is transferred to the accountAddress
*/
await ropstenEtherspotUser.batchCommitP2PPaymentChannel({
  paymentChannelHash,
  deposit: true, // See notes above
}).catch(console.error);

/**
* Next, we estimate the cost of the transaction...
*/
await ropstenEtherspotUser
 .estimateGatewayBatch();

/**
* And finally we submit this to the ETherspot Gateway.
*/
await ropstenEtherspotUser
  .submitGatewayBatch();

๐ŸŽ‰ Finished!

Last updated