|
| 1 | +# LayerZero Deployment Workflow |
| 2 | + |
| 3 | +This guide explains the complete workflow for deploying and configuring LayerZero OApps and OFTs. |
| 4 | + |
| 5 | +## The Big Picture |
| 6 | + |
| 7 | +``` |
| 8 | +┌─────────────────────────────────────────────────────────────────────────────────┐ |
| 9 | +│ Cross-Chain Message Flow │ |
| 10 | +├─────────────────────────────────────────────────────────────────────────────────┤ |
| 11 | +│ │ |
| 12 | +│ Source Chain LayerZero Network Destination Chain │ |
| 13 | +│ ──────────── ──────────────── ───────────────── │ |
| 14 | +│ │ |
| 15 | +│ ┌─────────┐ ┌─────────┐ │ |
| 16 | +│ │ Your │ ──► ┌──────────┐ ┌─────┐ ┌──────────┐ ──► │ Your │ │ |
| 17 | +│ │ OApp │ │ Endpoint │ ──► │ DVN │ ──► │ Endpoint │ │ OApp │ │ |
| 18 | +│ └─────────┘ └──────────┘ └─────┘ └──────────┘ └─────────┘ │ |
| 19 | +│ │ │ │ │ │ |
| 20 | +│ │ │ │ │ │ |
| 21 | +│ User calls DVNs verify Executor delivers │ |
| 22 | +│ send() the message and calls receive │ |
| 23 | +│ │ |
| 24 | +└─────────────────────────────────────────────────────────────────────────────────┘ |
| 25 | +``` |
| 26 | + |
| 27 | +## Transaction Model |
| 28 | + |
| 29 | +A LayerZero deployment requires transactions on **every chain** in your configuration. Here's the breakdown: |
| 30 | + |
| 31 | +### Phase 1: Deployment |
| 32 | + |
| 33 | +| Action | Transactions | Description | |
| 34 | +|--------|--------------|-------------| |
| 35 | +| Deploy contract | 1 per chain | Deploy your OApp/OFT contract | |
| 36 | +| **Total** | **N chains** | N deployment transactions | |
| 37 | + |
| 38 | +### Phase 2: Wiring (Configuration) |
| 39 | + |
| 40 | +For each **pathway** (A ↔ B), the wire task generates: |
| 41 | + |
| 42 | +| Action | Transactions | Description | |
| 43 | +|--------|--------------|-------------| |
| 44 | +| `setPeer` | 2 (A→B, B→A) | Set peer addresses | |
| 45 | +| `setConfig` (Send) | 2 | Configure send library (DVNs, etc.) | |
| 46 | +| `setConfig` (Receive) | 2 | Configure receive library | |
| 47 | +| `setEnforcedOptions` | 2 | Set minimum gas options | |
| 48 | +| **Total per pathway** | **~8 txs** | 4 on each chain | |
| 49 | + |
| 50 | +### Example: 2-Chain Deployment (Base ↔ Arbitrum) |
| 51 | + |
| 52 | +``` |
| 53 | +Deployment Phase: |
| 54 | + - Deploy MyOFT on Base: 1 tx |
| 55 | + - Deploy MyOFT on Arbitrum: 1 tx |
| 56 | + Total: 2 txs |
| 57 | +
|
| 58 | +Wiring Phase: |
| 59 | + - setPeer (Base → Arb): 1 tx on Base |
| 60 | + - setPeer (Arb → Base): 1 tx on Arbitrum |
| 61 | + - setConfig (Base send): 1 tx on Base |
| 62 | + - setConfig (Base receive): 1 tx on Base |
| 63 | + - setConfig (Arb send): 1 tx on Arbitrum |
| 64 | + - setConfig (Arb receive): 1 tx on Arbitrum |
| 65 | + - setEnforcedOptions: 2 txs (1 per chain) |
| 66 | + Total: ~8 txs |
| 67 | +
|
| 68 | +Grand Total: ~10 transactions |
| 69 | +``` |
| 70 | + |
| 71 | +### Example: 3-Chain Deployment (A ↔ B ↔ C) |
| 72 | + |
| 73 | +With 3 chains fully connected (3 pathways: A-B, A-C, B-C): |
| 74 | +- Deployment: 3 txs |
| 75 | +- Wiring: 3 pathways × ~8 txs = ~24 txs |
| 76 | +- **Total: ~27 transactions** |
| 77 | + |
| 78 | +## Hardhat as Task Orchestration |
| 79 | + |
| 80 | +**Key Insight**: In this repository, Hardhat is used as a **task orchestration system**, not just a Solidity compiler. |
| 81 | + |
| 82 | +The LayerZero SDK exposes functionality through Hardhat tasks: |
| 83 | + |
| 84 | +```bash |
| 85 | +# These are NOT just compile commands - they orchestrate multi-chain operations |
| 86 | +npx hardhat lz:deploy # Deploys to ALL configured networks |
| 87 | +npx hardhat lz:oapp:wire # Configures ALL pathways |
| 88 | +npx hardhat lz:oapp:config:get # Reads config from ALL chains |
| 89 | +``` |
| 90 | + |
| 91 | +### How It Works |
| 92 | + |
| 93 | +1. `@layerzerolabs/toolbox-hardhat` registers custom tasks |
| 94 | +2. Tasks read your `hardhat.config.ts` for network definitions |
| 95 | +3. Tasks read your `layerzero.config.ts` for pathway configuration |
| 96 | +4. Tasks execute transactions across multiple networks |
| 97 | + |
| 98 | +## Understanding layerzero.config.ts |
| 99 | + |
| 100 | +The configuration file defines your **OmniGraph** - the topology of your omnichain application. |
| 101 | + |
| 102 | +### Structure |
| 103 | + |
| 104 | +```typescript |
| 105 | +// layerzero.config.ts exports an async function |
| 106 | +export default async function() { |
| 107 | + return { |
| 108 | + contracts: OmniNode[], // Which contracts on which chains |
| 109 | + connections: OmniEdge[], // Pathways between contracts |
| 110 | + } |
| 111 | +} |
| 112 | +``` |
| 113 | + |
| 114 | +### Why Is Config Async? |
| 115 | + |
| 116 | +The config fetches **live metadata** at runtime: |
| 117 | +- DVN contract addresses per chain |
| 118 | +- Default configurations |
| 119 | +- Executor addresses |
| 120 | + |
| 121 | +This ensures your config uses the latest deployed infrastructure. |
| 122 | + |
| 123 | +### Key Types |
| 124 | + |
| 125 | +```typescript |
| 126 | +// OmniPointHardhat - A contract location |
| 127 | +const myContract: OmniPointHardhat = { |
| 128 | + eid: EndpointId.BASESEP_V2_TESTNET, // Which chain |
| 129 | + contractName: 'MyOFT', // Contract name (from hardhat-deploy) |
| 130 | +} |
| 131 | + |
| 132 | +// OmniNode - A contract with its configuration |
| 133 | +const node = { |
| 134 | + contract: myContract, |
| 135 | + config: { /* optional per-contract config */ } |
| 136 | +} |
| 137 | + |
| 138 | +// OmniEdge - A pathway between two contracts |
| 139 | +const edge = { |
| 140 | + from: contractA, |
| 141 | + to: contractB, |
| 142 | + config: { |
| 143 | + sendConfig: { /* DVN, executor config */ }, |
| 144 | + receiveConfig: { /* DVN config */ }, |
| 145 | + enforcedOptions: [ /* gas options */ ], |
| 146 | + } |
| 147 | +} |
| 148 | +``` |
| 149 | + |
| 150 | +### Using generateConnectionsConfig() |
| 151 | + |
| 152 | +The helper function simplifies bidirectional pathway configuration: |
| 153 | + |
| 154 | +```typescript |
| 155 | +import { generateConnectionsConfig, TwoWayConfig } from '@layerzerolabs/metadata-tools' |
| 156 | + |
| 157 | +const pathways: TwoWayConfig[] = [ |
| 158 | + [ |
| 159 | + contractA, // From |
| 160 | + contractB, // To |
| 161 | + [['LayerZero Labs'], []], // [requiredDVNs, [optionalDVNs, threshold]] |
| 162 | + [1, 1], // [A→B confirmations, B→A confirmations] |
| 163 | + [enforcedOptionsAtoB, enforcedOptionsBtoA], |
| 164 | + ], |
| 165 | +] |
| 166 | + |
| 167 | +const connections = await generateConnectionsConfig(pathways) |
| 168 | +``` |
| 169 | + |
| 170 | +## Pathway Lifecycle |
| 171 | + |
| 172 | +A pathway goes through these states: |
| 173 | + |
| 174 | +``` |
| 175 | +1. DEPLOYED |
| 176 | + └── Contracts deployed, but not connected |
| 177 | + └── Cannot send messages |
| 178 | +
|
| 179 | +2. WIRED (Peer Set) |
| 180 | + └── setPeer() called on both ends |
| 181 | + └── Contracts know each other's addresses |
| 182 | + └── Still cannot send without proper config |
| 183 | +
|
| 184 | +3. CONFIGURED |
| 185 | + └── setConfig() called for send/receive |
| 186 | + └── DVNs and executors configured |
| 187 | + └── setEnforcedOptions() called |
| 188 | +
|
| 189 | +4. LIVE ✓ |
| 190 | + └── All configuration complete |
| 191 | + └── Messages can flow in both directions |
| 192 | +``` |
| 193 | + |
| 194 | +### Checking Pathway Status |
| 195 | + |
| 196 | +```bash |
| 197 | +# Check if peers are set |
| 198 | +npx hardhat lz:oapp:peers:get --oapp-config layerzero.config.ts |
| 199 | + |
| 200 | +# Check full configuration |
| 201 | +npx hardhat lz:oapp:config:get --oapp-config layerzero.config.ts |
| 202 | + |
| 203 | +# Compare with LayerZero defaults |
| 204 | +npx hardhat lz:oapp:config:get:default --oapp-config layerzero.config.ts |
| 205 | +``` |
| 206 | + |
| 207 | +## Common Workflows |
| 208 | + |
| 209 | +### Workflow 1: Fresh Testnet Deployment |
| 210 | + |
| 211 | +```bash |
| 212 | +# 1. Setup environment |
| 213 | +cp .env.example .env |
| 214 | +# Edit .env with MNEMONIC or PRIVATE_KEY |
| 215 | + |
| 216 | +# 2. Install and build |
| 217 | +pnpm install |
| 218 | +pnpm compile |
| 219 | + |
| 220 | +# 3. Deploy to all networks |
| 221 | +npx hardhat lz:deploy |
| 222 | + |
| 223 | +# 4. Wire all pathways |
| 224 | +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts |
| 225 | + |
| 226 | +# 5. Verify configuration |
| 227 | +npx hardhat lz:oapp:config:get --oapp-config layerzero.config.ts |
| 228 | + |
| 229 | +# 6. Send test message/token (example-specific task) |
| 230 | +# Note: lz:oft:send is a custom task defined in examples/oft/tasks/, not a core SDK task |
| 231 | +# Each example may have its own send task implementation |
| 232 | +npx hardhat lz:oft:send --network base-sepolia --to arbitrum-sepolia --amount 1000000000000000000 |
| 233 | +``` |
| 234 | + |
| 235 | +### Workflow 2: Adding a New Chain |
| 236 | + |
| 237 | +1. **Update hardhat.config.ts**: |
| 238 | +```typescript |
| 239 | +networks: { |
| 240 | + // Existing networks... |
| 241 | + 'new-chain': { |
| 242 | + eid: EndpointId.NEW_CHAIN_V2_MAINNET, |
| 243 | + url: process.env.RPC_URL_NEW_CHAIN, |
| 244 | + accounts, |
| 245 | + }, |
| 246 | +} |
| 247 | +``` |
| 248 | + |
| 249 | +2. **Update layerzero.config.ts**: |
| 250 | +```typescript |
| 251 | +const newChainContract: OmniPointHardhat = { |
| 252 | + eid: EndpointId.NEW_CHAIN_V2_MAINNET, |
| 253 | + contractName: 'MyOFT', |
| 254 | +} |
| 255 | + |
| 256 | +// Add to contracts array |
| 257 | +contracts: [ |
| 258 | + { contract: existingContract1 }, |
| 259 | + { contract: existingContract2 }, |
| 260 | + { contract: newChainContract }, // Add new |
| 261 | +] |
| 262 | + |
| 263 | +// Add pathways to existing contracts |
| 264 | +const pathways: TwoWayConfig[] = [ |
| 265 | + // Existing pathways... |
| 266 | + [existingContract1, newChainContract, ...], |
| 267 | + [existingContract2, newChainContract, ...], |
| 268 | +] |
| 269 | +``` |
| 270 | + |
| 271 | +3. **Deploy and wire**: |
| 272 | +```bash |
| 273 | +# Deploy only to new network |
| 274 | +npx hardhat deploy --network new-chain --tags MyOFT |
| 275 | + |
| 276 | +# Wire all pathways (including new ones) |
| 277 | +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts |
| 278 | +``` |
| 279 | + |
| 280 | +### Workflow 3: Updating Configuration |
| 281 | + |
| 282 | +If you need to change DVNs, confirmations, or enforced options: |
| 283 | + |
| 284 | +1. Update `layerzero.config.ts` with new configuration |
| 285 | +2. Run wire again - it will only update changed values: |
| 286 | +```bash |
| 287 | +npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts |
| 288 | +``` |
| 289 | + |
| 290 | +## Environment Variables |
| 291 | + |
| 292 | +| Variable | Purpose | Example | |
| 293 | +|----------|---------|---------| |
| 294 | +| `MNEMONIC` | Wallet mnemonic phrase | `word1 word2 ... word12` | |
| 295 | +| `PRIVATE_KEY` | Alternative to mnemonic | `0xabc123...` | |
| 296 | +| `RPC_URL_<NETWORK>` | RPC endpoint per network | `https://rpc.example.com` | |
| 297 | + |
| 298 | +### .env Example |
| 299 | + |
| 300 | +```bash |
| 301 | +# Authentication (choose one) |
| 302 | +MNEMONIC="your twelve word mnemonic phrase goes here" |
| 303 | +# PRIVATE_KEY=0x... |
| 304 | + |
| 305 | +# RPC URLs |
| 306 | +RPC_URL_BASE_SEPOLIA=https://base-sepolia.gateway.tenderly.co |
| 307 | +RPC_URL_ARB_SEPOLIA=https://arbitrum-sepolia.gateway.tenderly.co |
| 308 | +``` |
| 309 | + |
| 310 | +## See Also |
| 311 | + |
| 312 | +- [DEBUGGING.md](./DEBUGGING.md) - Troubleshooting guide |
| 313 | +- [CHEATSHEET.md](./CHEATSHEET.md) - Quick reference |
| 314 | +- [examples/oft/](./examples/oft/) - OFT example with full config |
| 315 | +- [Official Documentation](https://docs.layerzero.network/) |
0 commit comments