--- name: cairo-deploy description: Deployment guidance for Cairo contracts on Starknet covering sncast commands, account setup, declare/deploy workflow, network configuration, and contract verification. license: Apache-2.0 metadata: {"author":"starknet-agentic","version":"1.0.0","org":"keep-starknet-strange"} keywords: [cairo, deploy, sncast, starknet, devnet, sepolia, mainnet, declare, verification] allowed-tools: [Bash, Read, Write, Glob, Grep, Task] user-invocable: true --- # Cairo Deploy Reference for deploying Cairo smart contracts to Starknet using sncast (Starknet Foundry). ## When to Use - Deploying contracts to Starknet devnet, Sepolia, or mainnet - Declaring contract classes - Setting up deployer accounts - Configuring network endpoints - Verifying deployed contracts - Invoking/calling deployed contracts ## When NOT to Use - Writing or refactoring contract logic (`cairo-contract-authoring`). - Unit, integration, or fuzz testing (`cairo-testing`). - Gas or performance optimization (`cairo-optimization`). - Security review of existing code (`cairo-auditor`). ## Quick Start 1. Build with `scarb build`, then declare and deploy with `sncast`. 2. Use [skills catalog](../README.md) when the task moves back into authoring, testing, or auditing. ## Setup ### Install Starknet Foundry ```bash # Install via asdf (recommended for version pinning) asdf plugin add starknet-foundry asdf install starknet-foundry 0.56.0 asdf global starknet-foundry 0.56.0 # Or install directly curl -L https://raw.githubusercontent.com/foundry-rs/starknet-foundry/master/scripts/install.sh | sh snfoundryup ``` ### .tool-versions Pin versions for reproducible builds: ``` scarb 2.15.1 starknet-foundry 0.56.0 ``` > **Note:** snforge 0.56.0 requires Scarb >= 2.12.0. Check [github.com/foundry-rs/starknet-foundry/releases](https://github.com/foundry-rs/starknet-foundry/releases) for the latest. ## Build ```bash # Build contracts (generates Sierra + CASM) scarb build ``` Output goes to `target/dev/`: - `myproject_MyContract.contract_class.json` (Sierra) - `myproject_MyContract.compiled_contract_class.json` (CASM) ## Account Setup ### Create a New Account ```bash # Generate account on Sepolia sncast account create \ --url https://starknet-sepolia.g.alchemy.com/v2/YOUR_KEY \ --name my-deployer # This outputs the account address — fund it with ETH/STRK before deploying # Deploy the account contract sncast account deploy \ --url https://starknet-sepolia.g.alchemy.com/v2/YOUR_KEY \ --name my-deployer ``` ### Import Existing Account ```bash sncast account import \ --url https://starknet-sepolia.g.alchemy.com/v2/YOUR_KEY \ --name my-deployer \ --address 0x123... \ --private-key 0xabc... \ --type oz ``` Account types: `oz` (OpenZeppelin), `argent`, `braavos` ## sncast.toml Configure defaults to avoid repeating flags: ```toml [default] url = "https://starknet-sepolia.g.alchemy.com/v2/YOUR_KEY" account = "my-deployer" accounts-file = "~/.starknet_accounts/starknet_open_zeppelin_accounts.json" wait = true [mainnet] url = "https://starknet-mainnet.g.alchemy.com/v2/YOUR_KEY" account = "mainnet-deployer" ``` Use profiles: `sncast --profile mainnet declare ...` ## Declare (Register Class) Before deploying, declare the contract class on-chain: ```bash # Declare contract sncast declare \ --contract-name MyContract # Output: # class_hash: 0x1234... # transaction_hash: 0xabcd... ``` If the class is already declared, sncast will tell you — that's fine, use the existing class hash. ## Deploy (Create Instance) ```bash # Deploy with constructor args sncast deploy \ --class-hash 0x1234... \ --constructor-calldata 0xOWNER_ADDRESS # Multiple constructor args (space-separated) sncast deploy \ --class-hash 0x1234... \ --constructor-calldata 0xOWNER 0xTOKEN_ADDRESS 1000 ``` ### Constructor Calldata Encoding Arguments are passed as felt252 values: - `ContractAddress` — pass as hex `0x123...` - `u256` — pass as TWO felts: `low high` (e.g., `1000 0` for 1000) - `felt252` — pass directly - `bool` — `1` for true, `0` for false - `ByteArray` (strings) — use sncast's string encoding or pass raw ## Invoke (Write) ```bash # Call a write function sncast invoke \ --contract-address 0xCONTRACT \ --function "transfer" \ --calldata 0xRECIPIENT 1000 0 ``` ## Call (Read) ```bash # Call a view function (free, no tx) sncast call \ --contract-address 0xCONTRACT \ --function "get_balance" \ --calldata 0xACCOUNT ``` ## Programmatic Deployment (starknet.js) ```ts import { Account, CallData, Contract, RpcProvider } from "starknet"; const provider = new RpcProvider({ nodeUrl: process.env.STARKNET_RPC! }); const account = new Account(provider, process.env.ACCOUNT_ADDRESS!, process.env.PRIVATE_KEY!); const declareTx = await account.declare({ contract: compiledSierra, casm: compiledCasm }); await provider.waitForTransaction(declareTx.transaction_hash); const deployTx = await account.deploy({ classHash: declareTx.class_hash, constructorCalldata: CallData.compile({ owner: process.env.OWNER! }), }); await provider.waitForTransaction(deployTx.transaction_hash); const contract = new Contract(abi, deployTx.contract_address[0], provider).connect(account); await contract.invoke("set_fee", [10]); const fee = await contract.call("get_fee", []); console.log({ fee }); ``` ## Multicall Execute multiple calls in a single transaction: ```bash # Create a multicall file cat > multicall.toml << 'EOF' [[call]] call_type = "deploy" class_hash = "0x1234..." inputs = ["0xOWNER"] [[call]] call_type = "invoke" contract_address = "0xTOKEN" function = "approve" inputs = ["0xSPENDER", "1000", "0"] EOF sncast multicall run --path multicall.toml ``` ## Deploy Script Pattern For complex deployments, use a script: ```bash #!/bin/bash set -euo pipefail RPC_URL="https://starknet-sepolia.g.alchemy.com/v2/YOUR_KEY" ACCOUNT="my-deployer" echo "Building..." scarb build echo "Declaring MyToken..." TOKEN_CLASS=$(sncast --json declare --contract-name MyToken --url $RPC_URL --account $ACCOUNT | jq -r '.class_hash') echo "Token class: $TOKEN_CLASS" echo "Deploying MyToken..." TOKEN_ADDR=$(sncast --json deploy --class-hash $TOKEN_CLASS --constructor-calldata 0xOWNER --url $RPC_URL --account $ACCOUNT | jq -r '.contract_address') echo "Token deployed at: $TOKEN_ADDR" echo "Declaring AMM..." AMM_CLASS=$(sncast --json declare --contract-name AMM --url $RPC_URL --account $ACCOUNT | jq -r '.class_hash') echo "Deploying AMM..." AMM_ADDR=$(sncast --json deploy --class-hash $AMM_CLASS --constructor-calldata $TOKEN_ADDR --url $RPC_URL --account $ACCOUNT | jq -r '.contract_address') echo "AMM deployed at: $AMM_ADDR" echo "Done. Addresses:" echo " Token: $TOKEN_ADDR" echo " AMM: $AMM_ADDR" ``` ## Network Endpoints | Network | RPC URL | |---------|---------| | Devnet (local) | `http://localhost:5050` | | Sepolia (testnet) | `https://starknet-sepolia.g.alchemy.com/v2/KEY` | | Mainnet | `https://starknet-mainnet.g.alchemy.com/v2/KEY` | Alternative providers: Infura, Blast, Nethermind (free tier available). ### Local Devnet ```bash # Install and run starknet-devnet-rs cargo install starknet-devnet starknet-devnet --seed 42 # Devnet provides pre-funded accounts — use them for testing ``` ## Contract Verification Verify source code on Voyager or Starkscan: ```bash # Verify on Voyager (manual: upload Sierra JSON via web UI) # https://sepolia.voyager.online/contract/0xADDRESS#code # Or use Walnut for programmatic verification # https://app.walnut.dev ``` > **Note:** `sncast verify` supports both Walnut and Voyager backends. Use `--verifier walnut` or `--verifier voyager` explicitly. ## Upgradeable Contracts For contracts using OZ UpgradeableComponent: ```bash # 1. Declare new class sncast declare --contract-name MyContractV2 # 2. Call upgrade on existing contract sncast invoke \ --contract-address 0xEXISTING_CONTRACT \ --function "upgrade" \ --calldata 0xNEW_CLASS_HASH ``` ## Common Errors | Error | Cause | Fix | |-------|-------|-----| | `Contract not found` | Account not deployed | Run `sncast account deploy` | | `Insufficient max fee` | Not enough ETH/STRK for gas | Fund the deployer account | | `Class already declared` | Same class hash exists | Use the existing class hash for deploy | | `Entry point not found` | Wrong function name | Check the contract ABI | | `Invalid calldata` | Wrong number/type of args | Check constructor signature, remember u256 = 2 felts |