Your First Contract
In this tutorial you’ll create a Counter contract, test it, compile it, deploy it to a local chain with Foundry, and interact with it.
Prerequisites
Section titled “Prerequisites”- Fe installed (Installation)
- Foundry installed (getfoundry.sh)
Create a Project
Section titled “Create a Project”Fe has a built-in project scaffolding command. Run it to create a new project called counter:
fe new countercd counterThis creates the following structure:
counter/├── fe.toml└── src/ └── lib.fefe.toml is the project manifest. src/lib.fe contains a starter Counter contract.
The Contract
Section titled “The Contract”Open src/lib.fe. You’ll see the following contract:
use std::abi::soluse std::evm::{Evm, Call}use std::evm::effects::assert
msg CounterMsg { #[selector = sol("increment()")] Increment, #[selector = sol("get()")] Get -> u256,}
struct CounterStore { value: u256,}
pub contract Counter { mut store: CounterStore
init() uses (mut store) { store.value = 0 }
recv CounterMsg { Increment uses (mut store) { store.value = store.value + 1 }
Get -> u256 uses (store) { store.value } }}Let’s break this down:
-
msg CounterMsgdefines the contract’s public interface. Each variant becomes a callable function. Thesol(...)helper computes standard Solidity function selectors, making the contract compatible with existing Ethereum tooling. -
struct CounterStoredeclares the on-chain storage layout. Its fields are persisted between calls. -
pub contract Counteris the contract itself. Themut storefield connects it to its storage. -
init()is the constructor. It runs once when the contract is deployed. Theuses (mut store)clause explicitly declares that this function writes to storage. -
recv CounterMsgis the receive block. It routes incoming messages to handlers.Incrementneedsmut storebecause it writes;Getonly needsstore(read-only).
This explicit declaration of effects — what each function reads or writes — is one of Fe’s defining features. Nothing is hidden.
Run the Tests
Section titled “Run the Tests”The generated file also includes a test. Run it:
fe testYou should see the test pass. The test deploys the contract in a local EVM sandbox, calls Get (expects 0), calls Increment, then calls Get again (expects 1).
Compile the contract to EVM bytecode:
fe buildThis creates an out/ directory with the compiled artifacts:
out/├── Counter.bin # Deploy (init) bytecode└── Counter.runtime.bin # Runtime bytecodeDeploy to a Local Chain
Section titled “Deploy to a Local Chain”Start a local Ethereum node with Foundry’s anvil:
anvilAnvil prints a list of pre-funded accounts and their private keys. Keep it running and open a second terminal.
Deploy the Counter contract using cast:
cast send \ --rpc-url http://127.0.0.1:8545 \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ --create 0x$(cat out/Counter.bin)In the output, look for the contractAddress field — that’s your deployed Counter. Export it for the next steps:
export COUNTER=<the contract address from the output>Interact with the Contract
Section titled “Interact with the Contract”Read the current counter value:
cast call --rpc-url http://127.0.0.1:8545 $COUNTER "get()(uint256)"This should return 0.
Increment the counter:
cast send \ --rpc-url http://127.0.0.1:8545 \ --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 \ $COUNTER "increment()"Read the value again:
cast call --rpc-url http://127.0.0.1:8545 $COUNTER "get()(uint256)"It should now return 1. Each cast send with increment() will increase the value by one.
Next Steps
Section titled “Next Steps”You’ve written, tested, compiled, deployed, and interacted with a Fe contract. From here:
- Key Concepts — understand Fe’s core ideas
- Effects & the
usesClause — learn how Fe tracks state access - Messages & Receive Blocks — dig deeper into the message model
- Examples — see more realistic contracts