This guide provides step-by-step instructions on how to deploy your custom EVM protocol.
Prerequisites
Before deployment, ensure you are familiar with payable functions in Solidity and have initiated a Hardhat project.
Package Installation
To streamline development, start by installing the Entangle Labs UIP Contracts NPM package. It includes ready-to-use code and interfaces for deployment.
npm install @entangle-labs/uip-contracts
Deployment Steps
1
Identify the Endpoint Contract Address
Obtain the endpoint contract address for the chain where you plan to deploy your protocol. You can find endpoint addresses for each connected chain and import them from the NPM package installed in the previous step. They are located in the following folder:
@entangle-labs/uip-contracts/addresses/testnet/
2
Import Required Interfaces and Libraries
Ensure the following imports are included in your contract:
import {IEndpoint, TransmitterParams} from "@entangle-labs/uip-contracts/contracts/interfaces/endpoint/IEndpoint.sol";
import "@entangle-labs/uip-contracts/contracts/lib/SelectorLib.sol";
3
Create a Default Selector Variable
To interact with the Endpoint properly, you need to send message with correct selector to execute its “propose” function. Make sure it's included in your contract like so:
bytes4 public constant DEFAULT_SELECTOR = bytes4(keccak256("execute(bytes calldata)"));
4
Create a Payable Function
Develop a payable function that interacts with the endpoint. For example, sending a message to the endpoint requires calling the propose function using the IEndpoint interface:
SelectorLib.encodeDefaultSelector(): A default selector for the execute function in the endpoint contract (this value should remain unchanged).
encodedParams: Encoded parameters such as blockFinalizationOption and customGasLimit inbyte format (details on creating these parameters are provided below).
destAddress: The address of the recipient on the destination chain.
Payload (abi.encode(...)): A combination of the encoded message and message sender inbytes format. This payload can carry any data in bytes format.
Configuring Encoded Parameters
The encodedParams parameter includes transmitter-related settings. These settings currently support two fields:
blockFinalizationOption: This field, defines the amount of blocks to wait before transmitting the message to UIP. Note that increasing the amount of blocks to wait also inceases the message transmission time. Currently there are several available options, such as:
0: Standard Finalization - This option ensures full finalization.
1: Fast Finalization - The default option, it is a safe shortened version of the standard finalization option.
2: Unsafe Finalization - This options does not have any finalization.
customGasLimit: The maximum gas allowed for executing the protocol contract on the destination chain.
To pack these parameters into a “bytes” type variable, use the abi.encodePacked() function and pass in the TransmitterParams struct fields, as shown in the example below.
For up-to-date parameter definitions please refer to the latest implementation of the TransmitterParamsLib.
Receiver Contract Requirements
The receiving contract must implement the MessageReceiver interface, which you can find in @entagle-labs/uip-contracts npm package — installed in the Package Installation step — specifically the execute function:
function execute(bytes calldata data) external override payable
The execute function processes the received payload. Note that the Endpoint contract should have access control to this function. Make sure that your contract imports MessageReceiver and inherits from it, for example:
import {MessageReceiver} from "@entangle-labs/uip-contracts/contracts/MessageReceiver.sol";
contract ExampleProtocol is MessageReceiver
Below, we provide the MessageReceiver code for convenience. Please refer to the package for the most up-to-date version.
MessageReceiver Code
abstract contract MessageReceiver {
/**
* @dev Executes a function call from a specified source chain and address.
*
* This external function is intended to be used for executing cross-chain
* calls with the provided payload. It requires to be overridden by child contracts to execute the command
* @param data The data payload to be sent along with the function call.
*
*/
function execute(bytes calldata data) external payable virtual {}
function _decode(
bytes calldata data
)
internal
virtual
returns (
uint256 sourceChainId,
bytes memory senderAddr,
bytes memory payload
)
{
(sourceChainId, senderAddr, payload) = abi.decode(
data,
(uint256, bytes, bytes)
);
}
}
Troubleshooting
This section provides solutions for common issues you may encounter while deploying the protocol
Deployment Failure
Check the signer account balance: Ensure the account has enough native currency on the target blockchain for deployment.
Verify the credentials: Double-check that you've correctly pasted the mnemonic phrase or private key in the hardhat.config.ts file under the appropriate chain section.
Compile before deploying: Run npx hardhat compile before deployment. If it fails, thoroughly check your contract for compilation errors.
Sending Message Failure
Check the signer account balance: Ensure the account has sufficient native currency for transactions.
Verify the installed package version: Make sure you have the latest version of the @entangle-labs/uip-contracts npm package installed. To check the latest version, visit the official npm page.
Confirm function signature compliance: Ensure your contract correctly follows the propose function signature in the Endpoint contract:
NOT_INITIALIZED: This indicates that the message was not proposed by the Endpoint on the source chain. Verify that the Endpoint address in your contract matches the official Endpoint address for your source chain.
PROTOCOL_FAILED: This occurs when the destination protocol is incompatible with the destination Endpoint. Ensure that the destination protocol implements the MessageReceiver interface (as described in the Receiver Contract Requirements section).
UNDERESTIMATED: This status means that the source transaction’s msg.value was too low to cover gas costs on the destination chain. Try resending the message with a higher msg.value.
OUT_OF_GAS: This means that the customGasLimit in the source transaction was too low, causing the execution on the destination chain to consume more gas than provided. Try resending the message with a higher customGasLimit.
Notes
Payment: All transactions use the native currency of the chain you're working on. Themsg.value sent during the propose call is considered the combined delivery fee.
References: Ensure all configurations adhere to the latest documentation and libraries provided in the @entangle-labs/uip-contracts package.
By following these steps, you can successfully deploy your custom EVM protocol across multiple chains. If you encounter issues, refer to the Troubleshooting section of this guide and the How to Debug Sent Messages guide or reach out to us through our contact form.