Skip to main content
LayerZero V2 on Solana mirrors the design of the EVM version in that it coordinates crosschain messaging through multiple protocol smart contracts. However, instead of EVM contracts and events, Solana programs use CPIs (cross–program invocations), PDAs (program–derived addresses), and a series of instructions that are tightly validated by Anchor:
  • Send Workflow: How a crosschain message packet is created, fees calculated via the Message Library, and sent from the source chain.
  • DVN Verification Workflow: How an application’s configured decentralized verifier networks (DVNs) initialize and later verify the message payload.
  • Executor Workflow: How the Executor program finally executes the message (invoking the receiving OApp via an lzReceive call).

Send Overview

When a user sends a crosschain message, the following high–level steps occur:

Endpoint Program

  1. Send Instruction on the LayerZero Endpoint:
    The Send instruction is called on the Endpoint program via a CPI call from another program:
    • Increments the outbound nonce.
    • Constructs a unique packet (including a GUID computed via a hash of parameters).
    • Invokes the send library (e.g. ULN302) via CPI to calculate fee allocations and emit the corresponding events.

SendUln302 Program

  1. Fee Quotation and Payment via CPI:
    The send library (ULN302) uses instructions like QuoteExecutor and QuoteDvn via a series of CPI calls to programs such as the Executor and DVN.
  2. Endpoint Packet Emission:
    Finally, after fee calculations and transfers, the Endpoint program emits an event (e.g. PacketSentEvent) and the packet is recorded onchain.

Verification Workflow

After the send operation, the DVNs must verify the message on the destination chain before message execution. On Solana, every account must be explicitly allocated with sufficient space. For DVN verification, this means a dedicated payload hash account is first created and initialized. This ensures that when a DVN writes its witness, the storage exists and is correctly sized.

DVN Verification

Each DVN individually performs the following steps:
  1. Initialization with ReceiveULN.init_verify:
    The DVN calls init_verify on the ULN program to create and initialize a dedicated Confirmations account. The init_verify method initializes the Confirmations’s account value field as None, and stores its PDA bump.
  2. Invocation with invoke:
    After initialization, the DVN triggers its own verification logic via an invoke instruction. This CPI call executes internal checks (such as signature verification and configuration validation) and, in the process, calls into the ULN’s verification logic by triggering a CPI call to the verify instruction.
  3. Final Verification via ReceiveULN.verify:
    Once the DVN’s internal verification logic completes and the conditions are met, the ULN program finalizes the DVN verification by calling its own verify function. This function updates the DVN-specific payload hash and emits a PayloadVerifiedEvent to signal that the message has been verified by that DVN.
Summary of DVN Verification:
  • ReceiveUln.init_verify(): Initializes a dedicated payload hash account with an empty hash.
  • DVN.invoke(): Executes the DVN’s internal verification logic and triggers the ULN’s verify instruction via a nested CPI.
  • ReceiveUln.verify(): The ULN finalizes the verification by updating the payload hash and emitting a PayloadVerifiedEvent.

Commit Verification

After all required verifications have been submitted (meeting the X of Y of N configuration), the payload hash can then be committed. The commit verification process ensures that the verified message is recorded in the Endpoint’s messaging channel. This process comprises two primary steps:
  1. Initialization via Endpoint.init_verify on the Endpoint:
    Before committing the verification, the system calls init_verify on the Endpoint. This creates and initializes a dedicated payload hash account, reserving space for the verification data. The account is set up with an initial empty payload hash (EMPTY_PAYLOAD_HASH) and a bump value for PDA derivation.
  2. Committing Verification via commitVerification on ReceiveUln302:
    Once the payload hash account is initialized and DVN confirmations have been collected, the ReceiveUln302.commitVerification() function is called to finalize the verification by:
    • Validating the Packet Header:
      It checks that the header version is correct and that the destination endpoint ID (EID) matches the ULN302’s configured EID.
    • Verifying DVN Confirmations:
      It calculates the number of DVN confirmation accounts (both required and optional) and uses helper functions (e.g., check_verifiable and verified) to ensure that every DVN has provided sufficient confirmation.
    • CPI to the Endpoint’s verify Instruction:
      If all checks pass, a CPI call is made to the Endpoint’s verify function. This call updates the payload hash stored in the dedicated account and emits a PacketVerifiedEvent, thereby recording the verified message on the Endpoint’s messaging channel.
  3. Insert Hash into the Endpoint’s Message Channel via verify:
    The Endpoint’s verify method is the final step in the commit verification process. Once invoked via CPI, it performs the following actions:
    • Nonce Management:
      It checks if the packet’s nonce is greater than the current inbound nonce and updates the pending inbound nonce if necessary.
    • Updating the Payload Hash:
      The verified payload hash is written into the payload hash account.
    • Event Emission:
      A PacketVerifiedEvent is emitted, signaling that the packet has been verified and recorded onchain.
Summary of Commit Verification:
  • Endpoint.init_verify(): Creates and initializes a dedicated payload hash account with an empty hash.
  • ReceiveUln302.commitVerification(): Validates the packet header and DVN confirmations, then commits the verification by calling the Endpoint’s verify via CPI.
  • Endpoint.verify(): Inserts the verified payload hash into the messaging channel, updates nonce management, and emits a PacketVerifiedEvent.
Together, these steps ensure that only messages with sufficient DVN confirmations are recorded onchain in the Endpoint’s messaging channel, thereby maintaining the integrity and security of the crosschain message.

Receive Workflow

The Solana receive flow is divided into three primary stages:
  1. Execute:
    The Executor program initiates the message execution process by calling its execute instruction. In this step, the Executor:
    • Gathers all required accounts.
    • Invokes downstream instructions via CPI to eventually call lzReceive.
    • Checks that its lamport balance does not drop unexpectedly.
    • If the CPI call fails, an alert is triggered via lzReceiveAlert.
  2. LzReceiveTypes – Account Assembly:
    The lzReceiveTypes instruction gathers all the accounts required by the final message execution. This step constructs the list of accounts—including PDAs for the peer, configuration accounts, token escrow (if needed), token destination, mint, and various system accounts—based on the parameters of the received message.
  3. LzReceive – Final Message Execution:
    Finally, the lzReceive instruction executes the received message. This is where the actual processing occurs. In this step, the program must implement safety checks that clear the payload to prevent reentrancy and double execution. Specifically, it:
    • Clears the Payload:
      Updates nonces, verifies that the payload hash matches the verified data, and deletes the message from storage.
    • Performs Token Operations (if applicable):
      Depending on the message type, it may mint tokens or perform transfers.
    • Emits an Event:
      Signals that the message has been successfully received and processed.

Key Solana-Specific Considerations

  • Explicit Safety Checks:
    Unlike the EVM, where safety checks such as payload clearing are handled by a provided inheritance pattern, the Solana OApp must explicitly implement these checks within its lzReceive logic. This includes updating nonces, verifying payload integrity, and deleting processed messages to prevent reentrancy or double execution.
  • CPI and Account Assembly:
    The flow (executelzReceiveTypeslzReceive) relies on explicit CPI calls, with each instruction receiving a full list of pre-allocated accounts. There is no runtime dispatch or inheritance; all required accounts must be passed along manually.
  • Token Operations:
    When the message carries token transfers (as in OFT), token operations (transfer or mint) are executed within lzReceive via CPI calls to the Token Program.
This documentation outlines the full receive workflow on Solana, detailing the flow from message execution to final processing while emphasizing the responsibility of the OApp to implement its own safety measures within lzReceive.