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:
Imports (optional)
Program scope (required)
Mappings (optional)
Records and Structs (optional)
Functions (required)
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