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
- Source Upload: Upload source code directly to SuiScan
- API Verification: Use SuiScan's API for programmatic verification
Verification via SuiScan
- Navigate to https://suiscan.xyz/mainnet/package-verification
- Enter your package address
- Upload source files
- 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 creatingCallobjects (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:
- Sui Multisig Addresses: Native 1-of-n or k-of-n multisig
- Shared Control Objects: Create a shared configuration object requiring multiple approvals
- 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
- 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.