Learn how to manage dependencies, configure your program manifest, and work with local and network imports in Leo.
Program Manifest (program.json)
Every Leo project has a program.json manifest file that defines metadata and dependencies.
Basic Structure
{
"program": "my_program.aleo",
"version": "0.1.0",
"description": "",
"license": "MIT",
"dependencies": null,
"dev_dependencies": null
}
Manifest Fields
The unique name of your program, must end with .aleo:"program": "token_manager.aleo"
Must follow naming rules:
- Start with a letter
- Contain only ASCII alphanumeric characters and underscores
- Not contain the keyword “aleo” in the name
- Not be a reserved keyword
Human-readable description of your program:"description": "A token management system for Aleo"
Software license identifier:Common options: MIT, Apache-2.0, GPL-3.0
External programs your code depends on:"dependencies": [
{
"name": "credits.aleo",
"location": "network"
}
]
dev_dependencies (optional)
Dependencies only needed for testing:"dev_dependencies": [
{
"name": "test_utils.aleo",
"location": "local",
"path": "../test_utils"
}
]
Dependency Types
Network Dependencies
Import deployed programs from the Aleo network:
"dependencies": [
{
"name": "credits.aleo",
"location": "network"
}
]
Use in your code:
import credits.aleo;
program my_program.aleo {
fn use_credits() {
// Access credits.aleo functions
}
}
Network dependencies are automatically fetched from the Aleo network and cached locally in ~/.aleo/registry/{network}/{program}/{edition}/.
Local Dependencies
Import programs from your local filesystem:
"dependencies": [
{
"name": "utils.aleo",
"location": "local",
"path": "../utils"
}
]
Project structure:
workspace/
├── my_program/
│ ├── program.json
│ └── src/
│ └── main.leo
└── utils/
├── program.json
└── src/
└── main.leo
Local dependencies use relative paths from the project directory. Use ../ to reference sibling directories.
Local Aleo File Dependencies
Import compiled .aleo bytecode files:
"dependencies": [
{
"name": "external.aleo",
"location": "local",
"path": "./imports/external.aleo"
}
]
The path must point to a .aleo file, not a directory. This is useful for pre-compiled dependencies.
Dependency Editions
Specify a specific version (edition) of a network dependency:
"dependencies": [
{
"name": "token.aleo",
"location": "network",
"edition": 2
}
]
Without an edition, Leo fetches the latest version:
{
"name": "token.aleo",
"location": "network"
// Fetches latest edition
}
Dependency Resolution
Leo resolves dependencies in topological order:
Parse Manifest
Leo reads program.json and collects all dependencies:let manifest = Manifest::read_from_file(path.join(MANIFEST_FILENAME))?;
Build Dependency Graph
Constructs a directed graph of program dependencies:let mut digraph = DiGraph::<Symbol>::new(Default::default());
Fetch Dependencies
For each dependency:
- Local: Read from filesystem
- Network: Fetch from Aleo network and cache
Program::fetch(name_symbol, edition, home_path, network, endpoint, no_cache)
Topological Sort
Orders programs so dependencies are compiled before dependents:let ordered_dependency_symbols =
digraph.post_order()
.map_err(|_| UtilError::circular_dependency_error())?;
Circular Dependency Detection
Leo detects and rejects circular dependencies:
program_a.aleo → program_b.aleo → program_a.aleo // Error!
Circular dependencies are not allowed. Restructure your programs to have a directed acyclic dependency graph.
Working with Imports
Basic Import
Import an external program:
import credits.aleo;
program my_program.aleo {
fn transfer_credits() {
// Use credits.aleo functions
}
}
Using Imported Types
Access structs and records from imported programs:
import child.aleo;
program parent.aleo {
fn create_foo() -> child.aleo/Foo {
return child.aleo/Foo {
x: 0u32,
y: 0u32
};
}
}
Calling Imported Functions
Invoke functions from dependencies:
import registry.aleo;
program relay.aleo {
fn check_user(addr: address) -> Final {
return final { finalize_check(addr); };
}
}
final fn finalize_check(addr: address) {
// Access external mapping
let is_registered: bool = Mapping::get(registry.aleo/users, addr);
assert_eq(is_registered, true);
}
Use the program.aleo/item syntax to reference imported items.
Package Structure
Leo expects a specific directory structure:
my_program/
├── program.json # Manifest
├── src/ # Source code
│ ├── main.leo # Main program
│ └── utils.leo # Additional modules (optional)
├── tests/ # Test files
│ └── test_*.leo
├── build/ # Build output
│ ├── main.aleo # Compiled program
│ └── imports/ # Compiled dependencies
│ └── credits.aleo
├── outputs/ # Compiler artifacts
│ └── *.ast
└── .gitignore
Creating Packages
Initialize a new package:
This creates:
- Package directory structure
program.json with defaults
- Basic
src/main.leo template
.gitignore file
- Sample test file
From crates/package/src/package.rs:420-442:
fn main_template(name: &str) -> String {
format!(
r#"// The '{name}' program.
program {name}.aleo {{
@noupgrade
constructor() {{}}
fn main(public a: u32, b: u32) -> u32 {{
let c: u32 = a + b;
return c;
}}
}}
"#
)
}
Package Constants
Key directory names (from crates/package/src/lib.rs):
pub const SOURCE_DIRECTORY: &str = "src";
pub const MAIN_FILENAME: &str = "main.leo";
pub const IMPORTS_DIRECTORY: &str = "build/imports";
pub const OUTPUTS_DIRECTORY: &str = "outputs";
pub const BUILD_DIRECTORY: &str = "build";
pub const ABI_FILENAME: &str = "abi.json";
pub const TESTS_DIRECTORY: &str = "tests";
Dependency Caching
Cache Location
Network dependencies are cached in:
~/.aleo/registry/{network}/{program}/{edition}/{program}.aleo
Example:
~/.aleo/registry/testnetv0/credits.aleo/0/credits.aleo
Disabling Cache
Force fresh dependency fetches:
Clearing Cache
Manually clear cached dependencies:
The cache improves build times by avoiding redundant network requests. Only disable it when debugging dependency issues.
Advanced Configuration
Multiple Dependencies
Combine local and network dependencies:
"dependencies": [
{
"name": "credits.aleo",
"location": "network"
},
{
"name": "utils.aleo",
"location": "local",
"path": "../utils"
},
{
"name": "token.aleo",
"location": "network",
"edition": 3
}
]
Test Dependencies
Separate dependencies for tests:
"dependencies": [
{
"name": "credits.aleo",
"location": "network"
}
],
"dev_dependencies": [
{
"name": "test_helpers.aleo",
"location": "local",
"path": "../test_helpers"
}
]
Test files automatically have access to both dependencies and dev_dependencies.
Dependency Conflicts
Leo detects conflicting dependencies:
// Error: Same program with different configurations
"dependencies": [
{
"name": "token.aleo",
"location": "network",
"edition": 1
},
{
"name": "token.aleo",
"location": "network",
"edition": 2 // Conflict!
}
]
Each dependency must have a unique name and consistent configuration across your dependency tree.
Program Size Limits
Programs have size limits (from crates/package/src/lib.rs):
pub const MAX_PROGRAM_SIZE: usize =
<snarkvm::prelude::TestnetV0 as snarkvm::prelude::Network>::MAX_PROGRAM_SIZE;
Leo validates program size during compilation:
if program_size > MAX_PROGRAM_SIZE {
return Err(UtilError::program_size_limit_exceeded(
name,
program_size,
MAX_PROGRAM_SIZE,
));
}
Best Practices
Use Semantic Versioning
Version your programs following semver:"version": "1.2.3" // MAJOR.MINOR.PATCH
- MAJOR: Breaking changes
- MINOR: New features, backwards compatible
- PATCH: Bug fixes
Pin Critical Dependencies
Specify editions for production dependencies:"dependencies": [
{
"name": "critical.aleo",
"location": "network",
"edition": 5 // Pin to specific version
}
]
Document Dependencies
Add descriptions explaining why dependencies are needed:{
"program": "my_program.aleo",
"description": "Token manager - depends on credits.aleo for transfers",
"dependencies": [{
"name": "credits.aleo",
"location": "network"
}]
}
Minimize Dependencies
Only include necessary dependencies to reduce:
- Build times
- Circuit size
- Attack surface
Test Dependency Changes
Run full test suite after updating dependencies:
Troubleshooting
Dependency Not Found
Error: Failed to fetch program from network
Solutions:
- Verify program name spelling
- Check network connectivity
- Confirm program exists on network
- Try with
--no-cache
Path Not Found
Error: Failed to load package at './relative/path'
Solutions:
- Verify relative path is correct
- Check directory structure
- Ensure
program.json exists in target directory
Version Mismatch
Error: Program name mismatch
Solutions:
- Ensure
program field in manifest matches actual program name
- Check imported program names match manifest declarations
Next Steps