Cairo Declare and Deploy: Separating Contract Logic from State on StarkNet
Cairo on StarkNet separates publishing bytecode from creating a contract instance. Learn how declare and deploy work, why class hashes matter, and how to verify the result.
26 Mar 2026, 12:38 UTC

The Problem: One Deployment, Two Different Identities
You deploy a Cairo contract to StarkNet testnet, record the address in your frontend, and later push a bug fix. You run the deployment command again, but the network returns a new address. Your frontend still points to the old contract, which still contains the bug. This happens because Cairo splits publishing code from creating state, and many teams treat the two steps as one.
The useful takeaway is simple: declare publishes the compiled bytecode and returns a class hash, while deploy creates a contract instance at an address using that class hash. The class hash identifies logic; the address identifies state.
What Declare Actually Publishes
First, compile the Cairo source with starknet-compile. This turns human-readable Cairo into a JSON artifact the network can execute. When you run starknet declare, you send that artifact to StarkNet. The network computes a class hash, a deterministic fingerprint of that specific bytecode version. Anyone compiling the same code with the same compiler version gets the same class hash.
Two terms matter here. A class hash identifies the contract's logic on a specific network. A contract address identifies a deployed instance and its storage. Constructor calldata is the typed input passed to the contract's initializer, and each value must fit inside a 256-bit field element, StarkNet's native numeric type.
Version assumption: the CLI and compiler version must match what the target StarkNet network expects. A mismatch can produce an undeclared class error or a rejected transaction.
Worked Example: Declare, Then Deploy
Run these commands from a local terminal or CI job. You need the StarkNet CLI installed, a configured account with enough ETH to pay transaction fees, and network access to the target StarkNet network.
# 1. Compile the contract source into a JSON artifact
starknet-compile token.cairo --output token.json
# 2. Declare the class and capture the class hash
starknet declare --contract token.json --network testnet
# The response includes a class_hash field. Save this value.
# 3. Deploy an instance using the class hash
starknet deploy --class_hash <CLASS_HASH> --constructor_calldata <INITIAL_SUPPLY> --network testnet
# The response includes the deployed contract address.
Replace <CLASS_HASH> with the hash returned by declare. Replace <INITIAL_SUPPLY> with a constructor argument formatted as a field element. The deploy step is deterministic: the same class hash, deployer, calldata, and salt produce the same address, which lets you pre-compute addresses for frontends or other contracts.
Verification
After deployment, confirm the address is live:
starknet get-contract-address --address <CONTRACT_ADDRESS> --network testnet
Cross-check the returned class hash against the value from your declare step. You can also paste the address into a StarkNet block explorer to verify the linked class and transaction status. Risks include paying declare and deploy fees on a class that later changes, and using a testnet class hash on mainnet, which will not resolve.
Limitations and Upgrade Planning
- Immutable logic: A declared class cannot be edited. A bug fix requires a new declare, which creates a new class hash and, unless you use a proxy, a new contract address.
- Network specificity: Class hashes are valid only on the network where they were declared. Declare separately on testnet and mainnet.
- Calldata constraints: Constructor arguments must be strictly typed field elements. Mismatched types or out-of-range values cause deployment failure.
Practical check: store the class hash in a CI artifact after each declare. Before deploying, compare the new hash to the stored one. If they match, skip redeclare and reuse the existing class. If they differ, plan for an address change or implement a proxy pattern that points a stable address to the new class hash.
CI Checklist for Releases
- Compile the contract on every push to the release branch.
- Declare only when the class hash changes; cache the hash to avoid duplicate transactions.
- Deploy using the cached class hash and constructor calldata.
- Save the resulting contract address to a deployment artifact.
- Assert the address matches the expected value and verify it on a block explorer before marking the release complete.
There is no direct rollback for a declared class or deployed contract. If a deployment fails, inspect the transaction revert reason, fix the calldata or compiler version, and redeploy. Treat the class hash as the source of truth for logic and the address as the source of truth for state.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.