Imagine sending your crypto tokens from Ethereum to Cosmos or Solana, like mailing a package across countries. Cross-chain bridges make this possible, connecting different blockchains for seamless asset transfers. This beginner-friendly tutorial shows you how to build a simple cross-chain bridge for Ethereum, Cosmos, and Solana using Solidity and Rust. We’ll create a system to lock tokens on one chain and mint them on another. Let’s bridge the blockchain world!

What Is a Cross-Chain Bridge?
A cross-chain bridge is like a ferry between blockchains, letting assets (e.g., tokens) move from one chain to another. For example, you lock 100 tokens on Ethereum, and the bridge mints 100 equivalent tokens on Solana. This is key for interoperability in DeFi, NFTs, and more.
Key features of a bridge include:
- Asset Transfer: Move tokens or data across chains.
- Security: Ensure funds are safe during transit.
- Decentralization: Often use validators or relayers to verify transfers.
Our bridge will lock tokens on Ethereum and mint them on Cosmos or Solana, using a simplified validator model.
Why Build a Cross-Chain Bridge?
In 2025, blockchains like Ethereum, Cosmos, and Solana thrive, but they don’t naturally “talk” to each other. Building a bridge lets you:
- Enable Interoperability: Connect ecosystems for better DeFi and NFT experiences.
- Learn Blockchain Tech: Master multi-chain development with Solidity and Rust.
- Tap into Web3: Join the growing trend of cross-chain apps.
This tutorial will teach you how to transfer tokens securely across chains.

Tools You’ll Need
To build the bridge, gather these tools:
- Node.js: For JavaScript execution. Download from nodejs.org.
- Hardhat: For Ethereum smart contracts. Install via
npm install --save-dev hardhat
. - Rust: For Solana and Cosmos contracts. Install from rust-lang.org.
- Anchor: For Solana development. Install via
cargo install --git https://github.com/coral-xyz/anchor anchor-cli
. - CosmWasm: For Cosmos contracts. Install via cosmwasm docs.
- MetaMask: For Ethereum wallet. Get it at metamask.io.
- Phantom: For Solana wallet. Get it at phantom.app.
These tools cover development across Ethereum, Cosmos, and Solana.
Step-by-Step: Building a Cross-Chain Bridge
We’ll create a bridge to transfer an ERC-20 token from Ethereum to equivalent tokens on Cosmos and Solana. The Ethereum contract locks tokens, while Cosmos and Solana contracts mint them. A simple off-chain validator script will relay messages. Let’s dive in!
Step 1: Set Up the Ethereum Contract
Create a Hardhat project for the Ethereum side:
mkdir cross-chain-bridge cd cross-chain-bridge npx hardhat npm install @openzeppelin/contracts
Create contracts/BridgeToken.sol
for the ERC-20 token:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract BridgeToken is ERC20 { constructor(uint256 initialSupply) ERC20("BridgeToken", "BRT") { _mint(msg.sender, initialSupply); } }
Create contracts/EthereumBridge.sol
to lock tokens:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract EthereumBridge is Ownable { IERC20 public token; mapping(address => uint256) public lockedTokens; event TokensLocked(address indexed user, uint256 amount, string destinationChain, string destinationAddress); constructor(address _token) Ownable(msg.sender) { token = IERC20(_token); } function lockTokens(uint256 amount, string memory destinationChain, string memory destinationAddress) external { require(amount > 0, "Amount must be greater than 0"); require(token.transferFrom(msg.sender, address(this), amount), "Transfer failed"); lockedTokens[msg.sender] += amount; emit TokensLocked(msg.sender, amount, destinationChain, destinationAddress); } }
This contract locks BRT tokens and emits an event with the destination chain (e.g., “Cosmos” or “Solana”) and address.
Step 2: Set Up the Cosmos Contract
Create a CosmWasm contract for Cosmos. Initialize a Rust project:
cargo new --lib cosmos-bridge cd cosmos-bridge
Update Cargo.toml
:
[package] name = "cosmos-bridge" version = "0.1.0" edition = "2021"
[lib]
crate-type = [“cdylib”, “rlib”]
[dependencies]
cosmwasm-std = { version = “1.5”, features = [“staking”] } cosmwasm-schema = “1.5” serde = { version = “1.0”, features = [“derive”] }
Create src/lib.rs
for the Cosmos bridge logic:
use cosmwasm_std::{ entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdResult, Uint128, }; use serde::{Deserialize, Serialize}; #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct InstantiateMsg { pub admin: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "snake_case")] pub enum ExecuteMsg { MintTokens { recipient: String, amount: Uint128 }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { Balance { address: String }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq)] pub struct BalanceResponse { pub balance: Uint128, } #[entry_point] pub fn instantiate( deps: DepsMut, _env: Env, info: MessageInfo, msg: InstantiateMsg, ) -> StdResult { deps.api.addr_validate(&msg.admin)?; Ok(Response::default()) } #[entry_point] pub fn execute( deps: DepsMut, _env: Env, info: MessageInfo, msg: ExecuteMsg, ) -> StdResult { match msg { ExecuteMsg::MintTokens { recipient, amount } => { deps.api.addr_validate(&recipient)?; // Simplified: In production, verify sender is validator Ok(Response::new() .add_attribute("action", "mint_tokens") .add_attribute("recipient", recipient) .add_attribute("amount", amount.to_string())) } } } #[entry_point] pub fn query(deps: Deps, _env: Env, msg: QueryMsg) -> StdResult { match msg { QueryMsg::Balance { address } => { // Simplified: Return dummy balance let balance = Uint128::from(0u128); to_binary(&BalanceResponse { balance }) } } }
This contract mints tokens on Cosmos when instructed by a validator. In production, add storage for balances and validator checks.
Step 3: Set Up the Solana Contract
Create a Solana program using Anchor:
anchor init solana-bridge cd solana-bridge
Update programs/solana-bridge/src/lib.rs
:
use anchor_lang::prelude::*; declare_id!("YOUR_PROGRAM_ID"); #[program] pub mod solana_bridge { use super::*; pub fn mint_tokens(ctx: Context, amount: u64, recipient: Pubkey) -> Result<()> { // Simplified: In production, verify validator msg!("Minting {} tokens to {}", amount, recipient); Ok(()) } } #[derive(Accounts)] pub struct MintTokens<'info> { #[account(mut)] pub signer: Signer<'info>, pub system_program: Program<'info, System>, }
Update Anchor.toml
for Solana devnet:
[provider] cluster = "devnet" wallet = "~/.config/solana/id.json"
[programs.devnet]
solana_bridge = “YOUR_PROGRAM_ID”
Build and deploy with anchor build
and anchor deploy
. This program mints tokens on Solana, with placeholder logic for validation.

Step 4: Deploy the Contracts
Ethereum: Create scripts/deploy.js
:
const hre = require("hardhat"); async function main() { const initialSupply = ethers.parseEther("1000000"); const BridgeToken = await hre.ethers.getContractFactory("BridgeToken"); const token = await BridgeToken.deploy(initialSupply); await token.deployed(); console.log("BridgeToken deployed to:", token.target); const EthereumBridge = await hre.ethers.getContractFactory("EthereumBridge"); const bridge = await EthereumBridge.deploy(token.target); await bridge.deployed(); console.log("EthereumBridge deployed to:", bridge.target); await token.approve(bridge.target, initialSupply); } main().catch((error) => { console.error(error); process.exitCode = 1); });
Deploy with npx hardhat run scripts/deploy.js --network sepolia
. Use Sepolia config from previous tutorials.
Cosmos: Compile and deploy using CosmWasm tools (see cosmwasm docs).
Solana: Deployed via Anchor. Note the program ID.
Step 5: Build a Validator Script
Create a Node.js script to listen for Ethereum’s TokensLocked
event and trigger minting on Cosmos or Solana. Create validator.js
:
const { ethers } = require("ethers"); const { AnchorProvider, Program, web3 } = require("@project-serum/anchor"); const ethProvider = new ethers.JsonRpcProvider("https://sepolia.infura.io/v3/YOUR_API_KEY"); const ethContractAddress = "YOUR_BRIDGE_ADDRESS"; const ethContractABI = [/* YOUR_ABI */]; async function main() { const ethContract = new ethers.Contract(ethContractAddress, ethContractABI, ethProvider); ethContract.on("TokensLocked", async (user, amount, destinationChain, destinationAddress) => { console.log(`Locked ${amount} tokens for ${user} to ${destinationChain}: ${destinationAddress}`); if (destinationChain === "Cosmos") { // Call Cosmos contract (simplified) console.log(`Minting ${amount} on Cosmos for ${destinationAddress}`); // Use CosmWasm client to call mint_tokens } else if (destinationChain === "Solana") { // Call Solana program const provider = new AnchorProvider( new web3.Connection("https://api.devnet.solana.com"), null, {} ); const program = new Program(/* IDL */, "YOUR_PROGRAM_ID", provider); await program.rpc.mintTokens(amount, destinationAddress, { accounts: { signer: provider.wallet.publicKey, systemProgram: web3.SystemProgram.programId, }, }); console.log(`Minted ${amount} on Solana for ${destinationAddress}`); } }); } main().catch(console.error);
Run with node validator.js
. This script listens for Ethereum events and triggers minting on the destination chain. In production, use a secure validator network.
Step 6: Test the Bridge
Test with MetaMask (Ethereum) and Phantom (Solana):
- Lock 100 BRT tokens on Ethereum, specifying “Solana” and a Solana address.
- Verify the validator script triggers minting on Solana.
- Repeat for Cosmos with a Cosmos address.
- Check balances on each chain.
Use testnets: Sepolia for Ethereum, Solana Devnet, and Cosmos testnet. Get test ETH from sepoliafaucet.com and Solana tokens from faucet.solana.com.

Benefits of Building Cross-Chain Bridges
Cross-chain bridges offer:
- Unified Ecosystems: Connect users across Ethereum, Cosmos, and Solana.
- Innovation: Enable new DeFi and NFT use cases.
- Scalability: Leverage each chain’s strengths (e.g., Solana’s speed).
Bridges are key to Web3’s interconnected future.
Challenges of Cross-Chain Bridges
Building bridges has risks:
- Security: Hacks like the $600M Ronin bridge exploit show vulnerabilities.
- Complexity: Coordinating multiple chains requires robust validation.
- Gas Costs: Ethereum transactions can be expensive.
Mitigate with audits, decentralized validators, and layer-2 solutions.
Tips for Developers Building Cross-Chain Bridges
To create a robust bridge:
- Audit Contracts: Use MythX or hire auditors.
- Secure Validators: Implement multi-signature or MPC validation.
- Test Extensively: Simulate attacks with Hardhat and Anchor.
- Learn from Pros: Study bridges like Wormhole or Cosmos IBC.
These practices ensure your bridge is secure and reliable.
Resources for Learning More
Deepen your cross-chain knowledge with these resources:
- Ethereum Docs: Smart contracts at ethereum.org.
- Cosmos Docs: CosmWasm at docs.cosmwasm.com.
- Solana Docs: Anchor at anchor-lang.com.
- Web3 Communities: Join discussions on Ethereum Stack Exchange.
Stay curious to master cross-chain development.
Conclusion
Cross-chain bridges unite blockchains, enabling seamless asset transfers across Ethereum, Cosmos, and Solana. By building a bridge with Solidity and Rust, you’ve learned how to lock and mint tokens across chains. Try adding multi-validator support or reverse transfers, and share your bridge ideas in the comments below!