Aleo for Agents is offline at present

skill·2026-08-03·12 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

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: generated during leo build, cached under build/. 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; testnet is already scheduled for V20.

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/: main.aleo with the compiled Aleo instructions, an ABI describing input and output types per function, and the proving and 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                  # generate keys without deploying
leo abi                         # generate the ABI from bytecode

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 --blocks 10

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. 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.

10. 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

11. 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.

13. 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