The next generation
language for Ethereum
Rust-inspired. EVM-native. Built for safety.
curl -fsSL https://raw.githubusercontent.com/argotorg/fe/master/feup/feup.sh | bash Fe at a glance.
A vault contract with traits, pattern matching, and explicit effects.
Explicit Effects
The uses clause declares all capabilities a function requires: storage access, event emission, external calls, contract creation. Side effects become explicit.
Explicit Mutability
All bindings, storage fields, and effect parameters are immutable unless marked mut. The compiler rejects writes to immutable state.
Message Passing
Contracts interact via msg types and recv handlers, mirroring the EVM's transaction-based execution model.
Pattern Matching
match expressions destructure enums, tuples, and nested data. Extract variant payloads and branch on values. The compiler checks that all cases are covered.
Traits
Define shared interfaces with trait and implement them for any type. Used throughout the standard library for Encode, Decode, Default, and more.
Generics
Type parameters on struct, enum, trait, and fn with trait bounds. Monomorphized at compile time with zero runtime overhead.
Higher-Kinded Types
Supports higher-kinded types via * -> * bounds on traits. Enables abstractions like Functor, Applicative, and Monad, generic over Option<T>, Result<E, T>, or any type constructor.
Const Functions
Functions marked const fn are evaluated at compile time. The sol("deposit()") selector in the code computes its 4-byte hash during compilation, not at runtime.
Standard Library
Ships with Option<T>, Result<E, T>, StorageMap<K, V>, and traits like Default, Encode, Decode.
use std::abi::sol
use std::evm::{Address, Ctx, Log, StorageMap, revert}
use core::result::Result::{self, Ok, Err}
use core::abi::{Abi, AbiEncoder, AbiSize, Encode}
// Enums as error types
enum VaultError {
InsufficientFunds,
ZeroAmount,
}
// Traits define shared behavior
trait Validate {
fn validate(self) -> Result<VaultError, u256>
}
struct Withdrawal {
balance: u256,
amount: u256,
}
impl Validate for Withdrawal {
fn validate(self) -> Result<VaultError, u256> {
if self.amount == 0 {
return Err(VaultError::ZeroAmount)
}
if self.balance < self.amount {
return Err(VaultError::InsufficientFunds)
}
Ok(self.balance - self.amount)
}
}
// Events with indexed fields for efficient filtering
#[event]
struct Deposited {
#[indexed]
owner: Address,
amount: u256,
}
struct VaultStore {
balances: StorageMap<Address, u256>,
}
// Message interface defines the contract's public ABI
msg VaultMsg {
#[selector = sol("deposit()")]
Deposit {},
#[selector = sol("withdraw(uint256)")]
Withdraw { amount: u256 },
#[selector = sol("balanceOf(address)")]
BalanceOf { addr: Address } -> u256,
}
// Effects declared explicitly, no hidden state access
pub contract Vault uses (ctx: Ctx, log: mut Log) {
mut store: VaultStore
recv VaultMsg {
Deposit {} uses (ctx, mut store, mut log) {
let who = ctx.caller()
store.balances.set(
who,
store.balances.get(who) + ctx.value()
)
log.emit(Deposited { owner: who, amount: ctx.value() })
}
// Pattern match on Result for control flow
Withdraw { amount } uses (ctx, mut store) {
let who = ctx.caller()
let req = Withdrawal {
balance: store.balances.get(who),
amount
}
match req.validate() {
Ok(new_bal) => { store.balances.set(who, new_bal) }
Err(e) => { revert(e) }
}
}
BalanceOf { addr } -> u256 uses (store) {
store.balances.get(addr)
}
}
} Built-in Testing
Run fe test with integrated EVM simulation. No external frameworks needed.
Package Manager
Organize code into ingots. Add dependencies and share reusable contracts.
Code Formatter
Run fe fmt for consistent, opinionated formatting.
Language Server
LSP support with diagnostics, go-to-definition, and completions for many popular editors.
Docs Generator
Generate browsable HTML docs from your code with fe doc.
Dual Backend
Compiles through Sonatina, Fe's custom SSA-based backend, or via Yul for Solidity toolchain compatibility.
Explore more contracts written in Fe
See examples →