Sway is a domain-specific programming language for smart contract development on the Fuel blockchain. Inspired by Rust's syntax and safety guarantees, Sway compiles to FuelVM bytecode and is paired with forc — the Fuel Orchestrator — a build tool and package manager analogous to Rust's cargo. Together, they form the complete developer experience for building contracts, scripts, predicates, and libraries on Fuel.
Version: 0.71.0 (commit e12e611)
Toolchain: forc with 16 built-in subcommands and 8 plugin binaries
1. Quick Start
Get from zero to a running Sway contract in under five minutes.
Prerequisites
- A Unix-like terminal (Linux, macOS, or WSL2 on Windows)
gitinstalled and accessible from your shell- Internet access for downloading the toolchain
Install fuelup
fuelup is the official Fuel toolchain manager. It installs and manages forc and all associated tools.
curl https://install.fuel.network | sh
Follow the on-screen instructions. Reload your shell or run:
source ~/.bashrc # or ~/.zshrc, ~/.profile — whichever your shell uses
Verify the install:
fuelup --version
forc --version
Create your first project
forc new hello-fuel
cd hello-fuel
This generates:
hello-fuel/
Forc.toml # Manifest (like Cargo.toml)
src/
main.sw # Entry point — a contract by default
The generated src/main.sw looks like:
contract;
abi MyContract {
fn test_function() -> bool;
}
impl MyContract for Contract {
fn test_function() -> bool {
true
}
}
Build and inspect
forc build
Output lands in out/:
out/
debug/
hello-fuel.bin # Contract bytecode
hello-fuel-abi.json # ABI for external tooling
Tip
Run
forc checkinstead offorc buildwhen you only want to type-check your code. It skips code generation and runs significantly faster — ideal for rapid iteration during development.
Run tests
forc test
Tests are written inline with #[test] attributes directly in your Sway source files. No separate test directory is required.
Next steps
- Learn the four program types
- Explore every forc subcommand
- See the full CLI reference tables
2. Installation
2.1 Installing via fuelup (Recommended)
fuelup manages Fuel toolchain versions, analogous to rustup. It is the recommended installation path for all platforms.
curl https://install.fuel.network | sh
After installation, fuelup adds itself and the toolchain binaries to ~/.fuelup/bin. Make sure this directory is on your PATH.
export PATH="$HOME/.fuelup/bin:$PATH"
Add that line to your shell's RC file (~/.bashrc, ~/.zshrc, etc.) to make it permanent.
Install a specific toolchain
fuelup toolchain install latest
fuelup toolchain install nightly
fuelup default latest
Switch between toolchains
fuelup default nightly
fuelup default latest
Update the toolchain
fuelup update
Important
Always use
fuelup updaterather than re-running the install curl script. Re-running the installer may reset your default toolchain selection.fuelup updateintelligently upgrades only what has changed.
2.2 Verify installation
After install, confirm all core tools are present:
forc --version
forc-fmt --version
forc-lsp --version
forc-doc --version
Check installed plugins:
forc plugins
Expected output lists all installed plugin binaries.
2.3 Shell completions
Generate completions for your shell:
# Bash
forc completions --shell bash >> ~/.bash_completion
# Zsh
forc completions --shell zsh > ~/.zfunc/_forc
# Fish
forc completions --shell fish > ~/.config/fish/completions/forc.fish
# PowerShell
forc completions --shell powershell >> $PROFILE
# Elvish
forc completions --shell elvish >> ~/.elvish/rc.elv
# Fig (macOS autocomplete)
forc completions --shell fig
Note
For zsh completions, ensure your
fpathincludes~/.zfuncand thatcompinitruns in your.zshrc. Addfpath=(~/.zfunc $fpath)before theautoload -Uz compinit && compinitline.
After generating completions, restart your shell or source the relevant file.
2.4 Platform-specific notes
Linux
Standard installation works on Ubuntu 20.04+, Fedora 36+, and other glibc-based distributions. The musl target is not officially supported.
macOS
Works on macOS 12+ (Monterey and later). Apple Silicon (M1/M2/M3) is fully supported via native ARM builds.
Windows (WSL2)
Native Windows is not supported. Use WSL2 with Ubuntu 22.04 or later. Install fuelup inside the WSL2 environment, not in the Windows host.
Warning
Do not attempt to run
forcorfuelupfrom a Windows Command Prompt or PowerShell directly. The Fuel toolchain requires a Unix-compatible environment. WSL2 is the supported Windows path.
2.5 Building from source
For contributors or when a specific unreleased commit is required:
git clone https://github.com/FuelLabs/sway.git
cd sway
cargo build --release -p forc
The binary lands at target/release/forc. Add it to your PATH or use it directly.
Prerequisites for building from source:
- Rust stable (1.76 or later)
cargo(bundled with Rust)- Standard C build tools (
gcc,make,pkg-config)
3. Configuration
3.1 The Forc.toml manifest
Every Sway project is described by a Forc.toml file at the project root. This is the single source of truth for package metadata, dependencies, build profiles, and workspace membership.
Minimal contract manifest
[project]
name = "my-contract"
version = "0.0.1"
entry = "main.sw"
license = "Apache-2.0"
[dependencies]
Full manifest reference
[project]
name = "my-contract" # Package name (required)
version = "0.0.1" # Semver version (required)
entry = "main.sw" # Entry source file (default: main.sw or lib.sw)
license = "Apache-2.0" # SPDX license identifier
authors = ["Alice <[email removed]>"]
description = "A sample Sway contract"
repository = "https://github.com/example/my-contract"
homepage = "https://example.com"
keywords = ["fuel", "defi"]
categories = ["finance"]
[dependencies]
# Registry dependencies
std = { version = "0.71.0" }
# Git dependencies
my-lib = { git = "https://github.com/example/my-lib", branch = "main" }
# Tag-pinned git dependency
other-lib = { git = "https://github.com/example/other-lib", tag = "v0.1.0" }
# Specific commit pinned
pinned-lib = { git = "https://github.com/example/pinned-lib", rev = "abc1234" }
# Local path dependency
local-utils = { path = "../local-utils" }
[build-profile.debug]
# Debug profile is the default. Optimizations off.
[build-profile.release]
# Release profile: run with `forc build --release`
3.2 Workspace manifest
A workspace groups multiple Sway packages under one root. Create a Forc.toml at the workspace root containing only a [workspace] section:
[workspace]
members = [
"contracts/token",
"contracts/exchange",
"libraries/shared-types",
"scripts/deploy-helper",
]
Initialize a workspace project with:
forc new my-workspace --workspace
Then add members manually by creating subdirectories with their own Forc.toml manifests and listing them in members.
Note
Workspace-level
forc build,forc test, andforc checkoperate on all members. Use--pathto target a specific member:forc build --path contracts/token.
3.3 Build profiles
Two profiles exist: debug (default) and release.
forc build # debug profile
forc build --release # release profile
Custom profiles can be defined in Forc.toml under [build-profile.<name>].
3.4 Experimental features
Sway has a feature flag system for opt-in or opt-out behaviors. Features have a defined precedence order (highest wins):
- Forc.toml manifest declaration (highest precedence)
- CLI
--no-experimental <feature> - CLI
--experimental <feature> FORC_NO_EXPERIMENTALenvironment variableFORC_EXPERIMENTALenvironment variable (lowest precedence)
Currently available features
| Feature | Default | Description |
|---|---|---|
new_encoding |
Enabled | New ABI encoding scheme |
references |
Enabled | Reference types in Sway |
new_hashing |
Enabled | Updated hashing semantics |
str_array_no_padding |
Disabled | String arrays without padding |
dynamic_storage |
Disabled | Dynamic storage capabilities |
Enable/disable via CLI
# Enable a disabled-by-default feature
forc build --experimental str_array_no_padding
# Disable an enabled-by-default feature
forc build --no-experimental new_encoding
Enable/disable via environment variable
# Enable via env (comma-separated list)
FORC_EXPERIMENTAL=str_array_no_padding forc build
# Disable via env
FORC_NO_EXPERIMENTAL=new_encoding forc build
Enable/disable in Forc.toml
[project]
name = "my-contract"
version = "0.0.1"
experimental = ["str_array_no_padding"]
no-experimental = ["new_encoding"]
3.5 Environment variables
All recognized environment variables for the Fuel toolchain:
| Variable | Purpose |
|---|---|
FORC_EXPERIMENTAL |
Comma-separated list of experimental features to enable |
FORC_NO_EXPERIMENTAL |
Comma-separated list of experimental features to disable |
FORC_PUB_TOKEN |
Authentication token for forc publish |
FORC_IMPLICIT_STD_PATH |
Override the path to the bundled std library |
FORC_IMPLICIT_STD_GIT |
Override the git URL used to fetch std |
FORC_IMPLICIT_STD_GIT_TAG |
Override the git tag for the std fetch |
FORC_IMPLICIT_STD_GIT_BRANCH |
Override the git branch for the std fetch |
Note
The
FORC_IMPLICIT_STD_*variables are primarily useful for toolchain developers and CI pipelines that need to test against a customstdbuild without modifyingForc.toml. Most users should never need them.
4. Core Usage
4.1 Program types
Every Sway source file must declare its program type as the very first statement. Sway supports four types:
contract
The most common type. Contracts are stateful programs deployed on-chain and called via transactions. They can hold storage, implement ABIs, and be called by other contracts or externally.
contract;
storage {
balance: u64 = 0,
}
abi SimpleWallet {
#[storage(read, write)]
fn deposit();
#[storage(read)]
fn get_balance() -> u64;
}
impl SimpleWallet for Contract {
#[storage(read, write)]
fn deposit() {
let amount = msg_amount();
storage.balance.write(storage.balance.read() + amount);
}
#[storage(read)]
fn get_balance() -> u64 {
storage.balance.read()
}
}
Build output: .bin bytecode + -abi.json ABI descriptor.
script
Scripts are single-execution programs that run in a transaction and can call contracts. They do not have persistent storage and cannot be called by other contracts. They are the equivalent of a "runner" or "orchestrator" in a transaction.
script;
fn main() -> bool {
// Call contracts, perform computations, return a result
true
}
Build output: .bin bytecode only.
predicate
Predicates are pure boolean programs used as spending conditions for UTXOs. They must return true for a transaction to consume the associated UTXO. Predicates have no storage and no side effects — they are deterministic.
predicate;
fn main() -> bool {
// Inspect transaction fields and return true/false
let sender = std::tx::tx_witness_data::<b256>(0);
sender == 0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
}
Build output: .bin bytecode only. Use forc predicate-root to compute the predicate root hash.
library
Libraries are reusable code modules. They do not have an entry point — they export functions, types, constants, and traits for use by other packages.
library;
pub fn add(a: u64, b: u64) -> u64 {
a + b
}
pub struct Point {
pub x: u64,
pub y: u64,
}
Entry point: src/lib.sw (not src/main.sw).
Build output: -abi.json ABI descriptor (no .bin).
4.2 Project initialization
forc new
Create a new project directory and initialize it:
forc new my-project # contract (default)
forc new my-script --script
forc new my-predicate --predicate
forc new my-lib --library
forc new my-workspace --workspace
The --name flag overrides the inferred package name (useful when the directory name is not a valid package identifier):
forc new my-project-dir --name my_project
forc init
Initialize a Sway project in the current directory (or a specified existing directory). Useful when you already have a directory and want to add Sway tooling:
mkdir existing-dir && cd existing-dir
forc init # contract in current dir
forc init --script
forc init --predicate
forc init --library
forc init --path /absolute/path/to/dir
forc template
Create a new project from a git-hosted template:
forc template --url https://github.com/FuelLabs/sway-applications \
--template-name counter \
my-counter-project
This clones the template repository, extracts the named template, and wires it up as a new project at my-counter-project/.
4.3 Building
Basic build
forc build
Compiles the project in the current directory using the debug profile. Output goes to out/debug/.
Release build
forc build --release
Uses the release build profile. Output goes to out/release/.
Build a specific project
forc build --path /path/to/project
Offline build (use cached dependencies)
forc build --offline
Uses only locally cached dependencies. Fails if a dependency is not cached.
Locked build (enforce Forc.lock)
forc build --locked
Requires Forc.lock to already exist and be up-to-date. Fails rather than updating the lock file. Use this in CI to prevent unexpected dependency changes.
Terse output
forc build --terse
Suppresses informational output, showing only warnings and errors.
Build for EVM target
forc build --build-target evm
Compile targeting the EVM instead of FuelVM. The default is fuel.
Custom output directory
forc build --output-directory /tmp/my-build
Build with tests compiled in
forc build --tests
Compiles test functions into the binary. Required before running forc test.
Build and print AST
forc build --print-ast
Dumps the parsed abstract syntax tree to stdout after parsing.
Build and print IR
forc build --print-ir initial
forc build --print-ir final
forc build --print-ir passes
Dumps Sway IR at the specified stage. initial = before optimization, final = after, passes = after each pass.
Build and print ASM
forc build --print-asm virtual
forc build --print-asm allocated
forc build --print-asm final
Dumps assembly at different stages of code generation.
Print bytecode as hex
forc build --print-bytecode
Minify ABI output
forc build --minify-json-abi
forc build --minify-json-storage-slots
Removes whitespace from the output JSON files.
Treat warnings as errors
forc build --error-on-warnings
Fails the build if any warnings are emitted. Useful for CI and pre-commit hooks.
Performance timing
forc build --time-phases
Prints timing data for each compilation phase (parsing, type-checking, IR generation, codegen, etc.).
forc build --metrics-outfile metrics.json
Writes timing metrics to a JSON file.
Reverse error ordering
forc build --reverse-order
Emits errors in reverse order (last error first). Occasionally useful when the root cause is buried under cascading errors.
4.4 forc CLI reference
Global flags (apply to all subcommands)
| Flag | Short | Description |
|---|---|---|
--verbose |
-v |
Enable verbose output |
--silent |
-s |
Suppress all output except errors |
--log-level <LEVEL> |
-L |
Set log level (error, warn, info, debug, trace) |
--help |
-h |
Show help for the current subcommand |
--version |
-V |
Show version and exit |
forc build flags
| Flag | Description |
|---|---|
--path <PATH> |
Path to project (default: current dir) |
--offline |
Use only cached dependencies |
--locked |
Require up-to-date Forc.lock, don't update |
--terse |
Minimal output (warnings + errors only) |
--release |
Use release build profile |
--tests |
Include test functions in compilation |
--build-target <TARGET> |
fuel (default) or evm |
--output-directory <DIR> |
Override output directory |
--print-ast |
Dump AST after parsing |
--print-asm <STAGE> |
Dump ASM (virtual, allocated, final) |
--print-ir <STAGE> |
Dump IR (initial, final, passes) |
--print-bytecode |
Print bytecode as hex |
--minify-json-abi |
Minify ABI JSON output |
--minify-json-storage-slots |
Minify storage slots JSON output |
--build-profile <NAME> |
Use a named build profile from Forc.toml |
--error-on-warnings |
Fail the build on any warning |
--experimental <FEATURE> |
Enable an experimental feature |
--no-experimental <FEATURE> |
Disable an experimental feature |
--time-phases |
Print timing for each compilation phase |
--metrics-outfile <FILE> |
Write metrics JSON to file |
--reverse-order |
Emit errors/warnings in reverse order |
forc new / forc init flags
| Flag | Description |
|---|---|
--contract |
Create a contract project (default for new/init) |
--script |
Create a script project |
--predicate |
Create a predicate project |
--library |
Create a library project |
--workspace |
Create a workspace |
--name <NAME> |
Override the package name |
--path <PATH> |
Target directory (for init) |
forc test flags
| Flag | Short | Description |
|---|---|---|
--path <PATH> |
Path to the project | |
--offline |
Use cached dependencies only | |
--locked |
Require locked dependencies | |
--terse |
Minimal output | |
--release |
Test in release mode | |
--filter <FILTER> |
Run only tests matching the filter string | |
--verbose |
-v |
Verbose test output |
--logs |
Print logs emitted during tests | |
--experimental <FEATURE> |
Enable experimental feature | |
--no-experimental <FEATURE> |
Disable experimental feature |
forc check flags
| Flag | Description |
|---|---|
--path <PATH> |
Path to project |
--offline |
Use cached dependencies |
--locked |
Require locked dependencies |
--experimental <FEATURE> |
Enable experimental feature |
--no-experimental <FEATURE> |
Disable experimental feature |
forc add flags
| Flag | Description |
|---|---|
--version <VERSION> |
Version constraint to add |
--git <URL> |
Git repository URL |
--branch <BRANCH> |
Git branch to use |
--tag <TAG> |
Git tag to use |
--rev <REV> |
Git commit hash to pin |
--path <PATH> |
Local path to dependency |
forc completions flags
| Flag | Short | Description |
|---|---|---|
--shell <SHELL> |
-s |
Shell: bash, zsh, fish, powershell, elvish, fig |
forc plugins flags
| Flag | Description |
|---|---|
--path <PATH> |
Look for plugins in this directory instead of PATH |
All 16 built-in subcommands at a glance
| Subcommand | Alias | Description |
|---|---|---|
add |
Add a dependency to Forc.toml | |
addr2line |
Resolve opcode address to source location | |
build |
b |
Compile the project |
check |
Type-check without codegen | |
clean |
Remove the out/ directory |
|
completions |
Generate shell completions | |
contract-id |
Compute the contract ID | |
init |
Initialize project in current/specified dir | |
new |
Create new project at path | |
parse-bytecode |
Decode a .bin file into readable opcodes | |
plugins |
List installed forc plugins | |
predicate-root |
Compute predicate root hash | |
remove |
Remove a dependency from Forc.toml | |
template |
Create project from a git template | |
test |
t |
Run inline #[test] functions |
update |
Update dependencies |
4.5 Type-checking without building
forc check
Performs full type-checking, borrow checking, and semantic analysis without generating any bytecode or ABI files. This is significantly faster than forc build and is the recommended command for rapid development feedback.
forc check --path /path/to/project
forc check --locked
4.6 Cleaning build artifacts
forc clean
Removes the out/ directory for the current project. Equivalent to rm -rf out/.
forc clean --path /path/to/project
4.7 Dependency management
forc add
Add a dependency to Forc.toml:
# Add from the Fuel registry by name
forc add my-library
# Add a specific version
forc add my-library --version 0.2.0
# Add from a git repository
forc add my-library --git https://github.com/example/my-library
# Add from a git repo at a specific branch
forc add my-library --git https://github.com/example/my-library --branch dev
# Add from a local path
forc add my-library --path ../my-library
This writes the dependency entry into Forc.toml automatically. You do not need to manually edit the manifest for most cases.
forc remove
Remove a dependency from Forc.toml:
forc remove my-library
This removes the entry from Forc.toml and updates Forc.lock.
forc update
Update dependencies to their latest compatible versions:
forc update # Update all dependencies
forc update my-library # Update a specific dependency
After running forc update, the Forc.lock file is regenerated. Commit this file to lock your build for reproducibility.
Tip
Commit
Forc.lockto version control for binary applications (contracts, scripts, predicates). For libraries, it is conventional to gitignoreForc.lockso downstream users resolve their own compatible versions.
4.8 Contract and predicate utilities
forc contract-id
Determine the contract ID (address) of a compiled contract without deploying:
forc contract-id
forc contract-id --path /path/to/contract
forc contract-id --release
The contract ID is a deterministic hash of the contract's bytecode. Knowing it in advance is useful for:
- Pre-computing addresses for cross-contract calls
- Verifying that a deployed contract matches expected bytecode
- Generating configuration for other contracts that reference this one
forc predicate-root
Compute the predicate root hash of a compiled predicate:
forc predicate-root
forc predicate-root --path /path/to/predicate
The predicate root is the hash used as the "address" of a predicate UTXO. It must match what the transaction expects.
4.9 Debugging and analysis
forc parse-bytecode
Parse a compiled .bin file and display it in a human-readable format:
forc parse-bytecode out/debug/my-contract.bin
Output shows decoded opcodes with operands. Useful for low-level inspection and verifying codegen output.
forc addr2line
Resolve a FuelVM opcode address back to its source location:
forc addr2line --contract-id 0xabc123... --opcode-index 42
Requires the binary and debug info to be available. Useful when a transaction fails at a specific opcode and you need to trace it back to source.
4.10 Publishing packages
forc publish
Publish a package to the Fuel registry:
FORC_PUB_TOKEN=your_token forc publish
Or:
forc publish --token your_token
Before publishing:
- Ensure
Forc.tomlhasname,version,license, anddescriptionset. - Your package must be compilable without errors.
- You must have a valid registry account and token.
Important
Once a specific version is published, it cannot be overwritten or deleted. Increment your version number (
versioninForc.toml) for each publish. Yanked versions remain accessible but are excluded from version resolution.
5. Common Tasks
5.1 Writing and running tests
Sway tests are inline #[test] functions. They live in the same source files as your code.
contract;
fn add(a: u64, b: u64) -> u64 {
a + b
}
#[test]
fn test_add() {
assert(add(2, 3) == 5);
}
#[test]
fn test_add_zero() {
assert(add(0, 100) == 100);
}
Run all tests:
forc test
Run tests matching a filter:
forc test --filter test_add
Print logs emitted during tests:
forc test --logs
Run tests in verbose mode (shows each test name and result):
forc test --verbose
Test with should_revert
Use #[test(should_revert)] for tests that are expected to panic or revert:
#[test(should_revert)]
fn test_division_by_zero() {
let _ = 10_u64 / 0_u64;
}
5.2 Formatting code
Use forc-fmt to automatically format your Sway source files:
# Format all .sw files in the current project
forc fmt
# Check formatting without modifying files (exit code 1 if changes needed)
forc fmt --check
# Format a specific file or directory
forc fmt src/main.sw
forc fmt src/
Tip
Add
forc fmt --checkto your CI pipeline to enforce consistent formatting. The--checkflag returns a non-zero exit code if any files would be changed, making it safe to use as a gate.
forc-fmt follows the official Sway style guide and is the canonical formatter — no configuration options are exposed. The style is intentionally opinionated.
5.3 Generating documentation
Use forc-doc to generate HTML documentation from doc comments (///) in your source:
# Generate docs for the current project
forc doc
# Generate and immediately open in browser
forc doc --open
Doc comments use triple slashes:
/// Calculates the sum of two unsigned 64-bit integers.
///
/// # Arguments
///
/// * `a` - The first operand.
/// * `b` - The second operand.
///
/// # Returns
///
/// The sum of `a` and `b`.
pub fn add(a: u64, b: u64) -> u64 {
a + b
}
Generated HTML is written to out/doc/. Open out/doc/index.html in your browser.
5.4 Using the Language Server (LSP)
forc-lsp provides IDE integration: completions, hover info, go-to-definition, diagnostics, and rename.
Start the LSP server:
forc lsp
This is rarely called directly — your editor's Sway extension manages it automatically.
VS Code
Install the Sway extension from the VS Code Marketplace. It automatically discovers and launches forc-lsp. Diagnostics appear in real time as you type.
Neovim
Use nvim-lspconfig with the sway language server entry, pointing to forc-lsp as the command.
Note
forc-lspreadsForc.tomlto understand your project structure. Always open VS Code (or your editor) at the project root — the directory containingForc.toml— not a subdirectory, or LSP features will be degraded.
5.5 Deploying and running contracts
Deploy a contract to a local Fuel node or testnet using forc-client:
# Deploy to a local node (default: http://localhost:4000)
forc deploy
# Deploy to testnet
forc deploy --node-url https://testnet.fuel.network/v1/graphql
# Run a script
forc run --node-url https://testnet.fuel.network/v1/graphql
forc-client is a plugin (forc-deploy / forc-run) installed alongside the main toolchain via fuelup.
5.6 Using the debugger
forc-debug implements the Debug Adapter Protocol (DAP), allowing step-through debugging of Sway programs:
forc debug
Connect your editor (VS Code, etc.) to the DAP server for interactive debugging. Set breakpoints in .sw files and inspect VM state.
5.7 Migrating projects
When breaking language changes land in a new Sway version, forc-migrate helps automate updates:
forc migrate
This analyzes your project and applies automated fixes for known migration paths. Review changes before committing.
5.8 Working with ABI files
The ABI JSON file (out/debug/{name}-abi.json) is the interface descriptor for a contract. Downstream tools use it to encode/decode function calls and events.
Build and inspect the ABI:
forc build
cat out/debug/my-contract-abi.json
For compact ABI files (e.g., for on-chain storage or size-constrained contexts):
forc build --minify-json-abi
5.9 Multi-contract projects (workspaces)
my-workspace/
Forc.toml # workspace manifest
token-contract/
Forc.toml
src/main.sw
exchange-contract/
Forc.toml
src/main.sw
shared-lib/
Forc.toml
src/lib.sw
Workspace Forc.toml:
[workspace]
members = [
"token-contract",
"exchange-contract",
"shared-lib",
]
Build all members:
cd my-workspace
forc build
Build a specific member:
forc build --path token-contract
Reference a workspace sibling as a dependency using a path dep:
# exchange-contract/Forc.toml
[dependencies]
shared-lib = { path = "../shared-lib" }
5.10 Cross-contract calls
Contracts can call other contracts via ABI casts. First, import the ABI:
contract;
// Declare the ABI of the target contract
abi Token {
fn transfer(to: Address, amount: u64);
fn balance_of(addr: Address) -> u64;
}
abi Exchange {
fn swap(token_contract_id: ContractId, amount: u64) -> u64;
}
impl Exchange for Contract {
fn swap(token_contract_id: ContractId, amount: u64) -> u64 {
// Cast to ABI to make cross-contract call
let token = abi(Token, token_contract_id.into());
token.transfer(msg_sender().unwrap().as_address().unwrap(), amount);
amount
}
}
5.11 Writing libraries
Libraries live in src/lib.sw. Use pub to export items:
library;
pub mod math;
pub mod types;
pub use math::add;
pub use types::Point;
Sub-modules go in separate files (e.g., src/math.sw):
library;
pub fn add(a: u64, b: u64) -> u64 {
a + b
}
Reference a library from a contract:
# Forc.toml
[dependencies]
my-lib = { path = "../my-lib" }
contract;
use my_lib::add;
Note
Module names in Sway use underscores (
my_lib) even if the package name inForc.tomluses hyphens (my-lib). Hyphens are converted to underscores when resolving module identifiers.
5.12 Storage in contracts
Sway contracts declare all storage in a top-level storage block:
contract;
storage {
owner: Address = Address::from(0x0000000000000000000000000000000000000000000000000000000000000000),
balances: StorageMap<Address, u64> = StorageMap {},
items: StorageVec<u64> = StorageVec {},
counter: u64 = 0,
}
Read and write storage:
#[storage(read)]
fn get_counter() -> u64 {
storage.counter.read()
}
#[storage(read, write)]
fn increment() {
let current = storage.counter.read();
storage.counter.write(current + 1);
}
Access a StorageMap:
#[storage(read, write)]
fn set_balance(addr: Address, amount: u64) {
storage.balances.insert(addr, amount);
}
#[storage(read)]
fn get_balance(addr: Address) -> u64 {
storage.balances.get(addr).try_read().unwrap_or(0)
}
Warning
Known issue:
StorageVec::insert()does not work correctly with nested storage types (issue #6040). If you need nested storage (aStorageVecof structs that themselves contain storage-typed fields), use aStorageMap<u64, T>indexed manually as a workaround until this is resolved upstream.
5.13 Configuring CI with forc
A minimal CI workflow (GitHub Actions):
name: Sway CI
on: [push, pull_request]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install fuelup
run: curl https://install.fuel.network | sh -s -- --no-modify-path
env:
FUELUP_HOME: ~/.fuelup
- name: Add fuelup to PATH
run: echo "$HOME/.fuelup/bin" >> $GITHUB_PATH
- name: Check formatting
run: forc fmt --check
- name: Type-check
run: forc check --locked
- name: Build
run: forc build --locked --release --error-on-warnings
- name: Test
run: forc test --locked
Key flags for CI:
--locked— ensuresForc.lockis respected and not silently updated--error-on-warnings— fails on any warningforc fmt --check— enforces formatting
5.14 Using forc-tx for transaction construction
forc-tx is a plugin for building raw Fuel transactions:
forc tx
This is an advanced tool primarily for protocol integrators who need to construct and inspect transaction structures without going through a higher-level SDK. For most application development, use the Fuel Rust SDK (fuels) instead.
5.15 Inspecting installed plugins
List all forc plugin binaries currently on your PATH:
forc plugins
Sample output:
forc-debug
forc-doc
forc-fmt
forc-lsp
forc-migrate
forc-publish
forc-tx
forc-client (provides forc-deploy, forc-run)
If an expected plugin is missing, verify it was installed via fuelup and that ~/.fuelup/bin is on your PATH.
5.16 Working with the EVM build target
Sway includes experimental EVM compilation support:
forc build --build-target evm
This compiles your Sway program to EVM bytecode. The feature is experimental and not all Sway constructs are supported. The default and primary target remains fuel. Do not use EVM output for production unless you have verified full feature compatibility.
5.17 Understanding the output directory structure
After a build, out/ contains:
out/
debug/ # or release/
my-contract.bin # FuelVM bytecode (contracts, scripts, predicates)
my-contract-abi.json # ABI descriptor (contracts, libraries)
my-contract-storage_slots.json # Storage slot declarations (contracts)
doc/ # Generated HTML docs (forc doc)
The -storage_slots.json file describes all storage variables and their slot keys. It is used by deployment tooling to initialize storage correctly.
6. Troubleshooting
Error: could not find Forc.toml in this directory or any of its parents
Cause: You are running a forc command outside of a Sway project directory.
Solution: Navigate to your project root (where Forc.toml lives) before running forc commands, or use --path:
forc build --path /absolute/path/to/my-project
Prevention: Always verify your working directory with ls Forc.toml before running forc commands.
Error: Dependency cycle detected
Cause: Package A depends on Package B, which directly or transitively depends back on Package A.
Solution: Refactor shared code into a third library that both A and B depend on, breaking the cycle.
Prevention: Design your library hierarchy as a DAG (directed acyclic graph). Libraries should only depend downward, never in circles.
Error: could not compile with no further details
Cause: Often occurs when the project has never been built and Forc.lock is missing or stale.
Solution:
forc clean
forc build
If that fails, run with verbose output:
forc build -v
Prevention: Don't .gitignore Forc.lock for contracts and scripts.
Error: Compiler ICE (Internal Compiler Error)
Cause: You have triggered an unhandled compiler code path — a bug. Active known ICEs include array const generics edge cases (issues #7603, #7604, #7602).
Solution: File an issue at https://github.com/FuelLabs/sway/issues with a minimal reproduction. In the meantime, simplify the problematic construct:
- Avoid combining type aliases with const generic arrays (#7604)
- Avoid self-indexing (
self[i]) with const generics (#7602) - Avoid invalid unification of arrays with const generics (#7603)
Prevention: Keep Sway toolchain updated. ICEs are fixed quickly once reported.
Error: could not find symbol with const generic functions
Cause: Issue #7599. The compiler fails to resolve symbols in certain const generic function contexts.
Solution: As a workaround, avoid relying on const generic functions in complex symbol resolution chains. Refactor to pass concrete types where possible until the fix is merged.
Error: Type is not indexable on const generic array reassignment
Cause: Issue #7600. Reassigning to a const generic array emits a misleading "Type is not indexable" error instead of a clear description of the actual issue.
Solution: Report the issue and avoid const generic arrays in reassignment positions as a workaround. Use a plain fixed-size array or a Vec in the interim.
Error: confusing out-of-bounds message with const generic arrays
Cause: Issue #7605. Out-of-bounds error messages for const generic arrays are confusing or point to incorrect source locations.
Solution: Carefully verify array sizes match usage. Compile with --print-ast to inspect resolved types and confirm the sizes the compiler sees.
Warning: CEI pattern violation appears inconsistently
Cause: Issue #7606. CEI (Checks-Effects-Interactions) pattern warnings are emitted nondeterministically — the same code may or may not warn across builds.
Solution: Follow CEI pattern discipline regardless of whether a warning appears:
- All checks first
- Storage writes (effects)
- External calls (interactions) last
Prevention: Do not rely on CEI warnings to catch violations. Manually audit all sequences involving storage writes followed by external calls.
Error: lock file is out of date
Cause: Forc.lock does not match Forc.toml. Happens when you modify Forc.toml manually or pull changes from git that include Forc.toml updates.
Solution:
forc update
Or, to update a specific dependency:
forc update std
Prevention: Always run forc update after pulling changes that modify Forc.toml.
Error: failed to download crate
Cause: Network issue or the registry is temporarily unavailable.
Solution:
# Retry the build (transient failure)
forc build
# If repeatedly failing, use local cache
forc build --offline
Prevention: For production CI, pin dependencies to specific git tags and run with --locked to avoid live registry fetches during builds.
Error: unauthorized from forc publish
Cause: The FORC_PUB_TOKEN environment variable is missing or invalid.
Solution:
export FORC_PUB_TOKEN=your_token_here
forc publish
Or pass it directly:
forc publish --token your_token_here
Error: missing storage annotation
Cause: A function reads or writes storage without being annotated with #[storage(read)] or #[storage(read, write)].
Solution: Add the appropriate storage annotation:
// Read-only
#[storage(read)]
fn get_value() -> u64 {
storage.value.read()
}
// Read and write
#[storage(read, write)]
fn set_value(v: u64) {
storage.value.write(v);
}
Error: cannot find type for an imported type
Cause: The type is defined in a dependency but not imported with use.
Solution:
use std::address::Address;
use my_lib::types::MyStruct;
Or use the full path inline:
let addr: std::address::Address = ...;
Error: main function not found
Cause: Your project is a script or predicate and the entry point is missing, or you have an incorrect program type declaration.
Solution:
- Scripts and predicates need
fn main()insrc/main.sw - Contracts need an ABI +
impl ContractName for Contract { }— no baremain - Libraries use
src/lib.swwith nomain
forc test panics or hangs indefinitely
Cause: Issue #7551. The compiler can run infinitely on specific test inputs due to pathological type inference loops.
Solution: Identify which test triggers the hang using --filter to binary-search:
forc test --filter test_suspect_fn
Simplify the triggering function and file an issue with the minimal reproduction.
Error: confusing __gtf error with non-const tx_field_id
Cause: Issue #7564 (closed/fixed in current release). The error message when passing a non-const value to tx_field_id in __gtf was misleading.
Solution: Update your toolchain — this is fixed. The tx_field_id argument to __gtf must be a compile-time constant.
fuelup update
LSP shows no completions or diagnostics
Cause: forc-lsp is not found on PATH, or the editor extension is misconfigured.
Solution:
which forc-lsp # verify it's on PATH
forc lsp --version # verify it runs
In VS Code, open the Output panel → select "Sway Language Server" to see LSP logs.
Prevention: Always install via fuelup — it manages all plugin binaries in a consistent location.
Build succeeds but ABI JSON is missing
Cause: Only contracts and libraries produce ABI JSON. Scripts and predicates produce only .bin output.
Solution: Verify your program type declaration:
contract; // → produces -abi.json
library; // → produces -abi.json
script; // → .bin only, no ABI
predicate; // → .bin only, no ABI
Error: Forc.toml parse error
Cause: Invalid TOML syntax or an unrecognized field in Forc.toml.
Solution: Validate your TOML with a linter (taplo check Forc.toml). Common mistakes:
- Missing quotes around string values
- Using
:instead of=for assignments - Typos in section headers (
[dependecies]instead of[dependencies])
forc build exits with no error but output is in unexpected location
Cause: Running forc build inside a workspace root builds all members. Each member's output goes into its own out/ subdirectory.
Solution: Check each member's out/ directory:
ls token-contract/out/debug/
ls exchange-contract/out/debug/
Or build a specific member:
forc build --path token-contract
Error: storage access is not allowed in this context
Cause: Attempting to read or write storage in an unannotated context, a pure function, or a non-contract program type.
Solution: Add the #[storage(read)] or #[storage(read, write)] annotation to the function. Ensure the file declares contract; at the top — storage is only available in contracts.
forc fmt produces unexpected changes
Cause: Your editor may be running an older forc-fmt binary with different formatting rules than the project's expected version.
Solution:
forc fmt --version
fuelup update
Prevention: Keep fuelup updated. The formatter's output is tied to the toolchain version.
Error: unresolved import
Cause: Attempting to use a module path that doesn't exist in the dependency tree.
Solution: Verify the dependency is in Forc.toml, run forc update, and confirm the module path by checking the library's src/lib.sw for pub mod declarations.
Error: predicate root mismatch at runtime
Cause: The predicate binary was rebuilt after the UTXO was locked to an older predicate root hash, causing the hashes to differ.
Solution: Lock UTXOs to a specific, stable predicate build. Use forc predicate-root to confirm the hash before locking funds:
forc predicate-root --path my-predicate
Pin the compiled binary (not just the source) if the predicate manages real funds.
Error: cannot move value or use of moved value
Cause: Sway has ownership semantics similar to Rust. Values can only be used once unless they implement the Copy trait.
Solution: Use references where the value should not be consumed, or derive Copy on your struct if it wraps only copy-able types:
// Pass by reference to avoid move (requires `references` feature)
fn inspect(val: &MyStruct) { ... }
Error: __smo or __log intrinsic compilation issue
Cause: Issue #7572 (closed/fixed in current release). __smo and __log intrinsics were not correctly compiled in certain contexts.
Solution: Update your toolchain:
fuelup update
Large contracts exceed gas limits during testing
Cause: Complex contracts with deeply nested loops or many storage accesses accumulate high gas costs during test execution.
Solution:
- Split large contracts into multiple cooperating contracts
- Move pure computation into scripts
- Profile the specific function using the Fuel Rust SDK's gas estimation utilities
forc build is extremely slow
Cause: Large dependency tree or pathological type inference on complex generics.
Solution:
# Use check for incremental feedback (faster than full build)
forc check
# Profile compilation phases
forc build --time-phases
# Skip network operations if dependencies are cached
forc build --offline
Prevention: Keep the dependency tree shallow. Each additional dependency increases compilation time.
Error: StorageKey impl fails to resolve get() on StorageMap in generic context
Cause: Issue #7559. Generic StorageKey implementations fail to resolve get() on StorageMap when used through generic type parameters.
Solution: Use concrete types rather than generic StorageKey parameters when working with StorageMap. File the specific reproduction with issue #7559 if you encounter a new variant.
Error: version conflict between dependencies
Cause: Two dependencies require incompatible versions of a shared transitive dependency.
Solution: Check which packages are in conflict:
forc build -v 2>&1 | grep "version"
Try pinning the conflicting transitive dependency directly in your Forc.toml, or upgrade/downgrade one of your direct dependencies to a version that resolves the conflict.
forc doc generates empty or incomplete documentation
Cause: Doc comments must use /// (triple slash), not // or /* */.
Solution:
/// This is a doc comment and will appear in generated docs.
pub fn my_func() {}
// This is a regular comment and will NOT appear in docs.
fn internal_func() {}
Error: entry point not found in library
Cause: A library project has src/main.sw instead of src/lib.sw.
Solution: Rename the entry file:
mv src/main.sw src/lib.sw
Update Forc.toml if it explicitly sets entry = "main.sw":
[project]
entry = "lib.sw"
7. FAQ
Q: What is Sway, and how is it different from Solidity?
Sway is a statically-typed, compiled language for the Fuel blockchain, syntactically inspired by Rust. Unlike Solidity (EVM-based, dynamic dispatch, JavaScript-like syntax), Sway targets the FuelVM with an ownership model, pattern matching, algebraic types, and generics. Sway is not EVM-compatible by default, though an experimental --build-target evm flag exists.
Q: What is forc?
forc (Fuel Orchestrator) is the official build tool and package manager for Sway. It handles project creation, compilation, testing, dependency management, formatting, and more — analogous to Rust's cargo. It ships with 16 built-in subcommands and is extended by plugin binaries.
Q: What are the four program types in Sway?
contract (stateful, on-chain callable), script (one-shot transaction runner), predicate (boolean spending condition for UTXOs), and library (reusable code module). Every Sway source file declares its type as the very first statement.
Q: What is the difference between forc build and forc check?
forc check performs type-checking and semantic analysis without generating any output files. It is faster. forc build does everything check does, plus IR generation, optimization, and bytecode/ABI output. Use check for fast feedback during development, build when you need the actual artifacts.
Q: Where does forc put compiled output?
out/debug/ for debug builds, out/release/ for release builds. Override with --output-directory. Each build produces:
- A
.binfile for contracts, scripts, and predicates - A
-abi.jsonfile for contracts and libraries - A
-storage_slots.jsonfile for contracts with storage
Q: What is Forc.lock and should I commit it?
Forc.lock is an auto-generated lockfile that records the exact resolved version of every dependency. Commit it for contracts, scripts, and predicates (binary outputs) to ensure reproducible builds. For library packages, it is conventional to gitignore it so consumers resolve their own compatible versions.
Q: How do I pin a dependency to a specific git commit?
In Forc.toml:
[dependencies]
my-lib = { git = "https://github.com/example/my-lib", rev = "abc1234" }
Q: Can I use Rust crates in Sway?
No. Sway is a separate language that compiles to FuelVM bytecode. It cannot import Rust crates. The Rust SDK (fuels-rs) is used on the caller side (scripts, deployment tools, integration tests) — not inside Sway programs themselves.
Q: How do I write tests for a contract?
Inline unit tests use #[test]:
#[test]
fn test_my_function() {
assert(my_function() == expected_value);
}
For integration tests (deploying and calling a real contract with state), use the Fuel Rust SDK (fuels) in a Rust test harness.
Q: What is std in the Sway dependency tree?
std is the Sway standard library. It is implicitly added to most projects. It provides common types (Address, ContractId, b256), utilities, cryptographic functions, and storage primitives. Override the implicit std location with FORC_IMPLICIT_STD_* environment variables.
Q: How do I enable experimental features?
Via CLI:
forc build --experimental str_array_no_padding
Via environment:
FORC_EXPERIMENTAL=str_array_no_padding forc build
Via Forc.toml:
[project]
experimental = ["str_array_no_padding"]
Q: What is a predicate root?
The predicate root is the SHA-256 hash of the predicate's bytecode. It functions as the "address" that UTXOs are locked to. A transaction can spend such a UTXO only if it includes the matching predicate bytecode that hashes to the same root and evaluates to true. Compute it with forc predicate-root.
Q: How do I find the contract ID before deploying?
forc contract-id
The contract ID is deterministically derived from the bytecode. It will be the same every time you compile the same source at the same toolchain version.
Q: How do I add IDE support?
For VS Code: install the Sway extension from the marketplace. It uses forc-lsp for real-time diagnostics, completions, and hover docs. For Neovim/Helix: configure forc-lsp as a language server via nvim-lspconfig or your editor's equivalent LSP configuration.
Q: Can contracts call other contracts?
Yes. Use an ABI cast:
let other = abi(OtherAbi, other_contract_id.into());
let result = other.some_function(arg);
This is how DeFi protocols, token standards, and multi-contract systems are built on Fuel.
Q: What is CEI order and why does it matter?
CEI stands for Checks-Effects-Interactions — a security pattern for preventing reentrancy:
- Checks — validate inputs, balances, permissions
- Effects — write to storage
- Interactions — call external contracts
Violating this order (writing to an external contract before updating state) opens reentrancy vulnerabilities. Follow CEI order regardless of whether the compiler warns about it (warning generation is nondeterministic per issue #7606).
Q: How do I generate shell completions?
forc completions --shell bash >> ~/.bash_completion
forc completions --shell zsh > ~/.zfunc/_forc
forc completions --shell fish > ~/.config/fish/completions/forc.fish
forc completions --shell powershell >> $PROFILE
Q: What is forc-migrate for?
When Sway introduces breaking language changes in a new version, forc-migrate automates migration of your source code. Run forc migrate and review the proposed changes before committing.
Q: What does forc plugins show?
It lists all forc-* plugin binaries installed on your system and found in PATH. Plugins extend forc with additional subcommands. The main plugins are: forc-debug, forc-doc, forc-fmt, forc-lsp, forc-migrate, forc-publish, forc-tx, forc-client.
Q: How does Sway handle integer overflow?
In debug mode, integer overflow causes a runtime revert. Use checked arithmetic from std for explicit overflow handling:
let result = a.checked_add(b).unwrap_or(u64::max());
Q: Can I use Sway with a local Fuel node?
Yes. Run a local fuel-core node and deploy with:
forc deploy --node-url http://localhost:4000
fuel-core is a separate binary managed by fuelup.
Q: What is the evm build target?
forc build --build-target evm compiles Sway to EVM bytecode instead of FuelVM. This is experimental. The primary and supported target is fuel.
Q: What is forc addr2line used for?
When a transaction fails at a specific opcode index, forc addr2line translates that index back to the source file and line number:
forc addr2line --contract-id 0x... --opcode-index 42
Q: How does forc template work?
forc template --url https://github.com/FuelLabs/sway-applications \
--template-name counter \
my-project
It fetches the git repository, extracts the named subdirectory as a template, and initializes a new project. A template is any standard Sway project hosted in a git repo.
Q: Why does my library import show as my_lib when Forc.toml says my-lib?
Hyphens in package names are automatically converted to underscores for use in Sway use statements. This matches Rust's convention. my-lib in Forc.toml becomes my_lib in source imports.
Q: What is forc parse-bytecode for?
It decodes a compiled .bin file into human-readable FuelVM opcodes. Useful for:
- Low-level debugging
- Verifying that a binary matches expected instructions
- Understanding what the compiler generated from your source
forc parse-bytecode out/debug/my-contract.bin
Q: What are the FORC_IMPLICIT_STD_* env vars for?
They override which version of the std library forc uses, without modifying Forc.toml. Primarily for toolchain developers testing against a custom or unreleased std build. Most users should never set these.
Q: Is there a way to see what forc is doing internally?
forc build --verbose # more detailed output
forc build --time-phases # phase-by-phase timing
forc build --print-ir initial # see IR before optimization
forc build --print-asm final # see final assembly
forc build --metrics-outfile metrics.json # write timing to JSON
These flags give progressively deeper visibility into the compilation pipeline.
Q: How do I format only a specific file?
forc fmt src/main.sw
Pass the file path as an argument to forc fmt.
Q: What does forc tx do?
forc-tx is a transaction builder plugin. It helps construct raw Fuel transactions for advanced use cases — primarily for tooling developers and protocol integrators who need direct transaction construction without a higher-level SDK.
Q: Can I define multiple contracts in one Sway file?
No. Each Sway source file has exactly one program type declaration, and a project has one entry point. Separate contracts must be in separate forc projects (or workspace members).
Q: What happens to forc build output if I run it multiple times?
The out/ directory is overwritten on each successful build. Previous output for the same profile is replaced. Run forc clean if you want a completely fresh start with no cached artifacts.
Q: How do I run a predicate?
Predicates are not "run" directly — they are evaluated by the Fuel VM when a transaction attempts to spend a UTXO locked to that predicate's root hash. Use the Fuel Rust SDK (fuels) to construct and submit transactions that include predicate bytecode as part of the transaction inputs.
Q: What is the difference between forc new and forc init?
forc new <path> creates the directory and initializes a project inside it. forc init initializes a project in an existing directory (either the current directory or one specified with --path). Use forc init when the directory already exists and may already contain files you want to keep.
Q: What does --terse do in forc build?
--terse suppresses all informational output and only shows warnings and errors. Useful when integrating forc into scripts or pipelines where clean output is required, or when you want to focus on actionable diagnostic messages without surrounding noise.
Q: How do I use a custom build profile?
Define it in Forc.toml:
[build-profile.staging]
# custom profile settings
Then use it:
forc build --build-profile staging
Q: What does the --locked flag do and when should I use it?
--locked requires Forc.lock to already exist and match Forc.toml exactly. If they diverge, forc fails rather than silently updating the lock file. Always use --locked in CI to catch unintended dependency changes before they reach production.