Skip to main content

Program Declaration

Every Leo program must declare a program scope with a unique program identifier. The program identifier follows the format name.aleo.
program token.aleo {
    // Program contents
}

Program Structure

A Leo program consists of several components, declared in the following order:
  1. Imports (optional)
  2. Program scope (required)
  3. Mappings (optional)
  4. Records and Structs (optional)
  5. Functions (required)
  6. Finalize functions (optional)

Complete Example

program lottery.aleo {
    // 1. Mapping declarations
    mapping num_winners: u8 => u8;
    
    // 2. Record definitions
    record Ticket {
        owner: address,
    }
    
    // 3. Functions
    fn play() -> (Ticket, Final) {
        let ticket: Ticket = Ticket {
            owner: self.caller,
        };
        return (ticket, final { finalize_play() });
    }
}

// 4. Finalize functions (outside program scope)
final fn finalize_play() {
    assert(block.height <= 1000u32);
    assert(ChaCha::rand_bool());
    
    let winners: u8 = num_winners.get_or_use(0u8, 0u8);
    assert(winners < 5u8);
    num_winners.set(0u8, winners + 1u8);
}

Program Components

Mappings

Mappings define on-chain storage with a key-value structure:
mapping account: address => u64;
mapping balances: address => u128;
mapping num_winners: u8 => u8;

Records

Records define private, owned data structures:
record token {
    owner: address,
    amount: u64,
}

record Ticket {
    owner: address,
}

Structs

Structs define composite data types:
struct Row {
    c1: u8,
    c2: u8,
    c3: u8
}

struct Board {
    r1: Row,
    r2: Row,
    r3: Row,
}

Functions

Functions define the executable logic of your program:
fn new() -> Board {
    return Board {
        r1: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
        r2: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
        r3: Row { c1: 0u8, c2: 0u8, c3: 0u8 },
    };
}

Imports

Leo programs can import other programs to reuse functionality:
import credits.aleo;
import token.aleo;

program my_app.aleo {
    // Use imported programs
    fn transfer_credits() {
        // Call functions from credits.aleo
    }
}
Imported programs must be available in your project’s dependencies or published on the Aleo network.

Self References

Within a function, you can access the caller’s address using self.caller:
fn transfer_public(public receiver: address, public amount: u64) -> Final {
    return final { finalize_transfer_public(self.caller, receiver, amount); };
}

Block Context

Finalize functions can access blockchain context through the block object:
final fn finalize_play() {
    // Access current block height
    assert(block.height <= 1000u32);
}

Program Naming

Valid Names

Program names must:
  • Be lowercase
  • Contain only alphanumeric characters and underscores
  • End with .aleo
  • Be unique on the Aleo network
program token.aleo { }
program my_app.aleo { }
program game2048.aleo { }

Multiple Programs

A Leo project typically contains:
  • One main program in src/main.leo
  • Optional imported programs
  • Build configuration in program.json

Project Structure

my-project/
├── src/
│   └── main.leo          # Main program
├── imports/
│   └── helper.leo        # Imported programs
├── build/                # Compiled output
├── program.json          # Project configuration
└── README.md

Program Constants

You can define program-level constants:
program token.aleo {
    const MAX_SUPPLY: u64 = 1000000u64;
    const DECIMALS: u8 = 6u8;
    
    mapping account: address => u64;
    
    fn get_max_supply() -> u64 {
        return MAX_SUPPLY;
    }
}

Best Practices

1. Clear Naming

Use descriptive names for programs, functions, and variables:
// Good
program token_swap.aleo
fn calculate_exchange_rate()

// Avoid
program p.aleo
fn calc()

2. Organize by Functionality

Group related functions and types together:
program token.aleo {
    // Storage
    mapping account: address => u64;
    
    // Types
    record token { ... }
    
    // Mint functions
    fn mint_public() { ... }
    fn mint_private() { ... }
    
    // Transfer functions
    fn transfer_public() { ... }
    fn transfer_private() { ... }
}

3. Document Public Interfaces

Add comments to explain function behavior:
// The function `mint_public` issues the specified token amount 
// for the token receiver publicly on the network.
fn mint_public(public receiver: address, public amount: u64) -> Final {
    return final { finalize_mint_public(receiver, amount); };
}

4. Separate Concerns

Use the finalize pattern to separate off-chain and on-chain logic:
// Off-chain: Create private records
fn transfer_private(sender: token, receiver: address, amount: u64) 
    -> (token, token) {
    // Private computation
}

// On-chain: Update public state
final fn finalize_transfer_public(
    public sender: address, 
    public receiver: address, 
    public amount: u64
) {
    // Public state changes
}

Next Steps

Data Types

Explore Leo’s type system

Functions

Learn about function declarations

Imports

Import other programs

Mappings

Use on-chain storage