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 Testnet
Rocstar RPC: wss://rocstar.astar.network
Prerequisite
- A bi-directional HRMP channel between Pangolin and Rocstar should be opened in advance.
- sdk installed.
bashnpm install @darwinia/contracts-periphery
- For Goerli > Pangolin > Rocstar
- Rocstar supports XCM v3 and allow
DescendOrigin
orWithComputedOrigin
. - Rocstar has registered PRING token and set
unitPerSecond
for PRING. - 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.
plain textfee(in PRING) = unitPerSecond * weight / weight_per_second(1_000_000_000_000) * 1_000_000_000_000_000_000
- For Rocstar > Pangolin > Goerli
- The Sovereign Account of the XCM sender on Pangolin should have a sufficient balance.
- XCM fee (in PRING) = XCM weight * 1_000_000_000
- Get Market fee for LCMP(in PRING) from the fee market contract.
Total fee = XCM fee + Market fee for LCMP
plain textcurl -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 textEthereum: 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
solidity// 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
soliditypragma 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.javascriptconst 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 textParachain: 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.
typescriptimport { 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:
bashcurl -XPOST \ -d 'ethereum_contract=0xa4656066de65a7d66a8a60e1077e99c43c4490cdðereum_call=0x1003e2d20000000000000000000000000000000000000000000000000000000000000002' \ -H 'Content-Type:application/x-www-form-urlencoded' \ http://123.58.217.13:4567/pangolin/encode_transact_call
PolkadotXcm. send(dest, message)
Check the Result on Goerli
About three minutes later, you will see the "Receive_messages" event on the Inboundlane contract.
And then you can check your contract, for example: