Skip to main content
Version: Endpoint V2

Starknet Development Guidance

This guide covers testing, networking, authority management, constraints, and upgradeability for LayerZero contracts on Starknet. For toolchain installation and account setup, see Getting Started. For project scaffolding and deployment steps, see the OApp and OFT overviews.

Project Scaffolding, Build, and Deploy

For project layout, Scarb.toml, build, declare/deploy, and verification steps, see:


Testing

Running Tests

# Run all tests
snforge test

# Run specific test
snforge test test_send_tokens

# Run tests with detailed traces
snforge test --trace-verbosity detailed

# Run tests matching pattern
snforge test test_oft

Test Structure

use snforge_std::{
declare, ContractClassTrait, DeclareResultTrait,
start_cheat_caller_address, stop_cheat_caller_address,
};

#[test]
fn test_oft_send() {
// Deploy contract
let contract = declare("MyOFT").unwrap().contract_class();
let constructor_calldata = array![
endpoint.into(),
owner.into(),
native_token.into(),
];
let (contract_address, _) = contract.deploy(@constructor_calldata).unwrap();

// Create dispatcher
let oft = IOFTDispatcher { contract_address };

// Cheat caller for owner operations
start_cheat_caller_address(contract_address, owner);

// Set peer
oft.set_peer(30101, peer_address);

// Verify
let stored_peer = oft.get_peer(30101);
assert(stored_peer == peer_address, 'peer mismatch');

stop_cheat_caller_address(contract_address);
}

Test Utilities

use snforge_std::{
start_cheat_caller_address, // Mock caller
stop_cheat_caller_address,
start_cheat_block_timestamp, // Mock block time
stop_cheat_block_timestamp,
spy_events, // Capture events
EventSpyAssertionsTrait,
};

Network Configuration

Endpoint IDs and addresses

For up-to-date Starknet endpoint IDs and LayerZero contract addresses, use V2 Protocol Contracts or query the Endpoint Metadata API.

RPC Endpoints

NetworkURL
Mainnet<YOUR_MAINNET_RPC_URL>
Sepolia<YOUR_SEPOLIA_RPC_URL>
Alchemyhttps://starknet-mainnet.g.alchemy.com/v2/<key>
Infurahttps://starknet-mainnet.infura.io/v3/<key>

LayerZero Contract Addresses

Check the LayerZero Deployments page for current addresses:

ContractAddress
EndpointV2See deployments page
ULN302See deployments page
LayerZero DVNSee deployments page
ExecutorSee deployments page

Public Key vs Address

Starknet accounts are smart contracts (native account abstraction), so an account address is a contract address, not a public-key-derived identifier. There is no default deterministic link between the key(s) controlling an account and its address, and keys can be rotated by the account logic. See Accounts and Account keys and addresses derivation standard.

Practical takeaways:

  • Use the account address anywhere an API expects a ContractAddress.
  • Treat the public key as signer metadata owned by the account contract, not as the account identifier.

Authority Management

Ownership Pattern

LayerZero Starknet contracts use OpenZeppelin's OwnableComponent:

// Transfer ownership
fn transfer_ownership(ref self: ContractState, new_owner: ContractAddress) {
self.ownable.transfer_ownership(new_owner);
}

// Renounce ownership (irreversible!)
fn renounce_ownership(ref self: ContractState) {
self.ownable.renounce_ownership();
}

Delegate Pattern

Set a delegate for configuration without transferring ownership:

// Set delegate via Endpoint
fn set_delegate(ref self: ContractState, delegate: ContractAddress) {
let endpoint = IEndpointV2Dispatcher {
contract_address: self.oapp_core.OAppCore_endpoint.read()
};
endpoint.set_delegate(delegate);
}

Delegates can:

  • Set library configurations
  • Update DVN settings
  • Manage pathway configurations

Delegates cannot:

  • Transfer ownership
  • Set peers (owner only)

Technical Constraints

Contract Size

Starknet has contract size limits (see the chain info cheat sheet for current values):

MetricLimit
Max contract bytecode size81,920 felts
Max contract class size (Sierra file)4,089,446 bytes
Contract classes per tx1

Mitigation: Split large contracts into components or use libraries.

Storage

ConstraintDetails
Storage keyfelt252
Storage valuefelt252
Complex typesSerialized across multiple slots

Compute

ResourceNotes
StepsVaries by transaction type
BuiltinsPedersen, Range Check, etc.
MemoryManaged by Cairo VM

Upgradeability

Upgradeable Contracts

Use OpenZeppelin's UpgradeableComponent:

use openzeppelin::upgrades::UpgradeableComponent;

component!(path: UpgradeableComponent, storage: upgradeable, event: UpgradeableEvent);

#[external(v0)]
fn upgrade(ref self: ContractState, new_class_hash: ClassHash) {
self.ownable.assert_only_owner();
self.upgradeable.upgrade(new_class_hash);
}

Upgrade Process

  1. Declare new contract version
  2. Call upgrade(new_class_hash) on existing contract
  3. Verify new implementation
# Declare new version
sncast declare --contract-name MyOFTv2

# Upgrade existing contract
sncast invoke \
--contract-address <EXISTING_CONTRACT> \
--function upgrade \
--network sepolia \
--arguments '<NEW_CLASS_HASH>'
warning

Ensure storage layout compatibility between versions!


Common Commands Reference

Scarb Commands

scarb build          # Build contracts
scarb clean # Clean build artifacts
scarb fmt # Format code
scarb test # Run Cairo tests (non-blockchain)

snforge Commands

snforge test                    # Run all tests
snforge test <name> # Run specific test
snforge test -v # Verbose output
snforge test --coverage # Generate coverage

sncast Commands

sncast account create           # Create new account
sncast account deploy # Deploy account contract
sncast declare # Declare contract class
sncast deploy # Deploy contract instance
sncast invoke # Call external function
sncast call # Call view function
sncast tx-status <hash> # Check transaction status

Class Hash Mismatch Errors

When declaring contracts, you may encounter:

Error: Mismatch compiled class hash for class with hash 0x...
Actual: 0x..., Expected: 0x...

This error occurs when the CASM (Cairo Assembly) hash computed locally doesn't match what the network expects.

Common Causes

CauseSolution
Scarb version mismatchUse Scarb 2.14.0 (matches starknet = "2.14.0" dependency)
Starknet Foundry version mismatchUse snfoundry 0.53.0 with Scarb 2.14.0
Stale build artifactsRun scarb clean && scarb build before declaring
RPC version incompatibilityUse RPC v0.9.0+ (see snfoundry.toml section above)

Version Compatibility Matrix

ScarbCairoStarknet FoundryRPC Version
2.14.02.14.00.53.0v0.9.0 - v0.10.0
2.13.12.13.10.49.0v0.9.0
tip

Always match your starknet dependency version in Scarb.toml with your installed Scarb version. Run scarb --version to check.

Debugging Steps

  1. Verify versions match:

    scarb --version    # Should show 2.14.0
    sncast --version # Should show 0.53.0
  2. Clean and rebuild:

    scarb clean
    rm -rf Scarb.lock
    scarb build
  3. Check Scarb.toml dependency:

    [dependencies]
    starknet = "2.14.0" # Must match scarb version
  4. Verify RPC version:

    curl -X POST "<YOUR_RPC_URL>" \
    -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","method":"starknet_specVersion","params":[],"id":1}'
    # Should return "0.9.0" or "0.10.0"

Next Steps