Connect your blockchain to the Resolution Layer.
This boilerplate provides the complete infrastructure to connect GenLayer Intelligent Contracts with EVM chains (Base, Ethereum, etc.) via LayerZero V2. It enables any blockchain to offload complex, non-deterministic workβAI reasoning, web access, data verificationβto GenLayer and receive verified results.
- The Resolution Layer
- Architecture
- Repository Structure
- Key Contracts
- Prerequisites
- Deployment Guide
- Development & Debugging
- Troubleshooting
- License
Blockchains are powerful but blind. They cannot read news, verify events, or access the web. GenLayer solves this by acting as the Resolution Layer for the ecosystem.
- Your Chain (Backbone): Holds liquidity, users, and core logic.
- GenLayer (Brain): Handles intelligence, web data, and AI processing.
This bridge connects the two, allowing you to build "Intelligent dApps" without migrating your users or liquidity.
The bridge implements a Hub-and-Spoke model with ZKsync Era serving as the central hub for GenLayer's interactions with the wider EVM ecosystem via LayerZero.
graph TD
subgraph "The World"
Web["Web Data / APIs"]
AI["LLM Reasoning"]
end
subgraph "GenLayer (The Brain)"
IC["Intelligent Contract<br/>(Your Logic)"]
BR_GL["BridgeReceiver.py<br/>(Receiver)"]
BS_GL["BridgeSender.py<br/>(Outbox)"]
end
subgraph "Transport"
Service["Relay Service<br/>(Node.js)"]
BF["BridgeForwarder.sol<br/>(Hub - ZKsync Era)"]
BR_HUB["BridgeReceiver.sol<br/>(Hub - ZKsync Era)"]
LZ["LayerZero V2"]
end
subgraph "EVM Chain (The Backbone)"
dApp["Your dApp"]
BS_EVM["BridgeSender.sol"]
BR_EVM["BridgeReceiver.sol"]
end
dApp -.->|"0. Quote Fee"| BS_EVM
dApp -->|"1. Request Resolution (+Fees)"| BS_EVM
BS_EVM --> LZ
LZ -->|"2. Relay Message"| BR_HUB
BR_HUB -.->|"2a. Poll Hub"| Service
Service -->|"3. Deliver to Receiver"| BR_GL
BR_GL -->|"4. Dispatch via emit()"| IC
Web -->|"5. Web Data"| IC
AI -->|"6. AI Consensus"| IC
IC -->|"7. Send Result"| BS_GL
BS_GL -.->|"8. Poll Event"| Service
Service -.->|"8a. Quote Fee"| BF
Service -->|"9. Relay Result (+Fees)"| BF
BF --> LZ
LZ --> BR_EVM
BR_EVM -->|"10. Callback"| dApp
- Source IC calls
BridgeSender.send_message(target_chain_eid, target_contract, data). - Service polls
get_message_hashes()andget_message()on GenLayer. - Service calls
BridgeForwarder.quoteCallRemoteArbitrary()to determine the fee. - Service calls
BridgeForwarder.callRemoteArbitrary()on ZKsync Era (Hub) with the required native fee. - LayerZero delivers to
BridgeReceiveron destination chain (Target). - BridgeReceiver dispatches to target contract via
processBridgeMessage().
- dApp calls
BridgeSender.quoteSendToGenLayer()to get the fee. - dApp calls
BridgeSender.sendToGenLayer(targetContract, data, options)withmsg.value >= fee. - LayerZero delivers to
BridgeReceiver.solon ZKsync Era (Hub). - BridgeReceiver (Hub) stores message (not just event) for polling.
- Service polls
getPendingGenLayerMessages()on ZKsync Era (Hub). - Service calls
BridgeReceiver.receive_message()on GenLayer. - BridgeReceiver dispatches to target IC via
emit().process_bridge_message(). - Service calls
markMessageRelayed()on ZKsync Era (Hub).
This is a monorepo containing all components of the bridge:
- /smart-contracts: Solidity contracts for EVM chains (Hardhat).
- /intelligent-contracts: Python contracts for GenLayer.
- /service: Node.js relay service that polls and relays messages.
- /example: Complete bidirectional example with StringSender/StringReceiver.
| Contract | Chain | Purpose |
|---|---|---|
BridgeSender.py |
GenLayer | Stores outbound GLβEVM messages |
BridgeReceiver.py |
GenLayer | Receives EVMβGL messages, dispatches to target |
BridgeForwarder.sol |
ZKsync Era | Relays GLβEVM via LayerZero |
BridgeReceiver.sol |
ZKsync Era | Stores EVMβGL messages for polling |
BridgeSender.sol |
Base/EVM | Entry point for EVMβGL messages |
To bridge intelligence to your dApp, you need:
- Node.js: v18+ & npm: v9+
- GenLayer Studio: GenLayer Studio
- Wallet: A private key with testnet funds on:
- Base Sepolia (Example Target Chain)
- ZKsync Era Sepolia (Hub Chain)
Follow these steps to deploy your own instance of the bridge infrastructure.
# 1. Install Smart Contracts dependencies (EVM)
cd smart-contracts && npm install && cd ..
# 2. Install Bridge Service dependencies (Relayer)
cd service && npm install && cd ..Create your environment files.
Smart Contracts (.env)
cp smart-contracts/.env.example smart-contracts/.env
# EDIT: Add your PRIVATE_KEY and RPC URLsService (.env)
cp service/.env.example service/.env
# EDIT: Add your PRIVATE_KEY and GENLAYER_RPC_URL (e.g. https://studio.genlayer.com/api/rpc)Deploy the "mailbox" contracts to the EVM chains.
cd smart-contracts
# 1. Deploy Receiver (Target & Hub)
CONTRACT=receiver npx hardhat run scripts/deploy.ts --network baseSepoliaTestnet
CONTRACT=receiver npx hardhat run scripts/deploy.ts --network zkSyncSepoliaTestnet
# 2. Deploy Forwarder (Hub - ZKsync Era)
CONTRACT=forwarder npx hardhat run scripts/deploy.ts --network zkSyncSepoliaTestnet
# 3. Deploy Sender (Target - Base)
CONTRACT=sender npx hardhat run scripts/deploy.ts --network baseSepoliaTestnetConfigure the trust relationships so messages can flow securely.
# Configure Hub (ZKsync Era)
ACTION=set-trusted-forwarder npx hardhat run scripts/configure.ts --network zkSyncSepoliaTestnet
ACTION=set-authorized-relayer npx hardhat run scripts/configure.ts --network zkSyncSepoliaTestnet
ACTION=set-bridge-address npx hardhat run scripts/configure.ts --network zkSyncSepoliaTestnet
# Configure Target (Base)
ACTION=set-sender-receiver npx hardhat run scripts/configure.ts --network baseSepoliaTestnet
ACTION=set-trusted-forwarder npx hardhat run scripts/configure.ts --network baseSepoliaTestnetDeploy the Intelligent Contracts via GenLayer Studio:
- Deploy
BridgeSender.py: The exit point for results returning to EVM.- No constructor args.
- Deploy
BridgeReceiver.py: Receives and dispatches incoming requests to target ICs.- No constructor args. After deployment, call
set_authorized_relayer(wallet_address, true).
- No constructor args. After deployment, call
Update service/.env with your new contract addresses:
BRIDGE_SENDER_ADDRESS=<GenLayer BridgeSender Address>
BRIDGE_RECEIVER_IC_ADDRESS=<GenLayer BridgeReceiver Address>
ZKSYNC_BRIDGE_FORWARDER_ADDRESS=<ZKsync Era BridgeForwarder Address>
ZKSYNC_BRIDGE_RECEIVER_ADDRESS=<ZKsync Era BridgeReceiver Address>Start the relay:
cd service
npm run build
npm startThe service is now polling. Your bridge is live.
The service directory includes a CLI for debugging the bridge state.
cd service
# Check ZKsync Era BridgeReceiver state
npx ts-node cli.ts check-receiver
# Check Base BridgeSender state
npx ts-node cli.ts check-sender
# Check ZKsync Era BridgeForwarder state
npx ts-node cli.ts check-forwarder
# Verify all configurations
npx ts-node cli.ts check-config
# List pending messages on ZKsync Era
npx ts-node cli.ts pending-messages
# Debug a specific transaction
npx ts-node cli.ts debug-tx <hash>To demonstrate the capability, we provide a bidirectional messaging example.
π Run the Example
- EVM β GenLayer: Send a string from Base. The BridgeReceiver dispatches it to your Intelligent Contract.
- GenLayer β EVM: The Intelligent Contract sends a response back to the EVM chain.
- Service Logs: The
serviceconsole is your best debugging tool. It tracks every step of the relay. - Gas: Ensure your relayer wallet has ETH on both Base Sepolia and ZKsync Era Sepolia.
- Trust: If messages fail to deliver, check that
set-trusted-forwarderwas run on the target chain. - LayerZero Endpoints: Ensure you are using the correct Endpoint IDs for your networks.
- ZKsync Era Sepolia:
40305 - Base Sepolia:
40245
- ZKsync Era Sepolia:
MIT