Skip to main content
Version: Endpoint V2

Sui Development Guidance

This page provides development guidance for building LayerZero applications on Sui, covering toolchain setup, operational practices, and technical constraints.

Toolchain Setup

Sui CLI

Tested Version: sui@v1.54.1

Install the Sui CLI:

cargo install --locked --git https://github.com/MystenLabs/sui.git --branch mainnet sui

Verify installation:

sui --version
# sui 1.54.1-...

Project Structure

Typical Sui Move project structure:

my-oapp/
├── Move.toml # Package manifest
├── sources/
│ ├── oapp.move # Main OApp logic
│ ├── config.move # Configuration
│ └── ...
├── tests/
│ └── oapp_tests.move # Unit tests
└── scripts/
└── deploy.sh # Deployment scripts

Move.toml Configuration

For package structure details:

[package]
name = "my_oapp"
version = "0.0.1"

[dependencies]
Sui = { git = "https://github.com/MystenLabs/sui.git", subdir = "crates/sui-framework/packages/sui-framework", rev = "mainnet" }
LayerZeroEndpoint = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", subdir = "packages/layerzero-v2/sui/contracts/endpoint-v2", rev = "main" }
LayerZeroOApp = { git = "https://github.com/LayerZero-Labs/LayerZero-v2.git", subdir = "packages/layerzero-v2/sui/contracts/oapps/oapp", rev = "main" }

[addresses]
my_oapp = "0x0"
sui = "0x2"

Package Verification

Sui supports package verification via SuiScan:

Verification Methods

  1. Source Upload: Upload source code directly to SuiScan
  2. API Verification: Use SuiScan's API for programmatic verification

Verification via SuiScan

  1. Navigate to https://suiscan.xyz/mainnet/package-verification
  2. Enter your package address
  3. Upload source files
  4. Wait for verification

Verification via API

curl -X POST https://suiscan.xyz/api/verify \
-H "Content-Type: application/json" \
-d '{
"packageId": "0x...",
"sourceCode": "...",
"network": "mainnet"
}'

Development Environment

Building Contracts

sui move build

Running Tests

sui move test

Local Development

Start a local Sui network:

sui start

Deployment

Deploying to Testnet

sui client publish \
--gas-budget 100000000 \
--json

Deploying to Mainnet

sui client switch --env mainnet
sui client publish \
--gas-budget 100000000 \
--json

Deployment Script Example

#!/bin/bash

# Build
echo "Building..."
sui move build

# Deploy
echo "Deploying..."
RESULT=$(sui client publish \
--gas-budget 100000000 \
--json)

# Extract package ID
PACKAGE_ID=$(echo $RESULT | jq -r '.objectChanges[] | select(.type=="published") | .packageId')

echo "Package ID: $PACKAGE_ID"

# Save to file
echo $PACKAGE_ID > deployed_package.txt

Operational Practices

Package Upgrades

Sui packages are immutable by default but can be made upgradeable.

UpgradeCap: Owned object granting upgrade authority

/// Automatically created when publishing with --with-unpublished-dependencies
public struct UpgradeCap has key, store {
id: UID,
package: ID, // Package being controlled
version: u64, // Current version
policy: u8, // Upgrade policy (compatible, additive, dep_only)
}

Transfer Upgrade Authority:

sui client transfer \
--to <NEW_OWNER_ADDRESS> \
--object-id <UPGRADE_CAP_OBJECT_ID> \
--gas-budget 10000000

Upgrade a Package:

sui client upgrade \
--upgrade-capability <UPGRADE_CAP_OBJECT_ID> \
--gas-budget 200000000

Key Point: Upgraded packages maintain compatibility with objects created by previous versions, provided you follow Sui's upgrade policies.

Capability Management

LayerZero uses multiple capability objects:

For OApp/OFT Packages:

  • CallCap: Authorizes creating Call objects (usually stored in package module)
  • AdminCap: Authorizes admin operations (transfer to new admin as needed)
  • MigrationCap: Authorizes migrating OApp/OFT to new implementations (store securely)
  • TreasuryCap<T>: Authorizes minting/burning coins (for OFT mint/burn type)
  • UpgradeCap: Authorizes package upgrades (transfer with caution)

Transfer Pattern:

// Transfer owned object to new owner
transfer::public_transfer(admin_cap, new_admin_address);

No Safe Transfer: Sui doesn't have EVM's safeTransfer callback. Transfers are direct:

transfer::public_transfer(object, recipient);  // Direct, no callback

Multisig Patterns

For multi-party control, use:

  1. Sui Multisig Addresses: Native 1-of-n or k-of-n multisig
  2. Shared Control Objects: Create a shared configuration object requiring multiple approvals
  3. Third-Party Solutions: Sui Wallet multisig, protocol-specific multisig

Example using address derivation:

# Create multisig address with multiple public keys
sui keygen multi-sig \
--pks <PUBKEY1> <PUBKEY2> <PUBKEY3> \
--weights 1 1 1 \
--threshold 2

Resource & Fee Models

See Sui Gas Model for complete details.

Storage Gas

Charged for storing data on-chain:

// Creating objects costs storage gas
let obj = MyObject { id: object::new(ctx), data: ... };
transfer::share_object(obj); // Storage charged here

Computation Gas

Charged for execution:

// Complex logic costs computation gas
public fun complex_operation(...) {
// Each instruction consumes gas
let result = heavy_computation();
// ...
}

Rebate Mechanism

When storage is freed, gas is refunded:

// Deleting objects triggers rebate
let MyObject { id, data } = obj;
object::delete(id); // Storage rebate issued

Important: This can result in negative gas utilization for net storage reduction.

Base Budget

Every transaction requires a minimum of 1000 gas units, even if net cost is negative due to rebates.

Technical Constraints

Package Size Limit

Maximum size per package: 250 KiB

If your package exceeds this:

  • Split into multiple packages
  • Reduce unused code
  • Optimize data structures

Transaction Size Constraints

See Sui Transaction Limits:

  • Max objects per transaction: 256
  • Max events per transaction: 1024
  • Max argument size: 128 KB

Compute Limits

Gas limits vary by network:

  • Testnet: Lower limits
  • Mainnet: Higher limits

For LayerZero operations, budget at least:

  • Simple send: 5,000,000 gas
  • Complex send: 20,000,000 gas
  • Receive: 10,000,000 gas

Network Resource Limits

Monitor these limits:

  • Object count per address: Unlimited, but impacts query performance
  • Storage per address: Unlimited, but costs scale linearly
  • Transaction throughput: ~5,000 TPS (network-wide)

Network Considerations

Finality

Sui uses a checkpoint-based finality system:

  • Soft finality: Certificate of transaction (milliseconds)
  • Hard finality: Checkpoint inclusion (~2-3 seconds)

For LayerZero verification, DVNs wait for checkpoint finality.

RPC Infrastructure

Public RPCs:

  • Mainnet: https://fullnode.mainnet.sui.io:443
  • Testnet: https://fullnode.testnet.sui.io:443
  • Devnet: https://fullnode.devnet.sui.io:443

Private RPC Providers:

  • Ankr
  • QuickNode
  • Blast API

For production, use private RPCs for better reliability and rate limits.

Channel Management

Recovery Methods

LayerZero provides recovery methods for stuck messages:

// Skip a message
public entry fun skip(
oapp: &mut OApp,
admin_cap: &AdminCap,
src_eid: u32,
sender: vector<u8>,
nonce: u64,
)

// Clear a message
public entry fun clear(
oapp: &mut OApp,
admin_cap: &AdminCap,
src_eid: u32,
sender: vector<u8>,
nonce: u64,
)

// Nilify a message
public entry fun nilify(
oapp: &mut OApp,
admin_cap: &AdminCap,
src_eid: u32,
sender: vector<u8>,
nonce: u64,
)

Authorization: All recovery methods require the AdminCap object.

Querying State with TypeScript SDKs

The Sui CLI has limitations for querying state. Use TypeScript SDKs instead:

import {SuiClient} from '@mysten/sui.js/client';
import {OApp} from '@layerzerolabs/lz-sui-sdk-v2';

const client = new SuiClient({url: 'https://fullnode.mainnet.sui.io:443'});

// Query peer configuration
const peer = await oapp.getPeer(client, 30101);

// Query nonce
const nonce = await oapp.getInboundNonce(client, 30101, senderBytes32);

// Query configuration
const config = await oapp.getConfig(client, 30101);

Sui CLI Limitations

The Sui CLI cannot easily:

  • Parse complex return values from view functions
  • Handle nested data structures
  • Decode bytes arrays

Workaround: Use the TypeScript SDK for all state queries.

Best Practices

1. Test Thoroughly

# Run unit tests
sui move test

# Run integration tests on testnet
sui client call --package $PKG ... --json

2. Monitor Gas Usage

# Use --gas-budget appropriately
sui client call \
--gas-budget 20000000 \ # Start higher
--json

3. Handle Rebates Correctly

// Don't assume gas cost is always positive
// Rebates can make net cost negative

4. Version Your Packages

[package]
name = "my_oapp"
version = "1.0.0" # Increment on upgrades

5. Secure Your Keys

# Use hardware wallets for mainnet
# Keep upgrade capabilities secure
# Use multisig for critical operations

Common Gotchas

1. Negative Gas Utilization

When storage is freed, transactions can have negative net gas cost. Budget at least 1000 base units.

2. Package Size Exceeded

Error: Package size exceeds maximum

Solution: Split into multiple packages or optimize code.

3. Object Ownership Errors

Error: Invalid object ownership

Solution: Verify object is owned by signer or is shared.

4. Insufficient Gas

Error: Insufficient gas

Solution: Increase --gas-budget parameter.

Additional Resources

Next Steps