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
For up-to-date Starknet endpoint IDs and LayerZero contract addresses, use V2 Protocol Contracts or query the Endpoint Metadata API.
RPC Endpoints
| Network | URL |
|---|---|
| Mainnet | <YOUR_MAINNET_RPC_URL> |
| Sepolia | <YOUR_SEPOLIA_RPC_URL> |
| Alchemy | https://starknet-mainnet.g.alchemy.com/v2/<key> |
| Infura | https://starknet-mainnet.infura.io/v3/<key> |
LayerZero Contract Addresses
Check the LayerZero Deployments page for current addresses:
| Contract | Address |
|---|---|
| EndpointV2 | See deployments page |
| ULN302 | See deployments page |
| LayerZero DVN | See deployments page |
| Executor | See 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):
| Metric | Limit |
|---|---|
| Max contract bytecode size | 81,920 felts |
| Max contract class size (Sierra file) | 4,089,446 bytes |
| Contract classes per tx | 1 |
Mitigation: Split large contracts into components or use libraries.
Storage
| Constraint | Details |
|---|---|
| Storage key | felt252 |
| Storage value | felt252 |
| Complex types | Serialized across multiple slots |
Compute
| Resource | Notes |
|---|---|
| Steps | Varies by transaction type |
| Builtins | Pedersen, Range Check, etc. |
| Memory | Managed 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
- Declare new contract version
- Call
upgrade(new_class_hash)on existing contract - 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>'
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
| Cause | Solution |
|---|---|
| Scarb version mismatch | Use Scarb 2.14.0 (matches starknet = "2.14.0" dependency) |
| Starknet Foundry version mismatch | Use snfoundry 0.53.0 with Scarb 2.14.0 |
| Stale build artifacts | Run scarb clean && scarb build before declaring |
| RPC version incompatibility | Use RPC v0.9.0+ (see snfoundry.toml section above) |
Version Compatibility Matrix
| Scarb | Cairo | Starknet Foundry | RPC Version |
|---|---|---|---|
| 2.14.0 | 2.14.0 | 0.53.0 | v0.9.0 - v0.10.0 |
| 2.13.1 | 2.13.1 | 0.49.0 | v0.9.0 |
Always match your starknet dependency version in Scarb.toml with your installed Scarb version. Run scarb --version to check.
Debugging Steps
-
Verify versions match:
scarb --version # Should show 2.14.0
sncast --version # Should show 0.53.0 -
Clean and rebuild:
scarb clean
rm -rf Scarb.lock
scarb build -
Check Scarb.toml dependency:
[dependencies]
starknet = "2.14.0" # Must match scarb version -
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
- OApp Overview - Building OApps
- OFT Overview - Token transfers
- Configuration Guide - Security setup
- Troubleshooting - Common errors