Skip to main content

Smart Contracts (snfoundry Package)

The packages/snfoundry directory contains the smart contract development environment. This package is identical to Scaffold Stark 2, ensuring full compatibility between web and mobile frontends.

Overviewโ€‹

packages/snfoundry/
โ”œโ”€โ”€ contracts/ # Cairo smart contracts
โ”‚ โ””โ”€โ”€ src/
โ”‚ โ”œโ”€โ”€ YourContract.cairo # Your contracts
โ”‚ โ””โ”€โ”€ lib.cairo # Module declarations
โ”œโ”€โ”€ deployments/ # Deployment artifacts
โ”œโ”€โ”€ scripts-ts/ # Deployment scripts
โ”œโ”€โ”€ Scarb.toml # Cairo package config
โ”œโ”€โ”€ snfoundry.toml # Foundry config
โ””โ”€โ”€ package.json # Scripts and dependencies

Reference Documentationโ€‹

Since the snfoundry package is identical to Scaffold Stark 2, refer to the main documentation for comprehensive guides:

Available Commandsโ€‹

Run these commands from the project root:

CommandDescription
yarn chainStart local Starknet devnet on port 5050
yarn compileCompile Cairo contracts with Scarb
yarn deployDeploy contracts to the active network
yarn deploy:clearDeploy with reset (clear previous deployments)
yarn deploy:no-resetDeploy without resetting existing contracts
yarn testRun contract tests with snforge
yarn verifyVerify contracts on Starkscan/Voyager

Starting Local Developmentโ€‹

# Terminal 1: Start local devnet
yarn chain

# Terminal 2: Deploy contracts
yarn deploy

# Terminal 3: Start mobile app
yarn start

Writing Contractsโ€‹

Basic Contract Exampleโ€‹

// contracts/src/YourContract.cairo
#[starknet::interface]
trait IYourContract<TContractState> {
fn set_greeting(ref self: TContractState, new_greeting: felt252);
fn get_greeting(self: @TContractState) -> felt252;
fn get_balance(self: @TContractState, account: ContractAddress) -> u256;
}

#[starknet::contract]
mod YourContract {
use starknet::ContractAddress;
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::get_caller_address;

#[storage]
struct Storage {
greeting: felt252,
owner: ContractAddress,
balances: LegacyMap<ContractAddress, u256>,
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
GreetingChanged: GreetingChanged,
}

#[derive(Drop, starknet::Event)]
struct GreetingChanged {
#[key]
user: ContractAddress,
new_greeting: felt252,
}

#[constructor]
fn constructor(ref self: ContractState, initial_greeting: felt252) {
self.greeting.write(initial_greeting);
self.owner.write(get_caller_address());
}

#[abi(embed_v0)]
impl YourContractImpl of super::IYourContract<ContractState> {
fn set_greeting(ref self: ContractState, new_greeting: felt252) {
let caller = get_caller_address();
self.greeting.write(new_greeting);
self.emit(GreetingChanged { user: caller, new_greeting });
}

fn get_greeting(self: @ContractState) -> felt252 {
self.greeting.read()
}

fn get_balance(self: @ContractState, account: ContractAddress) -> u256 {
self.balances.read(account)
}
}
}

Register Contract Moduleโ€‹

// contracts/src/lib.cairo
mod YourContract;
// Add more modules as needed
mod AnotherContract;

Deployment Configurationโ€‹

Environment Setupโ€‹

Create a .env file in packages/snfoundry/:

# packages/snfoundry/.env

# Local Devnet (default)
# No configuration needed - uses localhost:5050

# Sepolia Testnet
RPC_URL_SEPOLIA=https://starknet-sepolia.public.blastapi.io/rpc/v0_8
ACCOUNT_ADDRESS_SEPOLIA=0x...your_account_address
PRIVATE_KEY_SEPOLIA=0x...your_private_key

# Mainnet
RPC_URL_MAINNET=https://starknet-mainnet.public.blastapi.io/rpc/v0_8
ACCOUNT_ADDRESS_MAINNET=0x...your_account_address
PRIVATE_KEY_MAINNET=0x...your_private_key

Deployment Scriptโ€‹

// scripts-ts/deploy.ts
import { deployContract, deployer } from "./helpers";

async function main() {
// Deploy with constructor arguments
await deployContract({
contract: "YourContract",
constructorArgs: {
initial_greeting: "Hello, Starknet!",
},
});

// Deploy another contract
await deployContract({
contract: "AnotherContract",
constructorArgs: {},
});
}

main();

Deploy to Different Networksโ€‹

# Deploy to local devnet (default)
yarn deploy

# Deploy to Sepolia testnet
yarn deploy --network sepolia

# Deploy to mainnet
yarn deploy --network mainnet

Testing Contractsโ€‹

Writing Testsโ€‹

// tests/test_your_contract.cairo
use snforge_std::{declare, ContractClassTrait};

#[test]
fn test_greeting() {
// Declare and deploy
let contract = declare('YourContract');
let contract_address = contract.deploy(@array!['Hello']).unwrap();

// Create dispatcher
let dispatcher = IYourContractDispatcher { contract_address };

// Test read
let greeting = dispatcher.get_greeting();
assert(greeting == 'Hello', 'Wrong initial greeting');

// Test write
dispatcher.set_greeting('World');
let new_greeting = dispatcher.get_greeting();
assert(new_greeting == 'World', 'Greeting not updated');
}

#[test]
#[should_panic(expected: ('Only owner', ))]
fn test_unauthorized() {
// Test that should panic
}

Running Testsโ€‹

# Run all tests
yarn test

# Run specific test file
yarn test tests/test_your_contract.cairo

# Run with verbose output
yarn test -v

Contract Verificationโ€‹

Verify your contracts on block explorers:

# Verify on Starkscan (Sepolia)
yarn verify --network sepolia

# Verify on Voyager
yarn verify --network sepolia --explorer voyager

Auto-Generated Typesโ€‹

When you run yarn deploy, the deployment script automatically:

  1. Compiles your contracts
  2. Deploys to the target network
  3. Generates TypeScript types in packages/rn/contracts/deployedContracts.ts
// packages/rn/contracts/deployedContracts.ts (auto-generated)
export const contracts = {
devnet: {
YourContract: {
address: "0x...",
abi: [...],
classHash: "0x...",
},
},
sepolia: {
YourContract: {
address: "0x...",
abi: [...],
classHash: "0x...",
},
},
} as const;

This enables type-safe contract interactions in your React Native app:

// Type-safe hook usage
const { data } = useScaffoldReadContract({
contractName: "YourContract", // Autocomplete from deployedContracts
functionName: "get_greeting", // Autocomplete from ABI
});

Sharing Contracts with Webโ€‹

Since snfoundry is identical between Scaffold Stark 2 and React Native, you can:

  1. Share contracts between web and mobile projects
  2. Deploy once, use everywhere
  3. Maintain consistency across platforms

Example: Shared Contract Repositoryโ€‹

my-starknet-project/
โ”œโ”€โ”€ contracts/ # Shared Cairo contracts
โ”‚ โ””โ”€โ”€ src/
โ”‚ โ””โ”€โ”€ Token.cairo
โ”œโ”€โ”€ web-app/ # Scaffold Stark 2
โ”‚ โ””โ”€โ”€ packages/
โ”‚ โ”œโ”€โ”€ nextjs/
โ”‚ โ””โ”€โ”€ snfoundry/ # Symlink to ../contracts
โ””โ”€โ”€ mobile-app/ # Scaffold Stark RN
โ””โ”€โ”€ packages/
โ”œโ”€โ”€ rn/
โ””โ”€โ”€ snfoundry/ # Symlink to ../contracts

Compatible Versionsโ€‹

ToolVersion
Scarb2.16.0
Starknet Foundry0.57.0
Cairo2.16.x
starknet.js8.5.3
Starknet Devnet0.7.2

Troubleshootingโ€‹

Compilation Errorsโ€‹

# Check Scarb version
scarb --version
# Should be 2.16.0

# Clean and recompile
yarn compile

Deployment Failuresโ€‹

# Check devnet is running
curl http://127.0.0.1:5050/is_alive

# Check account balance
yarn chain # Devnet provides prefunded accounts

Type Generation Issuesโ€‹

# Regenerate types
yarn deploy:clear

Next Stepsโ€‹