Aleo for Agents is offline at present

skill·2026-08-03·14 min read

Aleo Program Deployment and Operations

Aleo program deployment and operations

1. Overview

The full lifecycle of getting a Leo program onto the Aleo network: project setup, building, local networks, deployment, execution, upgrades, fees, and dependencies.

Version and canonical syntax

Target: Leo compiler >= 4.4.0. Consensus versions activate per network at a block height, so the two chains are rarely on the same rule set:

NetworksnarkOS releaseConsensus versionActivation height
Mainnet4.9.0V1820,794,000
Mainnet4.9.1V1921,342,000
Testnet4.10.0V2019,374,000
Mainnet4.10.0V2022,175,000

snarkOS v4.10.1 is a snarkOS-only point release built on snarkVM 4.10.0. It does not change the V20 activation height on either network.

Query the target network for its active consensus version before you deploy. Testnet runs ahead of mainnet, so a rule you rehearsed on one chain may not be in force on the other.

Entry points are fn, state changes go in final { } blocks, and every deployable program carries a constructor with exactly one policy annotation. See aleo_smart_contracts for the language.

When these commands and the published docs disagree, trust leo --help on your installed version. The CLI has changed shape several times through the 4.x series.

2. Key concepts

  • leo run executes an entry point locally without generating a proof. It covers the proof half only; final blocks do not run.
  • leo execute executes locally, generates a proof, and builds a transaction. Add --broadcast to send it.
  • leo deploy compiles and broadcasts a deployment transaction. It costs credits.
  • leo upgrade deploys a new edition of an already-deployed program, if the constructor policy permits.
  • Program ID: the unique on-chain name, such as my_token.aleo. Once taken on a network, it is taken permanently.
  • Edition: a version counter on a deployed program. Starts at 0 and increments with each upgrade.
  • Microcredit: one credit is 1,000,000 microcredits. Fees are denominated in microcredits.
  • Proving and verifying keys: not a build artifact. leo synthesize <program>.aleo --save <directory> generates them and writes them to disk, and leo execute synthesizes whatever it needs while constructing a proof when suitable saved keys are neither supplied nor found. Proving keys are large.
  • Consensus version: the protocol rule set active at a given block height, and the heights are per network. V18 activated at mainnet height 20,794,000. V20 activates on testnet at 19,374,000 and on mainnet at 22,175,000.

3. Project setup

bash
leo new my_project
cd my_project
my_project/
├── build/              # compiled artifacts, generated
├── src/
│   └── main.leo
├── tests/
│   └── test_my_project.leo
├── .env                # gitignored by default
├── .gitignore
└── program.json

leo new generates a .gitignore that already excludes .env, build/, *.prover, and *.verifier. Leave it that way.

program.json

json
{
  "program": "my_project.aleo",
  "version": "0.1.0",
  "description": "My Aleo program",
  "license": "MIT",
  "leo": "4.4.0",
  "dependencies": null,
  "dev_dependencies": null
}

The leo field pins the compiler version. Leo 4.4 warns when it does not match the installed toolchain, which is worth heeding: a program compiled by a different minor version can produce different bytecode.

.env

bash
NETWORK=testnet
PRIVATE_KEY=APrivateKey1...
ENDPOINT=https://api.explorer.provable.com/v1

The public endpoint is /v1. Never commit a .env holding a real key; use a secrets manager or an HSM for anything that touches mainnet.

4. Build pipeline

bash
leo build

Output lands in build/{program}/: the compiled .aleo bytecode holding the Aleo instructions, and abi.json describing input and output types per function. Building does not produce proving or verifying keys.

leo build prints the compiled size against the 2000 KB ceiling, and since Leo 4.4 it lists each imported program separately. That breakdown is worth reading before you deploy: it is common for an application's own code to be modest while an imported registry or router dominates the footprint you are paying to store.

bash
leo run <fn> <inputs>                             # local, no proof, proof half only
leo execute <fn> <inputs>                         # local, with proof and transaction
leo synthesize <program>.aleo --save <directory>  # generate and save function proving and verifying keys
leo abi                                           # generate the ABI from bytecode

Synthesis is the only step that produces keys, and it is worth doing deliberately: if you do not pass saved keys to leo execute and it cannot find any, it synthesizes the ones it needs while building the proof, which pays that cost again on every run.

Every major command takes --json-output for structured results, which is the right interface for scripting and agent use:

bash
leo build --json-output
leo execute mint "aleo1..." 100u64 --json-output
leo deploy --json-output

Formatting is a plugin now

leo fmt ships as a separate plugin and is not part of the base install. Running it without the plugin gives:

Error [ECLI0377045]: 'leo-fmt' not found. Install the plugin and ensure it is available on your PATH.

Check what you have with leo plugins. A CI pipeline that assumes leo fmt --check works will fail on a clean install, so either install the plugin explicitly in CI or drop the formatting gate. The same applies to leo-lsp, leo-example, and leo-debug.

5. Local networks

Two options, and the lighter one is usually right.

leo devnode runs a single node. It starts in seconds and lets you advance the ledger on demand, which suits iterating on a deploy-execute-query loop:

bash
leo devnode start
leo devnode advance 10

NUM_BLOCKS is positional in the current CLI, so there is no --blocks flag. Run leo devnode advance with no number to advance one block.

leo devnet runs a multi-validator network when you need real consensus behaviour:

bash
leo devnet --num-validators 4 --num-clients 2 --network testnet
leo devnet --install                     # fetch snarkOS if you do not have it
leo devnet --snarkos /path/to/snarkos    # use a specific binary
leo devnet --consensus-heights 0         # pin activation heights for a custom devnet

--install will fetch snarkOS for you, and --snarkos-version pins which build. Useful extras: --tmux to lay the nodes out in panes, --storage to point at a state directory, and --rest-port and friends when the defaults collide with something already running.

Local key

Leo's own help publishes the devnode key:

bash
NETWORK=testnet
PRIVATE_KEY=APrivateKey1zkp8CZNn3yeCseEtxuVPbDCwSyhGW6yZKUYKfgXmcpoGPWH
ENDPOINT=http://localhost:3030

That key corresponds to aleo1rhgdu77hgyqd3xjj8ucu3jj9r2krwz6mnzyd80gncr5fxcwlh5rsvzp9px. It is published, so treat anything it signs as public. It must never appear in a configuration that can reach a live network.

6. Deployment

bash
leo deploy --broadcast                              # to the endpoint in .env
leo deploy --broadcast --devnet                     # against a local devnet
leo deploy --broadcast --priority-fees 1000000      # microcredits
leo deploy --broadcast --fee-records <record>        # pay privately from a record
leo deploy --broadcast --private-key APrivateKey1... # override .env
leo deploy --print                                  # build the transaction, print it, do not send
leo deploy --save ./tx                              # write the transaction to a directory

--print and --save without --broadcast are the safe way to inspect a deployment before it costs anything.

Cost model

Total = storage cost + synthesis cost + constructor cost + namespace cost

Storage cost scales with compiled byte size. Synthesis cost scales with the constraint count of each function. Constructor cost covers executing the constructor. Namespace cost depends on how short the program name is:

Name lengthApproximate namespace fee
10+ characters~1 credit
5 to 9 characters~10 credits
2 to 4 characters~1,000 credits
1 character~10,000,000,000 credits

Long names are cheap. This is deliberate: short names are a scarce resource and priced like one.

Deployment limits under V18 and V19

Consensus V18 added protocol-level deployment-density checks. A deployment carries program bytecode and verification material into consensus state, which makes it an unusually expensive object, and fees alone turned out to be a weak bound on how concentrated that activity can get. Under V18 the answer moved: a large application split across many programs could compile cleanly and still be rejected when its components were deployed close together.

V19 replaced that density policy with a fixed cap per deployment transaction: 2^22 variables and 2^22 constraints, or 4,194,304 of each. The ceiling no longer depends on what anyone else deployed two blocks earlier, so you can synthesize a release candidate, compare its counts against 4,194,304, and gate on that number in CI. Leo 4.4.2 reports the V19 limits and counts constant variables in the variable total; an older toolchain undercounts and will tell you that you have headroom you do not have.

Leo reports no per-transaction limit for V18, because V18 did not use this model. Pin --consensus-version to the version the target chain will actually be running rather than assuming the newest limit applies retroactively.

A local devnode that mines each deployment immediately hides all of this. Rehearse releases with realistic block production and the consensus version you expect on the target network, which leo deploy --consensus-version lets you pin.

V20 on testnet

Testnet snarkOS 4.10.0 activates ConsensusVersion::V20 at height 19,374,000. Two changes matter when you rehearse there. Testnet MAX_CERTIFICATES becomes 40, which puts the committee model closer to production. And leader-produced blocks now carry a spend_limit derived from a conservative certificate floor:

min_certificates = 2 * ((MAX_CERTIFICATES + 2) / 3)

With 40 certificates that gives 28, so a leader-produced block is budgeted as if only 28 certificates took part. A transaction that fits comfortably in an ordinary block can still miss inclusion in a leader-produced one.

None of this is live on mainnet. Query the active consensus version on the network you are deploying to instead of assuming testnet and mainnet move together.

Verify

bash
leo query program my_project.aleo
leo query program my_project.aleo --mappings
leo query program my_project.aleo --mapping-value account "aleo1..."
leo query program my_project.aleo --edition 0

7. Execution

bash
leo execute mint "aleo1..." 100u64 --broadcast
leo execute mint "aleo1..." 100u64 --broadcast --devnet
leo query transaction <transaction_id>
leo execute → proof generated → transaction broadcast → pending
  → accepted, included in a block, final block runs on-chain
  → or rejected: invalid proof, insufficient fee, or a failed final block

A rejected transaction still costs the base fee. That is the reason get_or_use exists.

Other useful queries:

bash
leo query block --latest
leo query mempool
leo query committee
leo query stateroot

8. Upgrades

The constructor's annotation decides whether an upgrade is possible at all, and it is fixed at deployment.

bash
leo query program my_project.aleo    # read the current edition
leo upgrade --broadcast

An upgrade can change function logic and add new functions, records, structs, mappings, and storage.

An upgrade cannot change the program ID, existing function signatures, existing struct or record definitions, or existing mapping names. The constructor and its policy annotation cannot be modified or removed once deployed.

That last constraint is the one to think about before your first deployment rather than after. @noupgrade is a promise to your callers that the code they audited is the code that runs. @admin is a promise that one key can change it. Both are visible on-chain and neither can be revised later.

9. Standalone Aleo bytecode workflows

The global --path option accepts a compiled .aleo file, not only a package directory. leo run, leo execute, leo deploy, and leo upgrade all operate on that artifact, so a build stage can compile once and hand the bytecode to test, proof, and deployment stages that never see the source.

bash
leo --path ./release/app.aleo run <function> <inputs>
leo --path ./release/app.aleo execute <function> <inputs> --broadcast
leo --path ./release/app.aleo deploy --imports-dir ./release/imports --broadcast
leo --path ./release/app_v2.aleo upgrade --imports-dir ./release/imports --broadcast

Imports resolve from the selected network by default. A .aleo file left beside the artifact does not quietly shadow a deployed program. --imports-dir <DIR> is the explicit opt-in to local bytecode imports, and it accepts the same flat directory and per-program build layouts that leo abi already understands. Dependency ordering is deterministic, so the same artifact and the same import set behave identically no matter how the filesystem enumerates the directory. Where it is supported, --no-local forces network-only resolution and excludes local overrides.

A standalone artifact has no manifest and no workspace behind it, so package filters and package-only import options are rejected for standalone upgrades rather than silently ignored. Pass only the options that mean something for a single file.

Release manifest

Bytecode is not hermetic by itself: network-resolved imports can change as their programs are upgraded. Write a release manifest beside the artifact recording the source commit, the Leo version that produced it, the target network, the program edition you expect, the digest of the main artifact, and the digest of every import. An operator who receives bytecode and nothing else can then prove what they are about to deploy.

10. Dependencies

bash
leo add credits.aleo --network
leo add my_lib.aleo --local ../my_lib
leo remove my_lib.aleo

Dependencies must be deployed before the programs that import them:

bash
cd my_lib && leo deploy --broadcast
cd ../my_app && leo deploy --broadcast

Read the per-import size lines in leo build output before deploying a program with dependencies. An import you added for one helper function may be carrying its entire compiled body into your deployment cost.

Verify the program ID of every dependency you add from the network. credits.aleo is the native credits program; a lookalike name is a supply-chain attack with a very small diff.

11. Common deployment errors

ErrorCauseFix
"program already exists"the program ID is taken on that networkPick a different name
"insufficient base fee"not enough creditsAdd credits, or reduce program size
"constructor failure"the constructor rejected the deployment or upgradeCheck that you satisfy the policy, for example that you are the @admin address
"missing dependency"an imported program is not on the networkDeploy the dependency first
"consensus version mismatch"the program uses features not yet activeCheck the network's consensus version and pin --consensus-version
"invalid private key"wrong format or missing keyCheck PRIVATE_KEY in .env
ECLI0377045 "'leo-fmt' not found"the plugin is not installedInstall the plugin or drop the formatting step
Deployment rejected near an activation heightthe per-transaction constraint cap or a version boundaryCheck the counts against the active version's limit and retry after the boundary

12. Pre-deployment checklist

  1. leo build succeeds and the size report looks reasonable, including imports.
  2. leo test passes.
  3. leo test --prove passes, which catches constraint problems the fast path hides.
  4. Sample inputs validated: leo run for pure entry points, leo execute for anything with a final block.
  5. The constructor policy is the one you want to live with permanently.
  6. The program name is available on the target network and long enough to avoid a namespace surprise.
  7. Dependencies are deployed and their program IDs are verified.
  8. Fee budget estimated from the build size report.
  9. The private key is in a secrets manager, not a file.
  10. .env points at the intended network, and the devnode key is nowhere near it.

Write programs with aleo_smart_contracts. Test with aleo_testing. Integrate with aleo_frontend and aleo_backend. Start from working code in aleo_cookbook.

14. Agent deployment workflow

  1. Write the program following aleo_smart_contracts.
  2. leo build, and read the size report.
  3. leo test, then leo test --prove.
  4. leo run <fn> <inputs> for entry points with no final block.
  5. leo devnode start.
  6. leo deploy --broadcast --devnet.
  7. leo execute <fn> <inputs> --broadcast --devnet for anything that finalizes.
  8. leo query program <name> --mapping-value <map> <key> to confirm the state change landed.
  9. leo upgrade --broadcast --devnet if you are rehearsing an upgrade.
  10. Point .env at testnet and repeat before going near mainnet.
  11. Use leo deploy --print first on any network where a mistake costs real credits.

Sources