# How Do Predicates Differ from Contracts in Sway: A Complete Technical Guide

> Learn the key differences between Sway predicates and contracts. Understand their stateless vs stateful nature, UTXO unlocking, ABI exposure, and data persistence.

- Repository: [Fuel Labs/sway](https://github.com/FuelLabs/sway)
- Tags: deep-dive
- Published: 2026-03-04

---

**Predicates in Sway are stateless, pure functions that return a boolean to unlock UTXOs, while contracts are stateful components that expose an ABI and can persist data to storage.**

Understanding how predicates differ from contracts in Sway is essential for building secure applications on the Fuel VM. While both are compiled from Sway source code, they serve fundamentally different purposes in the transaction lifecycle. This guide examines the architectural distinctions, compiler enforcement mechanisms, and practical implementation patterns defined in the FuelLabs/sway repository.

## Core Architectural Differences Between Predicates and Contracts in Sway

### Program Purpose and Runtime Behavior

A **predicate** serves as a spending condition for a UTXO. It evaluates to a Boolean value, and if the result is `true`, the funds owned by the predicate's address become spendable. Predicates execute in a read-only context during transaction validation.

A **contract** acts as a stateful on-chain component. It defines an Application Binary Interface (ABI), can store persistent data, receives external calls, and emits receipts. Contracts maintain state across multiple transactions and support complex interaction patterns.

### Entry Points and Function Signatures

The entry point requirements differ strictly between the two program types.

In [`sway-core/src/language/ty/program.rs`](https://github.com/FuelLabs/sway/blob/main/sway-core/src/language/ty/program.rs), the compiler defines `TyProgramKind::Predicate` to require a single `main()` function that **must return `bool`**. The compiler enforces this return type check during type validation.

```rust
pub enum TyProgramKind {
    Contract { entry_function: Option<...>, abi_entries: Vec<...> },
    Library { name: String },
    Predicate { entry_function: DeclId<TyFunctionDecl>, main_function: DeclId<TyFunctionDecl> },
    Script { entry_function: DeclId<TyFunctionDecl>, main_function: DeclId<TyFunctionDecl> },
}

```

Contracts, represented by `TyProgramKind::Contract`, use an `abi` block to define external functions. While contracts may include a `main()` function (called the *loader*), the public interface is generated from the ABI, not the entry function signature.

## Key Technical Constraints: Storage, Purity, and State

### Storage Restrictions in Predicates

Predicates are explicitly prohibited from declaring storage blocks. In the compiler's validation logic, the `validate_root` function checks for `storage_decl` and rejects predicates that attempt to define storage (lines 49-58 in the validation module).

Contracts, conversely, can declare persistent storage using the `storage` keyword. The compiler type-checks storage declarations and compiles them into the contract's storage layout, accessible across multiple transactions.

### Function Purity Requirements

All functions within a predicate must be **pure**. The compiler calls `disallow_impure_functions` for non-contract programs (lines 43-48), rejecting any function that attempts to perform impure operations like storage reads or writes.

Contracts permit impure functions because they must interact with storage. Functions that read from or write to the contract's state are inherently impure, and the compiler allows this behavior within contract contexts.

## ABI Generation and Deployment Models

### Bytecode Blobs vs. ABI Files

Predicates do not generate an ABI file. Instead, the compiler outputs the bytecode as a **blob**, identified by its bytecode root (which serves as the predicate address). External systems interact with predicates by providing input data that satisfies the predicate's logic.

Contracts generate a complete ABI file (`*.abi.json`) that defines the contract's interface, including method signatures, input types, and output types. This ABI is used by callers to construct valid contract calls.

### Deployment and Runtime Restrictions

When deploying a predicate, `forc deploy` uploads the bytecode blob and generates a loader. The predicate address is derived from the bytecode hash.

At runtime, predicates cannot use **contract VM instructions** such as `contract::balance_of`. The Fuel VM explicitly restricts predicates from accessing contract-specific opcodes. If a predicate attempts to revert or execute an impure opcode, the evaluation automatically yields `false` rather than generating a transaction receipt.

Contracts have full access to the Fuel VM instruction set, including contract-specific opcodes for balance checks, contract-to-contract calls, and receipt generation.

## Code Examples: Predicate vs. Contract Implementation

### Example 1: Simple Predicate with Boolean Return

This predicate always evaluates to `true`, allowing anyone to spend the UTXO locked to its address:

```sway
// src/main.sw
predicate;

fn main() -> bool {
    // Predicate logic – any condition that evaluates to true unlocks the UTXO.
    true
}

```

The compiled output is a bytecode blob identified by its root hash. As documented in [`docs/reference/src/documentation/language/program-types/predicate.md`](https://github.com/FuelLabs/sway/blob/main/docs/reference/src/documentation/language/program-types/predicate.md), this bytecode serves as the predicate address.

### Example 2: Parameterized Predicate with Input Data

Predicates can accept runtime arguments that influence the Boolean decision. This example checks if the spent coin amount matches an expected value:

```sway
// src/main.sw
predicate;

fn main(expected: u64) -> bool {
    // `msg_amount` is the amount of coins being spent.
    let msg_amount = msg::coin_amount();
    msg_amount == expected
}

```

The `expected` parameter is provided as predicate data when spending the UTXO. According to the predicate documentation, this allows flexible spending conditions without changing the bytecode.

### Example 3: Stateful Contract with Storage and ABI

Contracts define persistent storage and expose methods through an ABI:

```sway
// src/main.sw
contract;

abi MyContract {
    // Public function exposed via the ABI.
    fn get_balance() -> u64;
    fn set_balance(new_balance: u64);
}

storage {
    balance: u64,
}

impl MyContract for Contract {
    fn get_balance() -> u64 {
        self.balance
    }

    fn set_balance(new_balance: u64) {
        self.balance = new_balance;
    }
}

```

This contract generates an ABI file ([`MyContract.abi.json`](https://github.com/FuelLabs/sway/blob/main/MyContract.abi.json)) and can store data persistently across transactions. The implementation references the `Contract` program type defined in [`sway-core/src/language/ty/program.rs`](https://github.com/FuelLabs/sway/blob/main/sway-core/src/language/ty/program.rs).

## Compiler Implementation and Validation

### TyProgramKind Enum Distinction

The Sway compiler distinguishes between predicates and contracts through the `TyProgramKind` enum in [`sway-core/src/language/ty/program.rs`](https://github.com/FuelLabs/sway/blob/main/sway-core/src/language/ty/program.rs):

```rust
pub enum TyProgramKind {
    Contract { entry_function: Option<...>, abi_entries: Vec<...> },
    Library { name: String },
    Predicate { entry_function: DeclId<TyFunctionDecl>, main_function: DeclId<TyFunctionDecl> },
    Script { entry_function: DeclId<TyFunctionDecl>, main_function: DeclId<TyFunctionDecl> },
}

```

The **Predicate** variant stores references to the `main` function that must return a `bool`, while the **Contract** variant tracks ABI entries and an optional loader function.

### Validation Logic in validate_root

During type checking, the `validate_root` function branches on `TreeType` to enforce program-specific constraints.

For **predicates**, the compiler:

1. Ensures exactly one `main` function exists
2. Confirms `main` returns a Boolean via `is_bool()` checks (lines 52-57)
3. Calls `check_no_ref_main` to disallow mutable reference parameters
4. Prohibits impure functions via `disallow_impure_functions` (lines 43-48)

For **contracts**, the compiler:

1. Allows storage declarations (`StorageDecl`)
2. Permits impure functions (no call to `disallow_impure_functions`)
3. Collects ABI methods from `impl` blocks that implement an `abi` trait

These divergent validation paths guarantee that predicates remain **pure, stateless, and boolean-valued**, while contracts retain **stateful, mutable, and ABI-exposed** capabilities.

## Summary

- **Predicates** are pure, stateless spending conditions that return a boolean to unlock UTXOs, while **contracts** are stateful on-chain components with persistent storage and exposed ABIs.
- The Sway compiler enforces these distinctions through the `TyProgramKind` enum in [`sway-core/src/language/ty/program.rs`](https://github.com/FuelLabs/sway/blob/main/sway-core/src/language/ty/program.rs), specifically validating that predicates have a single `main()` returning `bool` and disallowing storage and impure functions.
- Predicates compile to bytecode blobs identified by their root hash without generating an ABI, whereas contracts produce `*.abi.json` files and support full Fuel VM instruction sets including contract-specific opcodes.
- At runtime, predicates execute in a restricted environment where reverts or impure operations automatically yield `false`, while contracts can emit receipts and maintain state across transactions.

## Frequently Asked Questions

### Can a predicate access storage in Sway?

No, predicates cannot declare or access storage in Sway. The compiler explicitly prohibits storage declarations for predicates during the `validate_root` phase, enforcing the `storage_decl` check to ensure predicates remain stateless. This restriction ensures predicates evaluate deterministically based only on their inputs and the transaction context, without relying on persistent on-chain state.

### Why must predicates return a boolean in Sway?

Predicates must return a boolean because they function as spending conditions for UTXOs. The compiler enforces this requirement in `TyProgramKind::Predicate` by checking that the `main()` function returns a `bool` type. If the predicate evaluates to `true`, the UTXO is unlocked and can be spent; if `false`, the transaction is invalid. This binary evaluation model aligns with the Fuel VM's predicate verification system.

### How does the Fuel VM treat predicates differently from contracts at runtime?

The Fuel VM executes predicates in a restricted, read-only environment during transaction validation, while contracts run in a full execution context. Predicates cannot use contract-specific VM instructions like `contract::balance_of`, and any revert or impure operation automatically causes the predicate to evaluate to `false` rather than generating a receipt. Contracts have full access to the Fuel VM instruction set, can emit receipts, read and write storage, and maintain persistent state across multiple transactions.

### Can I call a contract from within a predicate?

No, you cannot call a contract from within a predicate. The Fuel VM explicitly restricts predicates from accessing contract VM instructions, which includes contract calls. Additionally, the Sway compiler enforces purity requirements for predicates through `disallow_impure_functions`, preventing any operations that could cause side effects or external calls. Predicates must evaluate based solely on their input parameters and the immediate transaction context without interacting with external contracts or storage.