Skip to main content
Executors provide Execution as a Service for LayerZero messages. A custom Executor receives a fee on the source chain, waits until the destination chain can accept the verified packet, and then calls the destination Endpoint to deliver the message. This guide shows how to independently bring up an EVM Executor for a two-chain testnet path, then explains the checks and operational controls required before running one in production.
The reference implementations linked in this guide are third-party examples, not LayerZero-owned production services. Use them to learn the protocol flow, then harden your own deployment with the production checklist below.

What You Will Build

By the end of this guide, you should have:
  • an Executor fee contract deployed on each source chain you want to support;
  • an OApp configured to pay your Executor instead of the default Executor;
  • an off-chain process watching source-chain PacketSent events;
  • fee validation that proves your Executor was paid for the exact packet;
  • destination-chain verification and execution logic; and
  • a repeatable smoke test that sends a message in both directions.
The commands and addresses below use an EVM-to-EVM testnet path. For non-EVM chains, keep the same protocol lifecycle, but replace the EVM packet address codec, event provider, and execution client with chain-specific implementations.

Executor Lifecycle

A complete Executor has two off-chain workflows: commit and execute.
  1. Watch the source chain for PacketSent on EndpointV2.
  2. Validate that the same transaction paid your Executor through the trusted send library.
  3. Decode the packet and route it by destination endpoint ID.
  4. Wait for destination-chain DVN verification.
  5. Call ReceiveUln302.commitVerification(packetHeader, payloadHash) when the packet is verifiable.
  6. Poll EndpointV2View.executable(origin, receiver) until the packet is executable.
  7. Call EndpointV2.lzReceive(origin, receiver, guid, message, extraData).
  8. Persist the result so restarts do not duplicate or lose work.
The source-chain Executor contract handles fee quoting and fee collection. The off-chain worker handles observation, verification, commit, and execution.

Prerequisites

Install:
  • Node.js >=18
  • npm, pnpm, or Yarn
  • Foundry
  • git
  • jq
  • RPC URLs for both chains
  • a funded signer on both chains
Prepare environment variables:
Use a dedicated Executor signer. Do not run an Executor with a production owner key, treasury key, or deployer key unless you have intentionally designed that operational model.

Choose Your Chains

Use the deployment pages or metadata API to collect the current Endpoint, SendUln302, ReceiveUln302, EndpointV2View, and endpoint ID values for each chain.
For example, this guide has been validated with:
Sepolia testnet pricing can be expensive because LayerZero testnet endpoints use real mainnet pricefeeds for crosschain transfers. For repeated tests, choose testnets with cheaper blockspace when possible.

Bootstrap A Reference Implementation

The Paladin reference implementation is a useful EVM starting point because it already separates the source-chain event provider, destination-chain executor, packet codec, and sample contracts.
If a fresh checkout fails while fetching contracts/lib/forge-std, add the missing submodule entry and retry:
The reference is intentionally minimal. Before operating it beyond a testnet smoke test, add durable state, replay checkpoints, rate limiting, metrics, and classified retry handling.

Deploy The On-Chain Executor

Deploy an Executor fee contract on every source chain that should pay your Executor. The contract must implement ILayerZeroExecutor:
For a smoke test, a static-fee contract is enough. For production, the fee must be calculated from the destination gas limit, gas price, calldata size, native token price, margin, and any native drop or value instructions in _options. After deploying your Executor contracts, configure your OApp’s send library so messages to the remote EID use your Executor:
Also configure the DVN set used by your test OApp. The Executor can only commit after the receive library has enough DVN verification for the packet.

Configure OApp Peers

Your test OApps must be deployed on both chains and configured as peers before _lzSend can route packets. For EVM-to-EVM tests, set each remote peer as a left-padded bytes32 value:
Use the same owner account that controls the OApp configuration. If your test OApp configures DVNs and Executor settings inside setPeer, confirm that both the send library and receive library configs are written for each pathway before quoting a message.

Create Executor Configuration

Your off-chain Executor needs one config entry per chain. Use current metadata for LayerZero protocol addresses and your own deployed Executor contract for executor.
Use bootstrapLookbackBlocks only if your worker persists checkpoints and can rate-limit historical replay. Endpoint-wide log scans can be noisy because the Endpoint emits traffic for all applications and executors.

Validate Executor Payment

Do not decide that your Executor was paid by checking whether a transaction contains any ExecutorFeePaid event. A single transaction can send multiple packets, and only one of them may have paid your Executor. Use this receipt-walking algorithm for each PacketSent log:
  1. Fetch the transaction receipt.
  2. Start from the specific PacketSent.logIndex.
  3. Walk backward through logs in that transaction.
  4. Stop if you hit another PacketSent; that earlier packet owns earlier fee events.
  5. Ignore any event not emitted by the configured trustedSendLib.
  6. Decode ExecutorFeePaid(address executor, uint256 fee).
  7. Accept the packet only if executor == yourExecutorAddress.
Pseudocode:
Add tests for:
  • multiple PacketSent logs in one transaction;
  • ExecutorFeePaid emitted by an untrusted address;
  • ExecutorFeePaid for another Executor;
  • no ExecutorFeePaid; and
  • zero or insufficient fee.

Commit Verification

After accepting a packet, derive:
  • origin: { srcEid, sender, nonce }
  • receiver: destination OApp address
  • packetHeader
  • payloadHash
First check endpoint-level verifiability:
Then check receive-library verifiability before committing:
When the receive library reports the packet is verifiable, call:
A packet can look close to ready at the Endpoint level while still not being committable at the receive library. Check both layers to avoid unnecessary commit reverts.

Execute The Message

After commit, query:
Handle execution state as: When executable:
Use the message options to set destination gas and value. A production Executor should parse options and enforce that the collected fee covers the requested execution resources.

Run The Off-Chain Worker

For the reference implementation, run:
A clean startup should:
  • load each configured chain;
  • register one provider per source EID;
  • register one executor per destination EID; and
  • begin watching PacketSent.
If you enable historical replay, start with a small block window and confirm your RPC provider supports the requested eth_getLogs range:
For production, replay should resume from the last persisted checkpoint, not from an arbitrary fixed lookback on every restart.

Smoke Test Both Directions

Before treating the Executor as usable, run a two-way smoke test.
  1. Quote a message from chain A to chain B.
  2. Send a small payload from chain A to chain B.
  3. Confirm the source transaction emitted PacketSent and ExecutorFeePaid.
  4. Confirm your worker committed verification on chain B.
  5. Confirm your worker executed lzReceive on chain B.
  6. Query the destination OApp and confirm the payload was stored or handled.
  7. Repeat from chain B to chain A.
If you are using the Paladin reference scripts, the commands look like:
For a test OApp that stores received messages, the success condition is:
Record the source send tx, destination commit tx, and destination execution tx for each direction. These three hashes are the fastest way to debug later failures.

Production Runtime Checklist

A production Executor needs more than the minimal reference loop.

Persistent State

Persist one row per packet: On restart, load pending packets from storage before scanning new blocks.

Checkpointed Event Ingestion

Do not rely on watchEvent alone. RPC subscriptions can miss logs during disconnects or process restarts. Use:
  • per-chain block checkpoints;
  • bounded eth_getLogs windows;
  • packet GUID and tx/log-index deduplication;
  • rate limits per RPC provider;
  • retries with exponential backoff; and
  • alerts when the scanner falls behind.

Retry Classification

Classify errors before retrying:

Signer Operations

Monitor:
  • native token balance on every destination chain;
  • account nonce and stuck transactions;
  • RPC latency and error rate;
  • gas price spikes;
  • oldest pending packet age; and
  • queue depth by destination EID.

Observability

Expose health and metrics:
  • last source block scanned;
  • last packet observed;
  • packets committed;
  • packets executed;
  • retries by class;
  • failures by class;
  • signer balances;
  • pending queue depth; and
  • RPC provider status.

Troubleshooting

Non-EVM Chains

The EVM packet codec assumes sender and receiver values can be converted from bytes32 to EVM addresses. For Solana, Aptos, Sui, or other non-EVM chains, implement chain-specific versions of:
  • event provider;
  • packet/address codec;
  • destination execution client;
  • transaction construction;
  • signer management; and
  • gas or fee accounting.
The protocol lifecycle remains the same: observe, validate payment, wait for verification, commit when needed, and execute when the destination Endpoint allows delivery.

Summary

The minimal reference Executor is enough to prove the LayerZero V2 flow on testnet. To make it independently operable, you need:
  • current metadata-derived chain config;
  • an Executor contract deployed on each source chain;
  • OApp send config pointing to your Executor;
  • strict per-packet fee validation;
  • current receive-library and endpoint verifiability checks;
  • a two-way smoke test; and
  • durable runtime state, checkpointed replay, retries, and monitoring before production.

Important Security And Operational Considerations

Can a custom Executor forge or alter LayerZero messages?

No. A custom Executor cannot make the destination Endpoint accept an unverified packet. Message authenticity is still enforced by the configured MessageLib and DVNs. The primary risk is not message forgery. The primary risk is that a poorly implemented Executor can fail to deliver valid messages, execute messages without being properly paid, overpay for gas, or expose its signer infrastructure.

What happens if I validate ExecutorFeePaid incorrectly?

You may execute packets that did not pay your Executor. This most commonly happens when an implementation checks whether the transaction contains any ExecutorFeePaid event instead of tying the payment to the exact PacketSent log. Batched sends can emit multiple PacketSent events in one transaction, and each packet must be matched to its own fee event. Mitigation:
  • walk receipt logs backward from the exact PacketSent.logIndex;
  • require the event emitter to equal the trusted send library;
  • require the decoded executor to equal your Executor contract; and
  • stop when a previous PacketSent is reached.

What happens if I use the wrong Endpoint, SendUln, ReceiveUln, or DVN address?

Messages can become stuck or fail in non-obvious ways. A packet may be observed on the source chain, but never become committable or executable on the destination chain because the worker is checking the wrong contract or the OApp pathway is configured differently than the worker expects. Mitigation:
  • source addresses from the LayerZero metadata endpoint or deployment pages;
  • store the exact metadata snapshot used for a deployment;
  • validate every configured address at startup; and
  • include a two-way smoke test after every config change.

What happens if my worker misses events?

Valid packets can remain undelivered. RPC subscriptions and HTTP polling can miss logs during restarts, disconnects, rate limits, or provider outages. Mitigation:
  • persist per-chain scan checkpoints;
  • replay bounded block ranges on startup;
  • deduplicate by packet GUID and tx/log index;
  • alert when the scanner falls behind; and
  • use an indexed or production-grade RPC provider for production traffic.

What happens if I do not persist packet state?

The worker can lose pending packets during a restart, retry the same packet indefinitely, or submit duplicate commit and execution transactions. Mitigation:
  • persist packet status transitions;
  • treat packet GUID as the primary idempotency key;
  • record commit and execution transaction hashes; and
  • resume pending work from storage before processing new logs.

What are the signer risks?

The Executor signer needs native gas on every destination chain it executes on. If the signer is overfunded, reused as an owner key, or stored on an insecure host, compromise of the Executor process can become a key-management incident. Mitigation:
  • use a dedicated low-privilege Executor signer;
  • keep owner, treasury, and Executor keys separate;
  • monitor signer balances and nonce state;
  • rotate keys through an operational runbook; and
  • fund only the amount required for expected execution volume plus buffer.

Can a bad gas or fee model lose money?

Yes. If your getFee and assignJob logic undercharges relative to destination gas costs, your Executor can pay more to execute packets than it collected on the source chain. If it over-trusts user-provided options, it can also accept work that is too expensive or impossible to execute. Mitigation:
  • parse message options before quoting;
  • cap supported gas and value options;
  • price destination native gas and token conversion conservatively;
  • include an operator margin; and
  • pause or reprice pathways when gas markets move sharply.

Is a stalled Executor a security issue?

It can be. A stalled Executor does not let invalid messages through, but it can create liveness failures for applications that rely on automatic delivery. If a chain is running its own Executor because another off-chain service was turned off, the Executor becomes part of that chain’s application availability path. Mitigation:
  • alert on oldest pending packet age;
  • alert on failed commit or execution attempts;
  • maintain enough destination gas;
  • document manual execution fallback; and
  • run a tested recovery process for RPC outages and stuck nonces.