Aleo staking and delegation
1. Overview
Staking, delegation, and credit transfers all run through the credits.aleo system program. This skill is the function reference, the mechanics, and the operational workflows.
Version and canonical syntax
Target: Leo compiler >= 4.4.0, credits.aleo as deployed on mainnet.
CLI examples use leo execute credits.aleo <function> ... --broadcast. Amounts are microcredits expressed as u64 literals.
Every parameter in section 3 was read out of the deployed program rather than from documentation. To check them yourself:
curl -s https://api.explorer.provable.com/v1/mainnet/program/credits.aleo
2. Key concepts
- Validator: a node in consensus. Self-bonds at least 100 credits and needs 10,000,000 total credits bonded to join the committee.
- Delegator: bonds credits to a validator. Minimum 10,000 credits.
- Committee: the validators actively participating in consensus.
- Commission: the share of rewards the validator keeps. Set on the first
bond_validatorcall and immutable afterwards. - Withdrawal address: the address authorized to unbond and withdraw. Can differ from the staking address, and this is the key that actually controls the funds.
- Unbonding period: 360 blocks between requesting an unbond and claiming.
- credits.aleo: the system program behind all of it.
3. Staking parameters
| Parameter | Value | Microcredits |
|---|---|---|
| Minimum validator self-bond | 100 credits | 100_000_000u64 |
| Minimum total bonded to join the committee | 10,000,000 credits | 10_000_000_000_000u64 |
| Minimum delegation per delegator | 10,000 credits | 10_000_000_000u64 |
| Maximum delegators per validator | 100,000 | 100_000u32 |
| Unbonding period | 360 blocks | 360u32 |
| Commission rate | set once, immutable | u8 percentage |
One credit is 1,000,000 microcredits. Every amount you pass is microcredits, so a delegation of 10,000 credits is 10000000000u64. Getting this wrong by a factor of a million is the most common staking mistake, and the transaction fails rather than under-delegating, which is at least a fast way to find out.
4. credits.aleo functions
Transfers
| Function | Description |
|---|---|
transfer_public | Public to public, charged to the caller |
transfer_public_as_signer | Public to public, charged to the signer rather than the calling program |
transfer_private | Record to record |
transfer_public_to_private | Shielding: public balance becomes a record |
transfer_private_to_public | Unshielding: record becomes a public balance |
join | Combine two credit records |
split | Split one credit record into two |
transfer_public_as_signer is the one to reach for when your program calls credits.aleo on a user's behalf. Plain transfer_public debits the caller, which in a composed call is your program, not the user who initiated the transaction.
leo execute credits.aleo transfer_public "aleo1receiver..." "1000000u64" --broadcast
leo execute credits.aleo transfer_private <record> "aleo1receiver..." "1000000u64" --broadcast
leo execute credits.aleo split <record> "500000u64" --broadcast
Fees
| Function | Description |
|---|---|
fee_public | Pay the transaction fee from the public balance |
fee_private | Pay the fee from a private credit record |
fee_private keeps the fee source out of public view, which matters: a private transfer paid for by a publicly identifiable fee is only half private.
Staking
| Function | Description |
|---|---|
bond_validator | Validator self-bond, minimum 100 credits, sets commission |
bond_public | Delegate to a validator, minimum 10,000 credits |
unbond_public | Request unbonding, starting the 360-block timer |
claim_unbond_public | Claim after the unbonding period |
set_validator_state | Open or close the validator to new delegations |
upgrade | Program upgrade path, gated on a 4,000,000 credit threshold |
5. Delegation workflow
# 1. Delegate 10,000 credits
leo execute credits.aleo bond_public \
"aleo1validator_address..." \
"aleo1your_withdrawal_address..." \
"10000000000u64" \
--broadcast
# 2. Confirm the bond landed
leo query program credits.aleo --mapping-value bonded "aleo1your_address..."
# 3. Request unbonding, which starts the 360-block timer
leo execute credits.aleo unbond_public "10000000000u64" --broadcast
# 4. Watch for the unlock height
leo query program credits.aleo --mapping-value unbonding "aleo1your_address..."
leo query block --latest
# 5. Claim once the height has passed
leo execute credits.aleo claim_unbond_public --broadcast
The unbonding mapping stores an unbond_state of microcredits and height. Compare that height against the current block height rather than counting elapsed time; block production varies and a claim submitted early is a wasted fee.
One behaviour worth knowing: unbonding an amount that would leave you below the 10,000 credit minimum unbonds your whole position instead. Partial unbonds are only partial while the remainder stays above the floor.
6. Validator setup
# 1. Self-bond with a commission rate. The rate is permanent.
leo execute credits.aleo bond_validator \
"aleo1your_withdrawal_address..." \
"100000000u64" \
10u8 \
--broadcast
# 2. Open to delegations
leo execute credits.aleo set_validator_state true --broadcast
# 3. Check committee membership
leo query program credits.aleo --mapping-value committee "aleo1your_address..."
10u8 is a 10% commission and cannot be changed later. Self-bonding gets you a validator record; joining the committee needs 10,000,000 credits bonded in total, which means attracting delegation.
7. Monitoring mappings
| Mapping | Key | Value | Description |
|---|---|---|---|
committee | validator address | committee_state (is_open, commission) | Active validators |
delegated | validator address | u64 | Total credits bonded to the validator |
bonded | staker address | bond_state (validator, microcredits) | Individual bonds |
unbonding | staker address | unbond_state (microcredits, height) | Pending unbonds and their unlock height |
withdraw | staker address | address | Withdrawal address |
metadata | validator address | u32 | Validator delegator count |
account | address | u64 | Public credit balances |
pool | address | u64 | Reward pool balances |
leo query program credits.aleo --mapping-value bonded "aleo1staker..."
leo query program credits.aleo --mapping-value delegated "aleo1validator..."
These are ordinary public mappings, so the REST API works just as well and costs nothing:
curl -s "https://api.explorer.provable.com/v1/mainnet/program/credits.aleo/mapping/bonded/aleo1staker..."
8. Common errors
| Error | Cause | Fix |
|---|---|---|
| Bond rejected as too small | below 10,000 credits | Bond at least 10000000000u64 |
| Amount off by a factor of a million | credits passed where microcredits were expected | 1 credit is 1,000,000 microcredits |
| Claim rejected | fewer than 360 blocks since the unbond | Compare unbonding.height with the current block height |
| Cannot unbond | the withdrawal address does not match | Unbond from the address recorded in withdraw |
| Partial unbond took everything | the remainder fell below the 10,000 credit minimum | Expected behaviour; unbond down to the floor or all of it |
| Commission change rejected | commission is immutable | It is fixed at the first bond_validator call |
| Delegation to a validator rejected | the validator is closed | Check committee.is_open |
| Your program is charged instead of the user | used transfer_public in a composed call | Use transfer_public_as_signer |
9. Security notes
Staking is entirely public. Delegation amounts, validator choices, and reward flows are all readable by anyone.
The withdrawal address controls the funds. It is the key to protect, and it can and often should be a different key from the one that signs the bond.
The commission rate is permanent from the first bond_validator call. There is no correction path.
Validator performance affects rewards, so monitor uptime rather than assuming a bond is a set-and-forget position.
10. Performance notes
Poll on a schedule rather than per block. Bonded state changes on the order of epochs, not seconds.
Cache validator metadata and refresh committee and delegated only when a new block arrives.
Make automation idempotent. A retried staking transaction that was already accepted is a duplicate bond, not a no-op.
Track unbond unlock heights explicitly so you never submit a claim_unbond_public that is going to be rejected and charged for.
Cross-program calls into credits.aleo: aleo_smart_contracts. Backend automation: aleo_backend. Dashboards: aleo_frontend. Working code: aleo_cookbook.
12. Agent staking workflow
- Normalize units first. Convert user-facing credits to
u64microcredits before building any command. - Validate prerequisites: the validator is open, and the withdrawal address is the one you control.
- Execute one staking action at a time and record each transaction ID.
- Query the relevant mapping after each step to confirm the state transition landed.
- Check the current block height against
unbonding.heightbefore attempting a claim. - On failure, match the error to section 8 and fix the cause before retrying.