Documentation Index
Fetch the complete documentation index at: https://docs.layerzero.network/llms.txt
Use this file to discover all available pages before exploring further.
This page lists common errors you may encounter when developing LayerZero applications on Sui, along with their causes and solutions.
Deployment Issues
Git Dependencies Failed
Error Message:
Error: Package dependency does not specify published address
Error: Failed to resolve dependencies
Cause: Git dependencies for LayerZero packages don’t work due to missing Move.toml manifests in subdirectories.
Solution: Use local dependencies instead:
# Clone LayerZero repo
git clone https://github.com/LayerZero-Labs/LayerZero-v2.git
# Update Move.toml to use local paths
[dependencies]
OApp = { local = "../LayerZero-v2/packages/layerzero-v2/sui/contracts/oapps/oapp" }
EndpointV2 = { local = "../LayerZero-v2/packages/layerzero-v2/sui/contracts/endpoint-v2" }
# ... other packages
Or use published package addresses (see Deployed Contracts).
Unpublished Dependencies Error
Error Message:
Error: Modules in package '<package>' were not published with the '--with-unpublished-dependencies' flag
Cause: Package has dependencies that aren’t published onchain.
Solution: Add the flag when publishing:
sui client publish --with-unpublished-dependencies --gas-budget 200000000
Package Size Exceeded
Error Message:
Error: Package size (260 KB) exceeds maximum allowed size (250 KB)
Cause: Your package exceeds Sui’s 250 KiB limit per package object. See Sui transaction limits.
Solutions:
- Split into multiple packages
- Remove unused dependencies
- Optimize data structures
- Move large constants off-chain
Example Split:
// Package 1: Core logic
module my_oapp::core {
// Essential functions
}
// Package 2: Utilities
module my_oapp::utils {
// Helper functions
}
Insufficient Gas for Deployment
Error Message:
Error: Insufficient gas: needed 150000000, available 100000000
Cause: Deployment requires more gas than budgeted.
Solution: Increase gas budget:
sui client publish --gas-budget 200000000
Deployment typically requires:
- Simple packages: 50-100M gas
- Complex packages: 100-200M gas
- With dependencies: 200M+ gas
Upgrade Authority Issues
Error Message:
Error: UpgradeCap not found or not owned by signer
Cause: The signer doesn’t own the UpgradeCap for the package.
Solution:
- Verify you’re using the correct account
- Check UpgradeCap ownership:
sui client objects | grep UpgradeCap
- Transfer UpgradeCap if needed
Configuration Issues
Channel Not Initialized
Error Message:
Error: Channel not initialized for endpoint ID 30101
Cause: Attempting to send message before initializing the messaging channel.
Solution: Initialize channel first:
sui client call \
--package $PACKAGE \
--module oapp \
--function initialize_channel \
--args $OAPP_OBJECT $ADMIN_CAP 30101 \
--gas-budget 10000000
Peer Not Set
Error Message:
Error: Peer address not configured for endpoint ID 30101
Cause: No peer OApp address configured for the destination chain.
Solution: Set peer address:
sui client call \
--package $PACKAGE \
--module oapp \
--function set_peer \
--args $OAPP_OBJECT $ADMIN_CAP 30101 $PEER_ADDRESS_BYTES \
--gas-budget 10000000
Address Format: Ensure peer address is 32 bytes (pad EVM addresses).
Library Configuration Missing
Error Message:
Error: Send library not configured for endpoint ID 30101
Cause: Custom library set but not properly configured.
Solution: Either:
- Use default libraries (don’t set custom)
- Or properly configure custom library:
sui client call \
--package $PACKAGE \
--module oapp \
--function set_send_library \
--args $OAPP_OBJECT $ADMIN_CAP 30101 $LIBRARY_ADDRESS \
--gas-budget 10000000
Configuration Errors
Peer Address Error (oapp_registry abort)
Error Message:
Error: oapp_registry::get_messaging_channel abort code: 1
Error: MoveAbort in module oapp_registry
Cause: Remote chain is using wrong receiver address - likely using object ID instead of package ID.
Solution: On Sui, peer addresses must be package IDs, not object IDs.
Find Your Correct Package ID:
# Query your OApp/OFT object
sui client object <YOUR_OAPP_OBJECT_ID> --json | jq '.data.type'
# Output example:
# "0x061a47bffa630b8cd3735f8479edf7ab7897863fb3b796e77ebb8786af6f1bfc::oapp::OApp"
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
# This is your package ID - use as peer address!
Update Peer on Remote Chain:
// On EVM/Solana/other chains, use Sui package ID:
await oapp.setPeer(
30230, // Sui mainnet EID
'0x061a47bffa630b8cd3735f8479edf7ab7897863fb3b796e77ebb8786af6f1bfc', // Package ID
);
Why Package ID?
- Sui OApps use
CapType::Package for CallCap
- Registry and verification systems key by package address
- Object IDs are instance-specific, package ID is deployment-specific
Package ID vs Object ID Confusion
Error Message:
Error: Transaction was not signed by the correct sender
Error: Object ID does not exist
Cause: Using package ID when object ID is required (or vice versa).
Key Differences:
- Package ID: Address of published code (immutable bytecode)
- Object ID: Address of object instance (mutable state)
Example:
// - Wrong: Using package ID as object
tx.object(packageId); // Package is not an object!
// - Correct: Use object ID
tx.object(oappObjectId); // The OApp object instance
How to Find:
# View transaction outputs after publishing
sui client publish --json
# objectChanges array shows:
# - "published" type = package ID
# - "created" type = object IDs
Invalid BCS Bytes Error
Error Message:
Error: InvalidBCSBytes
Error: Unable to deserialize config
Error: Failed to deserialize argument at index 6
Cause: Using tx.pure() instead of SDK’s asBytes() helper for byte array parameters.
Solution: Use the SDK’s asBytes() helper:
import {SDK, OAppUlnConfigBcs} from '@layerzerolabs/lz-sui-sdk-v2';
// CRITICAL: Import asBytes helper
const {asBytes} = await import('@layerzerolabs/lz-sui-sdk-v2');
// Encode configuration
const config = OAppUlnConfigBcs.serialize({
use_default_confirmations: false,
use_default_required_dvns: false,
use_default_optional_dvns: true,
uln_config: {
confirmations: 15,
required_dvns: [dvnAddress],
optional_dvns: [],
optional_dvn_threshold: 0,
},
}).toBytes();
const tx = new Transaction();
// - WRONG: Using tx.pure() causes InvalidBCSBytes
tx.pure(config, 'vector<u8>');
// - CORRECT: Use asBytes() helper
asBytes(tx, config);
// In context:
tx.moveCall({
target: '...',
arguments: [
// ... other args
asBytes(tx, config), // ← This works
],
});
Why asBytes() is Required:
The SDK’s asBytes() function performs proper BCS vector wrapping:
// Actual implementation from SDK utils/index.ts
export function asBytes(
tx: Transaction,
bytes: Uint8Array | TransactionArgument,
): TransactionArgument {
if (isTransactionArgument(bytes)) {
return bytes;
}
// Wraps in BCS vector encoding
return tx.pure(bcs.vector(bcs.u8()).serialize(Array.from(bytes)).toBytes());
}
What it does:
- Takes raw bytes and wraps them in BCS vector format
- Handles Transaction Argument pass-through
- Ensures proper deserialization in Move’s
vector<u8> type
Why tx.pure() fails:
- Direct
tx.pure(bytes, 'vector<u8>') doesn’t apply BCS vector wrapping
- Move deserializer expects BCS-encoded vector format
- Results in
InvalidBCSBytes error
Common Scenarios Requiring asBytes():
-
-
-
-
- Any
vector<u8> config parameter
Execution Errors
Executor Transaction Fails (UnusedValueWithoutDrop)
Error Message:
Executor transaction simulation reverted
UnusedValueWithoutDrop { result_idx: 3, secondary_idx: 0 }
Error during lz_receive execution
Cause: Executor can’t properly build the PTB to call your OApp/OFT’s lz_receive() function.
Most Common Reason for OFTs: Missing or incorrect lz_receive_info during registration.
Solution for OFTs:
- Generate proper lz_receive_info:
const tx = new Transaction();
const [lzReceiveInfo] = tx.moveCall({
target: `${oftPackage}::oft_ptb_builder::lz_receive_info`,
typeArguments: [tokenType],
arguments: [
tx.object(oftObjectId),
tx.object(endpointObjectId),
tx.object('0xfbece0b75d097c31b9963402a66e49074b0d3a2a64dd0ed666187ca6911a4d12'), // OFTComposerManager
tx.object('0x6'), // Clock
],
});
const result = await client.devInspectTransactionBlock({
transactionBlock: tx,
sender: yourAddress,
});
const lzReceiveInfoBytes = bcs.vector(bcs.u8()).parse(...);
- Update OApp info in registry:
import {asBytes} from '@layerzerolabs/lz-sui-sdk-v2';
const tx = new Transaction();
tx.moveCall({
target: `${oappPackage}::endpoint_calls::set_oapp_info`,
arguments: [
tx.object(oappObjectId),
tx.object(adminCapId),
tx.object(endpointObjectId),
asBytes(tx, oappInfoBytes), // Includes lz_receive_info
],
});
await client.signAndExecuteTransaction({transaction: tx});
Prevention: Always provide lz_receive_info during initial OFT registration (see OFT Overview).
OApp Registry Error
Error Message:
Error: oapp_registry::get_messaging_channel abort code: 1
Error: MoveAbort in module oapp_registry
Cause: Remote chain is using wrong receiver address - using object ID instead of package ID.
Solution:
On Sui, peer addresses must be package IDs:
# Find your package ID
sui client object <YOUR_OAPP_OBJECT_ID> --json | jq '.data.type'
# Example: "0x061a47bf...::oapp::OApp"
# ^^^^^^^^^^^^
# Use this package ID as peer on remote chains
Update peer on remote chain:
// On EVM
oapp.setPeer(30230, bytes32(0x061a47bffa630b8cd3735f8479edf7ab7897863fb3b796e77ebb8786af6f1bfc)); // Package ID
Why: Sui uses Package CallCaps where callCap.id() returns the package address.
Runtime Errors
Call Object Not Consumed
Error Message:
Error: unused value without 'drop' ability
Error: unused value of type 'call::call::Call<...>'
Cause: A Call object was not properly consumed before the transaction ended.
Root Causes:
- Missing confirmation call (e.g.,
confirm_lz_send)
- PTB doesn’t route the
Call through all required modules
Call object created but never destroyed
Solution:
// - Incorrect: Call not confirmed
let call = oapp::send(&mut oapp, &call_cap, ...);
// Transaction ends → ERROR
// - Correct: Call confirmed and destroyed
let call = oapp::send(&mut oapp, &call_cap, ...);
// PTB processes the Call through Endpoint/ULN/Workers
let (params, receipt) = oapp::confirm_lz_send(&oapp, &call_cap, call);
Debug Checklist:
Invalid Recipient
Error Message:
Error: Invalid recipient: object not owned by recipient address
Cause: Trying to send tokens to an invalid or non-existent address.
Solution:
- Verify recipient address is valid
- For token sends, check if recipient needs an account created
- Ensure address format is correct (32 bytes)
Gas Estimation Failures
Error Message:
Error: Unable to estimate gas for transaction
Cause: Transaction simulation failed during gas estimation.
Solutions:
- Check transaction parameters are valid
- Verify all required objects exist
- Ensure signer has necessary permissions
- Try with higher gas budget
Debug:
# Dry run to see simulation errors
sui client call ... --json --dry-run
Transaction Issues
PTB Construction Failures
Error Message:
Error: Invalid PTB: missing required call
Cause: Programmable Transaction Block doesn’t include all required calls.
Solution: Verify PTB structure:
// Correct PTB structure for send
const tx = new Transaction();
// 1. Call OApp
tx.moveCall({
target: `${oappPackage}::oapp::send`,
arguments: [
/* ... */
],
});
// 2. PTB will route Hot Potatoes automatically
// 3. Confirm calls are added by the builder
await client.signAndExecuteTransaction({transaction: tx});
Object Ownership Errors
Error Message:
Error: Object 0x... is not owned by sender
Error: InvalidObjectOwnership
Cause: Trying to use an owned object that belongs to a different address.
Solutions:
- Verify object ownership:
sui client object <OBJECT_ID> --json | jq '.data.owner'
Output types:
{"AddressOwner": "0x..."} - Owned by specific address
"Shared" - Shared object (accessible to anyone)
"Immutable" - Immutable object (read-only)
-
Use correct signer: Ensure the transaction signer owns the object
-
Check object type:
Example:
// - Correct: AdminCap owned by signer
public fun set_peer(
oapp: &mut OApp, // Shared object (anyone can reference)
admin_cap: &AdminCap, // Owned object (must own to use)
...
)
Storage Rebate Confusion
Error Message (not actually an error):
Gas used: -500000 (negative)
Cause: Transaction freed storage, resulting in a rebate.
Explanation: This is normal behavior, not an error. When storage is freed:
- You get a rebate for the freed storage
- Net gas cost can be negative
- Base budget of 1000 is still required
Example:
// Deleting object frees storage
let MyObject { id, data } = obj;
object::delete(id); // Triggers rebate
SDK Errors
Connection Timeout
Error Message:
Error: Request timeout: No response from RPC
Cause: RPC endpoint is slow or unresponsive.
Solutions:
- Use a different RPC endpoint
- Increase timeout:
const client = new SuiClient({
url: 'https://fullnode.mainnet.sui.io:443',
timeout: 60000, // 60 seconds
});
- Consider using a private RPC provider
Invalid Object ID
Error Message:
Error: Invalid object ID format
Cause: Object ID is not properly formatted.
Solution: Ensure object IDs are 32-byte hex strings:
// - Correct
const objectId = '0x1234...'; // 64 hex chars (32 bytes)
// - Incorrect
const objectId = '0x123'; // Too short
const objectId = '1234...'; // Missing 0x prefix
Type Mismatch
Error Message:
Error: Type mismatch: expected '0x...::coin::Coin<0x...::token::TOKEN>', got '0x...::coin::Coin<0x2::sui::SUI>'
Cause: Wrong coin type passed to function.
Solution: Verify coin types match:
// Check coin type
const coin = await client.getObject({id: coinId});
console.log('Coin type:', coin.data?.type);
// Use correct coin type
const result = await oft.send({
tokenMint: '0x...::token::TOKEN', // Must match
// ...
});
Debugging Tips
Enable Verbose Logging
# Sui CLI with verbose output
sui client call ... --json | jq .
Check Transaction Effects
const result = await client.signAndExecuteTransaction({
transaction: tx,
options: {
showEffects: true,
showEvents: true,
showObjectChanges: true,
},
});
console.log('Effects:', result.effects);
console.log('Events:', result.events);
console.log('Object changes:', result.objectChanges);
Inspect Objects
# View object details
sui client object $OBJECT_ID --json
# View all objects for an address
sui client objects --json
Use Sui Explorer
Navigate to SuiScan to:
- View transaction details
- Check object states
- Inspect event logs
- Verify package deployments
Test on Devnet First
Always test on devnet before testnet/mainnet:
# Switch to devnet
sui client switch --env devnet
# Test your calls
sui client call ... --gas-budget 20000000
Getting Help
If you continue to experience issues:
- Check Documentation: Review Sui Documentation
- Search Discord: Look for similar issues in LayerZero Discord
- Ask for Help: Post in Discord with:
- Error message
- Transaction hash (if available)
- Code snippet
- What you’ve tried
Next Steps