Orb Labs
Search
K

Getting Started

Building an Earlybird smart contract application
In this section, we will walk through a simple setup using the Thunderbird V1 library for sending and receiving messages between chains. We will use an example PingPong application for the code samples.
  1. 1.
    Get endpoint and oracle-relayer pair addresses, and the chainID
Use addresses here for the fee collectors and oracle and relayer addresses: Broken link
  1. 2.
    Pick a library
For the purposes of this walkthrough, we will use the Thunderbird V1 library.
Implement the IRecsContractForThunderbirdReceiveModule interface
For our PingPong application, we implement the recommendations contract interface in the same contract with the other business logic. Also, we switch the recommended relayer based on the count of pings we have received.
A different implementation of the recommendations contract is demonstrated in the MockApp; it is a simple but sufficient implementation uses the same relayer for all messages.
Note the address of your newly deployed Recs Contract -- we'll need it in the next step
  1. 5.
    Configure the application
Now that we have all the information we need for our setup (endpoint address, library, oracle addresses, relayer addresses, recommendations contract addresses) we can configure the application to start sending and receiving messages. To do that, we will call the Earlybird endpoint’s setLibraryAndConfigs function
bytes memory sendModuleConfigs = abi.encode(
isSelfBroadcasting, // false - bool on whether the app is self broadcasting or not.
_sendingOracleAddress, // address of app selected oracle's fee collector
_sendingRelayerAddress // address of app selected relayer's fee collector
);
bytes memory receiveModuleConfigs = abi.encode(
_receivingOracle, // address of app's selected oracle
_receiveDefaultRelayer, // address of app's selected relayer
recsContract, // address of app's recs contract
emitMsgProofs, // true - bool indicating whether the protocol should broadcast msg proofs when they are submitted by an oracle.
directMsgsEnabled, // false - bool indicating whether the receive module should deliver messages to the app directly or not.
msgDeliveryPaused // false - bool indicating whether msg delivery is paused or not.
// If paused, the library will not accept new msg proofs or deliver messages to the app.
);
IEndpointFunctionsForApps(endpoint).setLibraryAndConfigs(
libraryName,
appConfigForSending,
appConfigForReceiving
);
Self-broadcasting, disabling message proofs, and using direct messages are advanced gas-optimization techniques we do not recommend for anyone unfamiliar with the protocol and the tradeoffs associated with these decisions, but you can find out more about these features in Rukh in-depth
  1. 6.
    Sending message
After successfully configuring the application, we can now send any messages we want to any chain via the endpoint’s sendMessage function on the IEndpoint interface. This requires 3 steps
  1. 1.
    Get the oracle and relayer fees
  2. 2.
    Get the protocol fees
  3. 3.
    Send the actual message along with the associated fees
Example code is shown below
bool isOrderedMsg = false;
bytes memory additionalParams = abi.encode(
defaultFeeToken,
isOrderedMsg,
500000 // max gas to use on the destionation chain for your app's logic
);
bytes memory _dst = abi.encode(_dstAddress);
// Check how much it costs to send messages with the default token
(bool isTokenAccepted, uint256 feeEstimated) =
IEndpointGetFunctions(endpoint)
.getSendingFeeEstimate(
address(this), // app address
_dstChainId,
_dst,
payload,
additionalParams
);
// Check that the fee token we indicated is accepted
require(isTokenAccepted, "Default fee token is not accepted by oracle and relayer");
// Get protocol fee and add it to token fees if
(
bool isProtocolFeeOn,
address protocolFeeToken,
uint256 protocolFeeAmount
) = IEndpointGetFunctions(endpoint)
.getProtocolFee(
address(this),
uint256(IEndpoint.ModuleType.SEND)
);
uint256 totalNativeTokenFee;
if (!isProtocolFeeOn) {
totalNativeTokenFee = _handleSendingAndProtocolFees(feeEstimated, 0, address(0));
} else {
totalNativeTokenFee = _handleSendingAndProtocolFees(
feeEstimated,
protocolFeeAmount,
protocolFeeToken
);
}
IEndpointFunctionsForApps(endpoint).sendMessage{
value: totalNativeTokenFee
}(_dstChainId, _dst, payload, additionalParams);
  1. 7.
    Receiving message
To receive messages, implement the IReceiver interface on the application. The endpoint on the destination chain will then call your receiveMsg function on that application to receive messages on the destination chain.
In the receiveMsg you can perform any checks you need on the the received data, if desired.