Skip to main content

General

Stellar uses Soroban, a smart contract platform where contracts are written in Rust and compiled to WebAssembly (WASM). The primary SDK is soroban-sdk (v25.1.1 for LayerZero contracts).
Key differences include:
  • Authorization: Uses require_auth() instead of msg.sender
  • Token standard: SEP-41 instead of ERC-20
  • Contract composition: Trait composition with proc macros instead of inheritance
  • Storage: Three-tiered storage (instance, persistent, temporary) with TTL management
  • Fee payment: Explicit SEP-41 token transfer instead of msg.value
  • Address format: 32 bytes (contract or account) instead of 20 bytes
See the Technical Overview for a detailed comparison.
No. LayerZero deploys and maintains the Endpoint V2 contract on Stellar. You only need to:
  1. Deploy your OApp or OFT contract, passing the Endpoint address in the constructor
  2. Set peers for each remote chain
  3. Configure enforced options and security stack (DVNs)
See OApp Overview for the deployment flow.

Development

Use Rust 1.90.0 or later with the wasm32v1-none target. Pin your version with a rust-toolchain.toml file:
The #[oapp] macro generates implementations for OAppCore, OAppSenderInternal, OAppReceiver, and OAppOptionsType3. Apply it to your contract struct together with #[lz_contract]:
You must always implement LzReceiveInternal for your receive logic. To customize specific traits, use #[oapp(custom = [receiver])].
  • LockUnlock: Tokens are locked in the OFT contract on send and unlocked on receive. Use this when you don’t control the token’s minting capability.
  • MintBurn: Tokens are burned on send and minted on receive via an external Mintable contract. Use this when you control the token supply.
Both modes are configured at deployment via the OftType enum in the constructor.
Stellar has contract addresses (C-addresses) and account addresses (G-addresses), both 32 bytes. LayerZero’s resolve_address utility handles disambiguation automatically by checking if a contract exists at the address. You typically don’t need to handle this yourself — the OFT contract resolves addresses when crediting tokens to recipients.
OFTs use shared decimals for crosschain consistency:
  • Local decimals: Token’s native decimals on the chain (e.g., 7 for Stellar)
  • Shared decimals: Crosschain precision (typically 6)
  • Conversion rate: 10^(local - shared)
When sending, amounts are divided by the conversion rate, removing dust. The truncated amount is sent crosschain, and the destination multiplies by its own conversion rate.For example, with local decimals = 7 and shared decimals = 6, the conversion rate is 10. An amount of 1,000,015 becomes 1,000,010 (the trailing 5 is dust).shared_decimals is set at deployment and cannot be changed. Use 6 for compatibility with most chains.See OFT Overview - Decimal Precision for details.

Gas and Fees

Unlike EVM where fees are paid via msg.value, Stellar requires explicit SEP-41 token transfers. The OApp base contract handles this internally — it transfers XLM (native token) to the Endpoint before calling send. As a developer, you just pass the fee from the quote function (__quote for OApp, quote_send for OFT) to the send function.
Soroban uses resource-based fees rather than gas. You’re charged for:
  • CPU instructions: Computation cost
  • Memory: Runtime memory allocation
  • Storage I/O: Read/write bytes and entries
  • Transaction size: Byte size of the transaction
Transactions declare resource limits upfront. The Stellar CLI estimates these automatically during simulation.
Soroban storage operations are priced to reflect the real cost of maintaining ledger state. Each storage entry requires rent to maintain its TTL. Writing to persistent storage is more expensive than temporary storage. To minimize costs, keep your storage footprint small and use temporary storage where possible.

Configuration

DVN configuration is done through the ULN-302 message library via the Endpoint’s set_config function. You provide an XDR-encoded OAppUlnConfig with config type 2 (send) or 3 (receive). See DVN & Executor Config for detailed instructions.
Enforced options define the minimum execution parameters (gas limit, native value) for messages sent to each destination. Without them, messages may fail on the destination chain due to insufficient gas. Always set enforced options for each destination and message type.
Where EVM uses ABI encoding, Soroban uses XDR (External Data Representation) for config encoding. This means ULN configs, executor configs, and other message library settings are XDR-encoded. The TypeScript SDK handles this automatically, but if you’re using CLI directly, you’ll need to provide XDR-encoded config values.
Yes. The contract owner can modify:
  • Peers: set_peer to add or update remote chain connections
  • Enforced options: set_enforced_options to adjust gas limits per destination
  • Delegate: set_delegate to assign or change the operational delegate
The delegate can configure:
  • Message libraries: Send and receive library selection
  • DVN and Executor settings: Via the Endpoint’s set_config function
See DVN & Executor Config for configuration details.

Troubleshooting

  1. Check LayerZero Scan: Look up the message GUID on LayerZero Scan to see its status.
  2. Verify peers: Ensure peers are set on both the source and destination chains.
  3. Check DVN status: The message may be waiting for DVN verification. Use the receive library’s verifiable (e.g. on ULN-302) to check whether DVNs have reached quorum.
  4. Check executor: The executor may not have delivered yet. Check endpoint.inbound_payload_hash(receiver, src_eid, sender, nonce) to see if it’s been verified but not delivered.
  5. Check enforced options: Insufficient gas on the destination can cause delivery to fail silently.
If a message is verified but delivery fails, you have several recovery options:
  • Retry delivery: The executor will typically retry automatically.
  • Skip: Skip the nonce if using ordered delivery: endpoint.skip()
  • Nilify: Void the message (can be re-verified later): endpoint.nilify()
  • Burn: Permanently destroy a nonce (irreversible): endpoint.burn()
  • Clear: Remove the payload: endpoint.clear()
See Technical Overview - Recovery Operations for details.
Soroban persistent storage entries have a TTL. If the TTL expires, the entry is archived (not deleted, but inaccessible until restored). This can happen to peer mappings, rate limit state, or other persistent data if the contract hasn’t been called for an extended period.Solution: Restore the entry with stellar contract restore or call any function that accesses the entry (TTL is automatically extended on access).

Security

To prevent accidental ownership loss, use the 2-step flow:
  1. Current owner calls begin_ownership_transfer(new_owner, ttl) — proposes transfer with a TTL window
  2. New owner calls accept_ownership() — confirms within the TTL
  3. If not accepted in time, the proposal expires and the original owner retains ownership
A single-step transfer_ownership is also available but is immediate and irreversible.This ensures the new owner can actually receive and operate the contract.
DVNs on Stellar are implemented as Soroban custom account contracts with multisig verification. They use secp256k1 signatures (compatible with EVM signers) for the multisig quorum, plus an optional Ed25519 admin key. Transaction data is hashed with keccak256, enabling crosschain signer reuse.

Next Steps