Ethereum <> Darwinia <> Parachain

In this guide, we will use Ethereum's testnet, Goerli, and Darwinia's testnet, Pangolin, as examples, as well as Astar’s testnet, Rocstar.
Pangolin RPC: Pangolin TestnetPangolin Testnet
Rocstar RPC: wss://rocstar.astar.network


  1. A bi-directional HRMP channel between Pangolin and Rocstar should be opened in advance.
  1. sdk installed.
    1. bash
      npm install @darwinia/contracts-periphery
  1. For Goerli > Pangolin > Rocstar
    1. Rocstar supports XCM v3 and allow DescendOrigin or WithComputedOrigin.
    2. Rocstar has registered PRING token and set unitPerSecond for PRING.
    3. The Sovereign account of the Pangolin XCM sender on Rocstar should have a sufficient balance. The Pangolin XCM sender in this example is the PangolinEndpoint.
      1. plain text
        fee(in PRING) = unitPerSecond * weight / weight_per_second(1_000_000_000_000) * 1_000_000_000_000_000_000
  1. For Rocstar > Pangolin > Goerli
    1. The Sovereign Account of the XCM sender on Pangolin should have a sufficient balance.
      1. Total fee = XCM fee + Market fee for LCMP
      2. XCM fee (in PRING) = XCM weight * 1_000_000_000
      3. Get Market fee for LCMP(in PRING) from the fee market contract.
        1. plain text
          curl -fsS https://pangolin-rpc.darwinia.network \ -H 'Content-Type: application/json' \ -d '{"id":1,"jsonrpc":"2.0","method":"eth_call","params":[{"data":"0x6673cb7a","gas":"0x5b8d80","to":"0x146aA5cd9a80398CE0854485Efae69A2DC38Df2b"},"latest"]}'
          Example response:{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000000008ac7230489e80000","id":1}. Then, convert result to decimal: 10000000000000000000

Goerli > Pangolin > Rocstar

plain text
Ethereum: Dapp > GoerliEndpoint — LCMP> Darwinia: PangolinEndpoint — XCMP> Parachain: Pallet's DispatchCall on Rocstar
An Endpoint is a cross-chain handle contract specific to a Dapp. Dapp developers are responsible for writing their own endpoints.

Create and Deploy GoerliEndpoint

// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@darwinia/contracts-periphery/ethereum/AbstractEthereumEndpoint.sol"; import "@darwinia/contracts-periphery/interfaces/ICrossChainFilter.sol"; contract GoerliEndpoint is AbstractEthereumEndpoint( 0x12225Fa4a20b13ccA0773E1D5f08BbC91f16f927, // outboundlane 0x527560d6a509ddE7828D598BF31Fc67DA50c8093 // fee market ) { function cross_chain_filter( uint32 bridgedChainPosition, uint32 bridgedLanePosition, address sourceAccount, bytes calldata payload ) external view returns (bool) { return true; } // Set darwinia endpoint as its remote endpoint. function setRemoteEndpoint(address _darwiniaEndpoint) external { _setRemoteEndpoint(_darwiniaEndpoint); } }
Here, we have implemented a GoerliEndpoint. The outboundlane and fee market contract addresses are constants provided by the Darwinia Messaging Service.
As a Dapp developer, you should implement the cross_chain_filter to ensure that your endpoint is only used in your specific circumstances.
The setRemoteEndpoint function informs your endpoint about the remote paired endpoint, in this case, the Pangolin2Endpoint.

Create and Deploy PangolinEndpoint

pragma solidity ^0.8.0; import "@darwinia/contracts-periphery/darwinia/AbstractDarwiniaEndpoint.sol"; import "@darwinia/contracts-periphery/interfaces/ICrossChainFilter.sol"; contract Pangolin2Endpoint is AbstractDarwiniaEndpoint( 0x2100, // _sendCallIndex 0xe520, // _darwiniaParaId 0x721F10bdE716FF44F596Afa2E8726aF197e6218E, // _toEthereumOutboundLane 0x9FbA8f0a0Bd6CbcB6283c042edc6b20894Be09c8 // _toEthereumFeeMarket ) { function cross_chain_filter( uint32 bridgedChainPosition, uint32 bridgedLanePosition, address sourceAccount, bytes calldata payload ) external view returns (bool) { return true; } }
The _sendCallIndex is the call index for PolkadotXcm.send on Pangolin2. _darwiniaParaId is Pangolin2's parachain ID. _toEthereumOutboundLane and _toEthereumFeeMarket are the addresses of Ethereum's outbound lane and fee market contracts, respectively. Dapp developers should use these addresses to ensure the correct routing of messages between Ethereum and Darwinia. The four parameters are constants provided by the Darwinia Messaging Service.

Remote call from Ethereum to a Parachain

After deploying the above two endpoints to Ethereum and Darwinia, respectively, you can code to make a remote call from Ethereum to a Parachain.
Now, let's call the System.RemarkWithEvent of a Parachain. The code below is written in JavaScript.
const fee = await goerliEndpoint.fee(); const tx = = await goerliEndpoint .dispatchOnParachain( "0x591f", // dest paraid, 0x591f is the paraid of Rocstar "0x0a07081234", // System.RemarkWithEvent(0x1234) "5000000000", // weight of the call "20000000000000000000", // fungible { value: fee } ); console.log(`https://goerli.etherscan.io/tx/${(await tx.wait()).transactionHash}`)
fee() is used to get the current cross-chain market fee from the fee market. You can provide a fee larger than the market fee. After deducting the market fee, the extra fee will be refunded to the original sender address. If the fee you provide is lower than the fee, your cross-chain transaction will fail.
To confirm that the transaction has been successfully executed, open the transaction hash on the Goerli explorer.
If everything is fine, the System.RemarkWithEvent call on Rocstar will be executed after approximately 20 minutes. You can confirm this by checking the remark event on https://polkadot.js.org/apps/?rpc=wss://rocstar.astar.network#/explorer.

Rocstar > Pangolin > Goerli

plain text
Parachain: call PolkadotXcm.send(dest, message) on Rocstar — XCMP> Darwinia: PangolinEndpoint.executeOnEthereum(target, call)LCMP> Ethereum: A target contract(callee)'s function
The target contract we used in this example.

Construct the Pangolin Call to transact

In the next step, PolkadotXcm.send(dest, message) will be executed on Rocstar. The 'message' parameter contains an XCM 'Transact' instruction, which is used to transact a Pangolin call. Therefore, we need to construct the Pangolin call first.
We can use the darwinia-js-sdk to generate the call data.
import { ethers } from "ethers"; import { hexlify } from "ethers/lib/utils"; import { pangolinTools } from "darwinia-js-sdk"; async function main(): Promise<void> { const provider = new ethers.providers.JsonRpcProvider( "https://pangolin-rpc.darwinia.network" ); const pangolinEndpoint = "0x5a07DB2bD2624DD2Bdd5093517048a0033A615b5"; const ethereumContract = "0xa4656066de65a7d66a8a60e1077e99c43c4490cd"; const ethereumCall = "0x1003e2d20000000000000000000000000000000000000000000000000000000000000002"; const callData = await pangolinTools.buildTransactCallForExecuteOnEthereum2( provider, pangolinEndpoint, 600000n, // gas limit of executing `executeOnEthereum` ethereumContract, ethereumCall ); console.debug(`transact call data: ${hexlify(callData)}`); } main().catch((err) => console.log(err));
Or, just use a web service to generate it:
curl -XPOST \ -d 'ethereum_contract=0xa4656066de65a7d66a8a60e1077e99c43c4490cd&ethereum_call=0x1003e2d20000000000000000000000000000000000000000000000000000000000000002' \ -H 'Content-Type:application/x-www-form-urlencoded' \

PolkadotXcm. send(dest, message)

Image without caption

Check the Result on Goerli

About three minutes later, you will see the "Receive_messages" event on the Inboundlane contract.
Image without caption
And then you can check your contract, for example:
Image without caption