Skip to main content

Hello World

Send a string from one chain to another. One contract, two deployments, under 30 lines of Solidity.


Prerequisites


Step 1: Create the Project

mkdir hello-via && cd hello-via
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox
npm install @openzeppelin/contracts
npx hardhat init

Select Create a TypeScript project when prompted. Accept the defaults.


Step 2: Add the VIA Contracts

Copy the VIA Labs base contract and interfaces into your project. Create the folder and files:

mkdir contracts/via

Copy all 5 files from the Contract Source page into contracts/via/:

  • ViaIntegrationV1.sol
  • IViaGatewayV1.sol
  • IViaIntegrationV1.sol
  • IFeeCollector.sol
  • IGasCollector.sol

Step 3: Write the Contract

Create contracts/HelloVIA.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "./via/ViaIntegrationV1.sol";

contract HelloVIA is ViaIntegrationV1 {
string public lastMessage;
uint64 public lastSourceChain;

event MessageReceived(uint64 sourceChainId, string message);

constructor() ViaIntegrationV1(msg.sender) {}

function sendMessage(
uint64 destChainId,
string calldata message
) external payable returns (uint256) {
return messageSend(destChainId, abi.encode(message), 1);
}

function messageProcess(
uint256,
uint64 sourceChainId,
bytes32,
bytes32,
bytes memory onChainData,
bytes memory,
uint256
) internal override {
string memory message = abi.decode(onChainData, (string));
lastMessage = message;
lastSourceChain = sourceChainId;
emit MessageReceived(sourceChainId, message);
}
}

What this does:

  • sendMessage() — encodes a string and sends it to another chain via messageSend()
  • messageProcess() — receives the message on the destination chain, decodes it, stores it

Step 4: Configure Hardhat

Replace hardhat.config.ts with:

import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import * as dotenv from "dotenv";
dotenv.config();

const PRIVATE_KEY = process.env.PRIVATE_KEY || "";

const config: HardhatUserConfig = {
solidity: "0.8.17",
networks: {
sepolia: {
url: process.env.SEPOLIA_RPC_URL || "https://rpc.sepolia.org",
accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
},
amoy: {
url: process.env.AMOY_RPC_URL || "https://rpc-amoy.polygon.technology",
accounts: PRIVATE_KEY ? [PRIVATE_KEY] : [],
},
},
};

export default config;

Install dotenv:

npm install --save-dev dotenv

Create .env:

PRIVATE_KEY=your_wallet_private_key_here
SEPOLIA_RPC_URL=https://rpc.sepolia.org
AMOY_RPC_URL=https://rpc-amoy.polygon.technology
danger

Never commit .env to git. Add it to .gitignore.


Step 5: Write the Deploy Script

Create scripts/deploy.ts:

import { ethers } from "hardhat";

async function main() {
const HelloVIA = await ethers.getContractFactory("HelloVIA");
const contract = await HelloVIA.deploy();
await contract.waitForDeployment();
const address = await contract.getAddress();
console.log("HelloVIA deployed to:", address);
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

Step 6: Deploy to Both Chains

npx hardhat run scripts/deploy.ts --network sepolia

Save the output address. Then:

npx hardhat run scripts/deploy.ts --network amoy

Save that address too. You now have two contract addresses:

SEPOLIA_CONTRACT=0x...   ← from first deploy
AMOY_CONTRACT=0x... ← from second deploy

Step 7: Configure the Contracts

Create scripts/configure.ts:

import { ethers } from "hardhat";

// ---- FILL THESE IN ----
const CONTRACT_ADDRESS = ""; // deployed HelloVIA address on THIS chain
const GATEWAY_ADDRESS = ""; // VIA Gateway address on THIS chain
const REMOTE_CHAIN_ID = 0; // chain ID of the OTHER chain
const REMOTE_CONTRACT_ADDRESS = ""; // deployed HelloVIA address on the OTHER chain
// ------------------------

async function main() {
const contract = await ethers.getContractAt("HelloVIA", CONTRACT_ADDRESS);

// 1. Connect to the VIA Gateway
console.log("Setting gateway...");
const tx1 = await contract.setMessageGateway(GATEWAY_ADDRESS);
await tx1.wait();
console.log("Gateway set.");

// 2. Tell the contract where its peer lives on the other chain
console.log("Setting endpoint...");
const tx2 = await contract.setMessageEndpoints(
[REMOTE_CHAIN_ID],
[ethers.zeroPadValue(REMOTE_CONTRACT_ADDRESS, 32)]
);
await tx2.wait();
console.log("Endpoint set. Configuration complete.");
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});

Run it on Sepolia (fill in addresses first):

npx hardhat run scripts/configure.ts --network sepolia

Then update the constants for the Amoy side and run:

npx hardhat run scripts/configure.ts --network amoy
Gateway Addresses

VIA Gateway testnet addresses are available on the Supported Networks page. Testnet gateways are being deployed — check the page for the latest status.


Step 8: Send a Message

Create scripts/send.ts:

import { ethers } from "hardhat";

const CONTRACT_ADDRESS = ""; // your HelloVIA on Sepolia
const DEST_CHAIN_ID = 80002; // Amoy

async function main() {
const contract = await ethers.getContractAt("HelloVIA", CONTRACT_ADDRESS);

console.log("Sending message...");
const tx = await contract.sendMessage(
DEST_CHAIN_ID,
"Hello from Sepolia!",
{ value: ethers.parseEther("0.001") }
);
await tx.wait();
console.log("Message sent! TX:", tx.hash);
console.log("Wait 1-5 minutes for cross-chain delivery.");
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
npx hardhat run scripts/send.ts --network sepolia

Step 9: Verify on Destination

Create scripts/read.ts:

import { ethers } from "hardhat";

const CONTRACT_ADDRESS = ""; // your HelloVIA on Amoy

async function main() {
const contract = await ethers.getContractAt("HelloVIA", CONTRACT_ADDRESS);
const message = await contract.lastMessage();
const sourceChain = await contract.lastSourceChain();
console.log("Last message:", message);
console.log("From chain:", sourceChain.toString());
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
npx hardhat run scripts/read.ts --network amoy

Expected output after delivery:

Last message: Hello from Sepolia!
From chain: 11155111

Project Structure

When you're done, your project looks like:

hello-via/
├── contracts/
│ ├── via/
│ │ ├── ViaIntegrationV1.sol
│ │ ├── IViaGatewayV1.sol
│ │ ├── IViaIntegrationV1.sol
│ │ ├── IFeeCollector.sol
│ │ └── IGasCollector.sol
│ └── HelloVIA.sol
├── scripts/
│ ├── deploy.ts
│ ├── configure.ts
│ ├── send.ts
│ └── read.ts
├── .env
├── hardhat.config.ts
└── package.json

Next Steps