Jesse A. Green / notebook

Sway — User Guide

Generated May 2026 from FuelLabs/sway at e12e611

AI-generated documentation. The library is checked by sampling (three repositories so far); this guide was not individually reviewed. Verify against the project before relying on it.

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)
  • git installed 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 check instead of forc build when 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


2. Installation

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 update rather than re-running the install curl script. Re-running the installer may reset your default toolchain selection. fuelup update intelligently 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 fpath includes ~/.zfunc and that compinit runs in your .zshrc. Add fpath=(~/.zfunc $fpath) before the autoload -Uz compinit && compinit line.

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 forc or fuelup from 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, and forc check operate on all members. Use --path to 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):

  1. Forc.toml manifest declaration (highest precedence)
  2. CLI --no-experimental <feature>
  3. CLI --experimental <feature>
  4. FORC_NO_EXPERIMENTAL environment variable
  5. FORC_EXPERIMENTAL environment 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 custom std build without modifying Forc.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.

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.lock to version control for binary applications (contracts, scripts, predicates). For libraries, it is conventional to gitignore Forc.lock so 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:

  1. Ensure Forc.toml has name, version, license, and description set.
  2. Your package must be compilable without errors.
  3. 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 (version in Forc.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 --check to your CI pipeline to enforce consistent formatting. The --check flag 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-lsp reads Forc.toml to understand your project structure. Always open VS Code (or your editor) at the project root — the directory containing Forc.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 in Forc.toml uses 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 (a StorageVec of structs that themselves contain storage-typed fields), use a StorageMap<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 — ensures Forc.lock is respected and not silently updated
  • --error-on-warnings — fails on any warning
  • forc 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:

  1. All checks first
  2. Storage writes (effects)
  3. 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() in src/main.sw
  • Contracts need an ABI + impl ContractName for Contract { } — no bare main
  • Libraries use src/lib.sw with no main

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 .bin file for contracts, scripts, and predicates
  • A -abi.json file for contracts and libraries
  • A -storage_slots.json file 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:

  1. Checks — validate inputs, balances, permissions
  2. Effects — write to storage
  3. 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.