Skip to main content
Version: Endpoint V2

Interactive Solana Program Playground

Test LayerZero Solana programs directly from your browser. Build and send instructions without writing code. Explore key program instructions for message fee calculation, sending, receiving, and configuration management.

For production use, please use the official SDKs which provides proper type-safe instruction builders:

Real On-Chain Methods

All instructions shown in this playground are real methods available in the LayerZero Solana programs today:

We only document OApp-relevant instructions, excluding admin-only functions. State variables are clearly marked as direct account data reads, not instructions.

LayerZero EndpointV2

The main entry point for all cross-chain messaging operations on Solana. This program handles message routing, fee calculation, and configuration management.

Message Routing

Core functions for sending and receiving messages between smart contracts.

quote() - Get Fee Estimates

READ
quote(sendLibraryProgram: AccountInfo, sendLibraryConfig: AccountInfo, defaultSendLibraryConfig: AccountInfo, sendLibraryInfo: AccountInfo, endpoint: AccountInfo, nonce: AccountInfo, sender: Pubkey, dstEid: u32, receiver: bytes32, message: bytes, options: bytes, payInLzToken: bool)

send() - Send Messages

WRITE
send(sender: Signer, sendLibraryProgram: AccountInfo, sendLibraryConfig: AccountInfo, defaultSendLibraryConfig: AccountInfo, sendLibraryInfo: AccountInfo, endpoint: AccountInfo, nonce: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, dstEid: u32, receiver: bytes32, message: bytes, options: bytes, nativeFee: u64, lzTokenFee: u64)

clear() - Clear Payload

WRITE
clear(signer: Signer, oappRegistry: AccountInfo, nonce: AccountInfo, payloadHash: AccountInfo, endpoint: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, receiver: Pubkey, srcEid: u32, sender: bytes32, nonce: u64, guid: bytes32, message: bytes)
Why is clear() under Message Routing instead of Message Recovery?

On Solana, the clear() instruction placement differs from EVM:

  • EVM: The Endpoint handles clearing payloads directly during message delivery
  • Solana: The OApp must explicitly call clear() via CPI in its lzReceive implementation

This architectural difference means Solana developers need to implement the CPI call to clear() within their OApp's message handling logic, giving them more control over the message lifecycle but requiring explicit implementation. See the architectural note at the top of this page for the complete message flow.

sendCompose() - Send Compose Messages

WRITE
sendCompose(from: Signer, payer: Signer, composeMessage: AccountInfo, systemProgram: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, to: Pubkey, guid: bytes32, index: u16, message: bytes)

clearCompose() - Clear Compose Message

WRITE
clearCompose(to: Signer, composeMessage: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, from: Pubkey, guid: bytes32, index: u16, message: bytes)
Architectural Difference: lzReceive

If you're looking for lzReceive or lzCompose instructions in the Endpoint (as you might expect from EVM), note that on Solana these are implemented directly in the OApp (e.g., OFT program) due to architectural differences.

On Solana:

  • The executor calls lzReceive directly on the OApp/OFT program
  • The OApp then makes a CPI (Cross-Program Invocation) call to the Endpoint's clear instruction to validate and clear the payload
  • After validation, the OApp continues with its business logic (e.g., minting tokens)

This is different from EVM where the Endpoint calls back to the OApp's _lzReceive function. On Solana, the flow is inverted with the OApp calling into the Endpoint.

See the OFT lzReceive implementation for an example.

Configuration Management

Functions for setting custom verification, execution, and pathway management.

registerOapp() - Register OApp

WRITE
registerOapp(payer: Signer, oapp: Signer, oappRegistry: AccountInfo, systemProgram: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, delegate: Pubkey)

setDelegate() - Set Delegate Address

WRITE
setDelegate(oapp: Signer, oappRegistry: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, delegate: Pubkey)

setSendLibrary() - Configure Send Library

WRITE
setSendLibrary(signer: Signer, oappRegistry: AccountInfo, sendLibraryConfig: AccountInfo, messageLibInfo: AccountInfo?, eventAuthority: AccountInfo, program: AccountInfo, sender: Pubkey, eid: u32, newLib: Pubkey)

setReceiveLibrary() - Configure Receive Library

WRITE
setReceiveLibrary(signer: Signer, oappRegistry: AccountInfo, receiveLibraryConfig: AccountInfo, messageLibInfo: AccountInfo?, eventAuthority: AccountInfo, program: AccountInfo, receiver: Pubkey, eid: u32, newLib: Pubkey, gracePeriod: u64)

setConfig() - Set Configuration Parameters

WRITE
setConfig(signer: Signer, oappRegistry: AccountInfo, messageLibInfo: AccountInfo, messageLib: AccountInfo, messageLibProgram: AccountInfo, oapp: Pubkey, eid: u32, configType: u32, config: bytes)

initNonce() - Initialize Nonce

WRITE
initNonce(delegate: Signer, oappRegistry: AccountInfo, nonce: AccountInfo, pendingInboundNonce: AccountInfo, systemProgram: AccountInfo, localOapp: Pubkey, remoteEid: u32, remoteOapp: bytes32)

initVerify() - Initialize Verification

WRITE
initVerify(payer: Signer, nonce: AccountInfo, payloadHash: AccountInfo, systemProgram: AccountInfo, srcEid: u32, sender: bytes32, receiver: Pubkey, nonce: u64)

initSendLibrary() - Initialize Send Library

WRITE
initSendLibrary(delegate: Signer, oappRegistry: AccountInfo, sendLibraryConfig: AccountInfo, systemProgram: AccountInfo, sender: Pubkey, eid: u32)

initReceiveLibrary() - Initialize Receive Library

WRITE
initReceiveLibrary(delegate: Signer, oappRegistry: AccountInfo, receiveLibraryConfig: AccountInfo, systemProgram: AccountInfo, receiver: Pubkey, eid: u32)

initConfig() - Initialize Configuration

WRITE
initConfig(delegate: Signer, oappRegistry: AccountInfo, messageLibInfo: AccountInfo, messageLib: AccountInfo, messageLibProgram: AccountInfo, oapp: Pubkey, eid: u32)

setReceiveLibraryTimeout() - Set Library Timeout

WRITE
setReceiveLibraryTimeout(signer: Signer, oappRegistry: AccountInfo, receiveLibraryConfig: AccountInfo, messageLibInfo: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, receiver: Pubkey, eid: u32, lib: Pubkey, expiry: u64)

Message Recovery & Security

Functions for handling message exceptions, security threats, and recovery scenarios.

burn() - Permanently Block Message

WRITE
burn(signer: Signer, oappRegistry: AccountInfo, nonce: AccountInfo, payloadHash: AccountInfo, endpoint: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, receiver: Pubkey, srcEid: u32, sender: bytes32, nonce: u64, payloadHash: bytes32)

skip() - Skip Inbound Nonce

WRITE
skip(signer: Signer, oappRegistry: AccountInfo, nonce: AccountInfo, pendingInboundNonce: AccountInfo, payloadHash: AccountInfo, endpoint: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, receiver: Pubkey, srcEid: u32, sender: bytes32, nonce: u64)

nilify() - Mark Message as Nil

WRITE
nilify(signer: Signer, oappRegistry: AccountInfo, nonce: AccountInfo, pendingInboundNonce: AccountInfo, payloadHash: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, receiver: Pubkey, srcEid: u32, sender: bytes32, nonce: u64, payloadHash: bytes32)

withdrawRent() - Withdraw Rent

WRITE
withdrawRent(admin: Signer, endpoint: AccountInfo, receiver: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, amount: u64)

Status Checks

Functions for querying current configuration settings, library assignments, nonce tracking, and message states.

eid() - Get Endpoint ID

READ
eid(endpoint: AccountInfo)

endpointAdmin() - Get Endpoint Admin

READ
endpointAdmin(endpoint: AccountInfo)

oappDelegate() - Get OApp Delegate

READ
oappDelegate(oappRegistry: AccountInfo)

outboundNonce() - Get Outbound Nonce

READ
outboundNonce(nonce: AccountInfo)

inboundNonce() - Get Inbound Nonce

READ
inboundNonce(nonce: AccountInfo)

Omnichain Fungible Token (OFT)

Omnichain Fungible Token (OFT) enables seamless cross-chain token transfers. Deploy once and bridge your SPL tokens to any supported blockchain.

Since Solana uses a rent-based storage model rather than EVM's gas-per-bytecode deployment costs, and has no restrictive contract size limits (like EVM's 24KB limit), we can include all of these extensions in the same program. While some OFT instances may not utilize all features (like fees or rate limits), having them built-in provides maximum flexibility without the contract splitting requirements common in EVM development.

Default OFT Program

For Solana Mainnet, we use PENGU OFT as the default example:

  • Program: EfRMrTJWU2CYm52kHmRYozQNdF8RH5aTi3xyeSuLAX2Y
  • OFT Store: qMNo1RFo11J9ZLGuq7dVmWAssuCZaNsSamk8g2q4UZA

You can replace these with your own OFT deployment addresses.

lzReceive Implementation

The lzReceive instruction is implemented here in the OFT program (not in the Endpoint). This is a key architectural difference from EVM:

  • On Solana, executors call lzReceive directly on the OApp/OFT
  • The OFT program then makes a CPI call to the Endpoint's clear instruction to validate the message
  • After successful validation, the OFT continues with its logic (minting tokens, updating balances, etc.)

The flow is: Executor → OFT.lzReceive → Endpoint.clear (via CPI) → Continue OFT logic

Send Tokens

quoteSend() - Get Transfer Fees

READ
quoteSend(oftStore: AccountInfo, peer: AccountInfo, tokenMint: AccountInfo, dstEid: u32, to: bytes32, amountLd: u64, minAmountLd: u64, options: bytes, composeMsg: bytes?, payInLzToken: bool)

quoteOft() - Get Detailed Transfer Quote

READ
quoteOft(oftStore: AccountInfo, peer: AccountInfo, tokenMint: AccountInfo, dstEid: u32, to: bytes32, amountLd: u64, minAmountLd: u64, options: bytes, composeMsg: bytes?, payInLzToken: bool)

send() - Transfer Tokens

WRITE
send(signer: Signer, peer: AccountInfo, oftStore: AccountInfo, tokenSource: AccountInfo, tokenEscrow: AccountInfo, tokenMint: AccountInfo, tokenProgram: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, dstEid: u32, to: bytes32, amountLd: u64, minAmountLd: u64, options: bytes, composeMsg: bytes?, nativeFee: u64, lzTokenFee: u64)

lzReceiveTypes() - Get Receive Account Types

READ
lzReceiveTypes(oftStore: AccountInfo, tokenMint: AccountInfo, srcEid: u32, sender: bytes32, nonce: u64, guid: bytes32, message: bytes, extraData: bytes)

lzReceive() - Receive Tokens

WRITE
lzReceive(payer: Signer, peer: AccountInfo, oftStore: AccountInfo, tokenEscrow: AccountInfo, toAddress: AccountInfo, tokenDest: AccountInfo, tokenMint: AccountInfo, mintAuthority: AccountInfo?, tokenProgram: AccountInfo, associatedTokenProgram: AccountInfo, systemProgram: AccountInfo, eventAuthority: AccountInfo, program: AccountInfo, srcEid: u32, sender: bytes32, nonce: u64, guid: bytes32, message: bytes, extraData: bytes)

Token Details

The OFT Store account is a Program Derived Address (PDA), not the token mint itself. This account stores essential OFT related state variables.

oftAdmin() - Get OFT Admin

READ
oftAdmin(oftStore: AccountInfo)

tokenMint() - Get Token Mint

READ
tokenMint(oftStore: AccountInfo)

tokenEscrow() - Get Token Escrow

READ
tokenEscrow(oftStore: AccountInfo)

sharedDecimals() - Get Shared Decimals

READ
sharedDecimals(oftStore: AccountInfo)

decimalConversionRate() - Get Decimal Conversion Rate

READ
decimalConversionRate(oftStore: AccountInfo)

oftVersion() - Get OFT Version

READ
oftVersion()

tvlLd() - Get Total Value Locked

READ
tvlLd(oftStore: AccountInfo)

isPaused() - Check Pause State

READ
isPaused(oftStore: AccountInfo)

defaultFeeBps() - Get Default Fee

READ
defaultFeeBps(oftStore: AccountInfo)

Peer Configuration

These functions read peer-specific configuration from PeerConfig accounts:

peerAddress() - Get Peer Address

READ
peerAddress(peerConfig: AccountInfo)

enforcedOptions() - Check Enforced Options

READ
enforcedOptions(peerConfig: AccountInfo)

peerFeeBps() - Get Peer Fee

READ
peerFeeBps(peerConfig: AccountInfo)

outboundRateLimiter() - Check Outbound Rate Limiter

READ
outboundRateLimiter(peerConfig: AccountInfo)

inboundRateLimiter() - Check Inbound Rate Limiter

READ
inboundRateLimiter(peerConfig: AccountInfo)
Finding PeerConfig Account

The PeerConfig account is a PDA (Program Derived Address) derived from:

  • OFT program ID
  • Seeds: [b"peer_config", oft_store.key().as_ref(), &dst_eid.to_be_bytes()]

You'll need to derive this address using the OFT store account and the destination chain's endpoint ID.

Management Functions

initOft() - Initialize OFT

WRITE
initOft(payer: Signer, oftStore: AccountInfo, lzReceiveTypesAccounts: AccountInfo, tokenMint: AccountInfo, tokenEscrow: Signer, tokenProgram: AccountInfo, systemProgram: AccountInfo, oftType: u8, admin: Pubkey, sharedDecimals: u8, endpointProgram: Pubkey?)

setPeerConfig() - Configure Remote Peer

WRITE
setPeerConfig(admin: Signer, peer: AccountInfo, oftStore: AccountInfo, systemProgram: AccountInfo, remoteEid: u32, configType: u8, configData: bytes)

setOftConfig() - Update OFT Settings

WRITE
setOftConfig(admin: Signer, oftStore: AccountInfo, configType: u8, configData: bytes)

setPause() - Pause/Unpause OFT

WRITE
setPause(signer: Signer, oftStore: AccountInfo, paused: bool)

withdrawFee() - Withdraw Collected Fees

WRITE
withdrawFee(admin: Signer, oftStore: AccountInfo, tokenMint: AccountInfo, tokenEscrow: AccountInfo, tokenDest: AccountInfo, tokenProgram: AccountInfo, feeLd: u64)

Events and Errors

Endpoint Events

Key events emitted by the Endpoint program during cross-chain operations.

PacketSentEvent - Message Sent

Emitted when a packet is sent through the endpoint. Contains the encoded packet data and execution options for tracking cross-chain messages.

PacketVerifiedEvent - Message Verified

Emitted when an inbound message has been verified by the DVNs and is ready for execution. Indicates the message passed all security checks.

PacketDeliveredEvent - Message Delivered

Emitted when a message is successfully delivered to the destination OApp. This confirms the cross-chain transaction completed.

ComposeSentEvent - Compose Message Queued

Emitted when a compose message is queued for sequential execution after the primary message. Used for complex multi-step operations.

ComposeDeliveredEvent - Compose Message Executed

Emitted when a compose message is successfully executed. Indicates the secondary operation completed successfully.

DelegateSetEvent - Delegate Updated

Emitted when an OApp delegate is set or changed. The delegate can configure settings on behalf of the OApp.

SendLibrarySetEvent - Send Library Configured

Emitted when the send library is configured for a specific destination. Tracks library changes for outbound messages.

ReceiveLibrarySetEvent - Receive Library Configured

Emitted when the receive library is configured for a specific source. Tracks library changes for inbound messages.

OAppRegisteredEvent - OApp Registration

Emitted when a new OApp is registered with the endpoint. This establishes the OApp's ability to send and receive messages.

Endpoint Errors

Common errors returned by the Endpoint program.

Unauthorized - Permission Denied

Thrown when the caller lacks required permissions for the operation. Only authorized addresses can perform certain actions.

InvalidNonce - Nonce Mismatch

Thrown when processing a message with an invalid nonce. Ensures messages are processed in the correct sequential order.

InvalidSender - Unauthorized Sender

Thrown when receiving a message from an unauthorized sender. Only configured peers can send messages to the OApp.

InvalidReceiver - Invalid Destination

Thrown when the specified receiver address is invalid or not configured properly for the destination chain.

LzTokenUnavailable - LayerZero Token Error

Thrown when LayerZero token operations fail or tokens are unavailable for fee payment.

OFT Events

Key events emitted by the OFT program during token operations. (Source)

OFTSent - Tokens Sent Cross-Chain

Emitted when tokens are sent to another chain. Contains the message GUID, destination chain ID, sender and recipient addresses, and the amount sent in both shared and local decimals.

OFTReceived - Tokens Received

Emitted when tokens are received from another chain. Contains the source chain ID, sender address, and the amount received after decimal conversion.

OFT Errors

Common errors returned by the OFT program. (Source)

Paused - OFT Operations Paused

Thrown when attempting operations while the OFT is paused. No transfers can be initiated until unpaused by the designated unpauser.

SlippageExceeded - Insufficient Received Amount

Thrown when the received amount after cross-chain transfer falls below the minimum acceptable amount due to decimal conversions or fees.

RateLimitExceeded - Transfer Rate Limit Hit

Thrown when a transfer exceeds the configured rate limits for the peer connection. Wait for the rate limit window to reset.

InvalidOptions - Invalid Message Options

Thrown when the provided LayerZero message options are invalid or incompatible with the OFT configuration.

InvalidAmount - Invalid Transfer Amount

Thrown when the transfer amount is zero, exceeds limits, or is otherwise invalid for the operation.

InvalidPeer - Peer Not Configured

Thrown when attempting to interact with a destination chain where no peer OFT has been configured. Use setPeerConfig() first.

Usage Tips

Getting Started

  1. Connect Your Wallet: Click "Connect Phantom Wallet" to connect your Solana wallet
  2. Select Network: Choose between Solana Mainnet and Devnet
  3. Custom RPC (Optional): If you encounter rate limits (403 errors), add a custom RPC URL:

Common Workflows

Sending Tokens Cross-Chain (OFT)

  1. Initialize your OFT with initOft()
  2. Configure peers with setPeerConfig()
  3. Get a quote with quoteOft() or quoteSend()
  4. Send tokens with send()

Setting Up Messaging (Endpoint)

  1. Register your OApp with registerOapp()
  2. Initialize nonce tracking with initNonce()
  3. Set up libraries with initSendLibrary() and initReceiveLibrary()
  4. Configure DVNs/executors with setConfig()
  5. Get quotes with quote() and send messages with send()

Troubleshooting

  • 403 Errors: Use a custom RPC URL instead of public endpoints
  • "Account does not exist": Ensure all required accounts have been initialized
  • "Invalid arguments": Check that byte arrays are properly formatted (0x prefix)
  • Simulation failures: This playground uses simplified encoding - use official SDKs for production

For production applications, always use the official LayerZero SDKs which provide proper type safety and encoding.