Hello! Welcome to the final lesson in our module on the multi-chain ecosystem.
In our previous lessons, we've pieced together the on-chain components of a cross-chain application. We wrote sender and receiver contracts and manually deployed them to different testnets, painstakingly copying addresses and calling configuration functions in Remix. While this was great for understanding the mechanics, it's not a sustainable workflow for real-world development. It's slow, error-prone, and doesn't scale as complexity grows.
Today, we'll bridge the gap between manual experimentation and professional-grade development. Our goal is to automate this entire process. We will write a script to deploy and test the end-to-end cross-chain communication flow between two testnets. This will involve using a development framework to manage deployments, configure contracts, and verify the results, all from your local machine.
The Power of Scripted Deployments
Before we dive in, let's appreciate why scripting is a cornerstone of smart contract development:
- Repeatability: You can deploy your entire system to a local node, a testnet, or mainnet with a single command, ensuring consistency every time.
- Reliability: Scripts eliminate the manual errors that come from copying the wrong address or forgetting a configuration step.
- Configuration Management: Complex deployments often involve deploying multiple contracts and wiring them together. A script acts as executable documentation for this setup. For example, a script can deploy a token, then deploy a staking contract, and automatically pass the token's address to the staking contract's constructor.
- Testing: Scripts are essential for end-to-end and integration testing, allowing you to simulate user interactions with your deployed contracts on a live testnet.
Given your extensive background in front-end development, you're already familiar with the power of build scripts and automation tools like Webpack or Vite. We'll apply the same mindset here.
Tooling for Automation: Hardhat
Both Foundry and Hardhat offer powerful scripting capabilities. Foundry uses Solidity for its scripts, which is excellent for staying within a single language. However, for this lesson, we'll focus on Hardhat. Its scripting environment is based on JavaScript/TypeScript, making it a natural extension of your existing skills and a dominant tool in the ecosystem.
We'll use a tutorial that demonstrates a full cross-chain deployment and interaction flow using Hardhat and LayerZero.
The End-to-End Scripting Workflow
The process involves several distinct steps, which we will script one by one.
- Project Setup and Configuration
- Writing Deployment Scripts for Each Chain
- Scripting the Trust Configuration
- Writing an Interaction Script to Test the Flow
Let's start with the setup. The following tutorial will be our guide. We will use its code as a base, but I will ask you to make critical improvements, especially regarding security.
LayerZero Tutorial for Beginners
This tutorial, 'LayerZero Tutorial for Beginners' by Tim4l1f3, walks through setting up a Hardhat project to deploy and interact with a LayerZero contract across two testnets. We will use its code and scripting examples as our foundation.
Please read the 'Setup for the Tutorial' section. This will guide you on how to initialize a Hardhat project. You can follow the steps to create a project, but you don't need to implement the code just yet. The key takeaway is understanding the initial Hardhat project structure.
A core part of a multi-chain Hardhat project is the hardhat.config.js file. This is where you define the networks you'll be deploying to. For a cross-chain application, you'd configure at least two networks, providing their RPC URLs and the private key for the deployer account (safely loaded from an environment file like .env).
Here's an example of what that configuration looks like for deploying to Polygon's Mumbai and Fantom's testnet, as in the tutorial:
// hardhat.config.js
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config(); // To load environment variables
module.exports = {
solidity: "0.8.9",
networks: {
mumbai: {
url: process.env.MUMBAI_RPC_URL, // e.g., from Alchemy or Infura
accounts: [process.env.PRIVATE_KEY],
},
fantom_testnet: {
url: process.env.FANTOM_TESTNET_RPC_URL,
accounts: [process.env.PRIVATE_KEY],
},
},
};
1. Writing the Deployment Scripts
With the project configured, the next step is to write scripts to deploy our contract. A common pattern is to have one script per network. This keeps things clean and allows you to pass network-specific parameters, like the LayerZero Endpoint address, during deployment.
LayerZero Tutorial for Beginners
Let's examine the deployment scripts from the tutorial. Notice how two separate files are created, one for each testnet.
First, review the LayerZeroDemo1.sol contract code to understand what we're deploying. Then, carefully read the section 'Deploy the contract on different chains'. Focus on the structure of the two JavaScript deployment scripts and the command used to run them (npx hardhat run ... --network ...).
The core of a Hardhat deployment script is this pattern:
async function main() {
// 1. Get the contract factory
const ContractFactory = await hre.ethers.getContractFactory("MyContract");
// 2. Deploy the contract, passing constructor arguments
const contract = await ContractFactory.deploy("constructor_arg_1");
// 3. Wait for the deployment to be confirmed on the blockchain
await contract.deployed();
// 4. Log the address for future use
console.log("MyContract deployed to:", contract.address);
}
main().catch(/* error handling */);
You run these scripts from your terminal, specifying which network to use from your hardhat.config.js:npx hardhat run scripts/deploy_mumbai.js --network mumbainpx hardhat run scripts/deploy_fantom.js --network fantom_testnet
After running these, you will have two contract addresses, one on each chain.
2. Scripting the Trust Configuration
In our previous lesson, we stressed the importance of the trust relationship: the receiver contract must only accept messages from a specific, whitelisted sender address. The tutorial's contract (LayerZeroDemo1.sol) dangerously omits this check.
Let's fix this. A production-ready contract should look more like this:
// A more secure version of the receiver
contract SecureReceiver is ILayerZeroReceiver {
// ... endpoint setup ...
mapping(uint16 => bytes) public trustedRemotes; // chainId => remoteAddress
function setTrustedRemote(uint16 _remoteChainId, bytes calldata _remoteAddress) public onlyOwner {
trustedRemotes[_remoteChainId] = _remoteAddress;
}
function lzReceive(uint16 _srcChainId, bytes memory _srcAddress, /*...*/) external override {
// CRITICAL CHECKS
require(msg.sender == address(endpoint), "Only endpoint can call");
require(
keccak256(_srcAddress) == keccak256(trustedRemotes[_srcChainId]),
"Untrusted source"
);
// Now it's safe to process the payload...
}
}
After deploying our contracts, we need to call setTrustedRemote on each one to whitelist the other. This can also be scripted! You would create a new script, say configure.js, that does the following:
// A conceptual script: configure.js
async function main() {
const mumbaiAddress = "0x...address on Mumbai...";
const fantomAddress = "0x...address on Fantom...";
// Get a signer for Mumbai
const [deployer] = await hre.ethers.getSigners();
const mumbaiProvider = new hre.ethers.providers.JsonRpcProvider(process.env.MUMBAI_RPC_URL);
const mumbaiWallet = new hre.ethers.Wallet(process.env.PRIVATE_KEY, mumbaiProvider);
// Attach to the Mumbai contract and set its trusted remote
const mumbaiContract = await hre.ethers.getContractAt("SecureReceiver", mumbaiAddress, mumbaiWallet);
const remoteFantomAddressBytes = hre.ethers.utils.defaultAbiCoder.encode(['address'],[fantomAddress]);
let tx = await mumbaiContract.setTrustedRemote(10109, remoteFantomAddressBytes); // 10109 is Fantom's LZ ID
await tx.wait();
console.log("Set trusted remote on Mumbai contract.");
// Now do the reverse for Fantom...
}
Note: LayerZero packs remote addresses as bytes, requiring the defaultAbiCoder.encode step.
This configuration step is vital for security and is a perfect candidate for automation.
3. Writing the Interaction Script
With the contracts deployed and configured, the final step is to test the end-to-end flow. We'll write one more script to call the sendMsg function on the source chain and another to check the result on the destination chain.
LayerZero Tutorial for Beginners
The tutorial demonstrates this testing phase well. It shows how to attach to a deployed contract and call its functions.
Read the 'Test' section. It contains two scripts: A script for the Fantom testnet that estimates fees and calls sendMsg. A script for Mumbai that reads the state (messageCount, message) to verify the message was received. This demonstrates the complete, scripted test cycle.
The key function here is hre.ethers.getContractAt() (or .attach() in older versions), which allows you to interact with a contract you didn't just deploy, simply by providing its name and address.
Test your understanding!
You've successfully run your deployment scripts for a Sender.sol contract on Goerli and a Receiver.sol on Sepolia. You now need to write a script to call setTrustedRemote(goerliSenderAddress) on the Sepolia Receiver contract.
Which Hardhat command would you use to execute this script (configure.js) correctly?
Show answer
You would use npx hardhat run scripts/configure.js --network sepolia.
The key is specifying --network sepolia. This tells Hardhat to connect to the Sepolia RPC endpoint defined in your hardhat.config.js and use it to send the transaction to the Receiver contract on the Sepolia network.
An Alternative with Foundry
As mentioned, this entire workflow is also achievable with Foundry. The approach is conceptually identical, but you write your scripts in Solidity.
If you're interested in seeing what this looks like, the following video provides a very thorough demonstration. It uses a series of Foundry scripts to deploy and configure a cross-chain token using Chainlink CCIP. You are not required to watch it, but it's a great resource for further learning.
Deploy a Cross-Chain Token (CCT) | Chainlink and Solidity Bootcamp
This Chainlink Bootcamp video shows an advanced, multi-step deployment and configuration process using Foundry scripts for a CCIP-enabled token.
If you are curious, you can watch the section 'Local Development Environment Setup with Foundry' (from 42:43 to 1:01:46). Notice how the presenter uses a series of forge script commands, each targeting a specific part of the setup (deploying tokens, deploying pools, setting admin roles, etc.). This demonstrates a powerful, modular approach to scripting complex deployments.
Conclusion
You have now mastered the final and most critical skill for multi-chain development: automating the deployment and testing lifecycle. By replacing manual steps with scripts, you dramatically increase your speed, reduce errors, and create a professional, maintainable development process.
Key Takeaways:
- Scripting is essential for reliable, repeatable, and complex smart contract deployments.
- Hardhat provides a powerful JavaScript/TypeScript environment for scripting, which is a natural fit for web developers.
- The typical cross-chain scripting flow involves:
- Configuring multiple networks in
hardhat.config.js. - Writing separate deployment scripts for each chain.
- Writing a configuration script to set trust relationships (e.g.,
setTrustedRemote). - Writing interaction scripts to perform an end-to-end test of the cross-chain functionality.
- Configuring multiple networks in
- The key Hardhat functions are
hre.ethers.getContractFactory,deploy, andhre.ethers.getContractAt.
Preview of the Next Module:
This lesson serves as a perfect bridge to our next module, "Advanced On-Chain Optimization & Operations." We will start by looking at advanced gas optimization techniques. Our work today on authoring a reusable deployment script (Module 7, Outcome 3) is the first step toward professionalizing your on-chain operations. Well done
Can't find a good explanation? Sign up and we'll make it for you
Sign up