Each NCN relies on off-chain agents called keepers. Keepers are essentially permissionless automation agents that execute all necessary on-chain instructions to advance (“crank”) the NCN through its epoch phases. Anyone can run a keeper. There are no special authorities required to keep the NCN operational. By monitoring network state and calling the NCN program’s instructions at certain times, keepers make sure the NCN progresses correctly and remains in sync with Solana’s epoch.
This guide provides an overview of how to use the ncn-program-cli
, a command-line interface for interacting with an NCN program using the NCN template. Below, we cover installation, configuration, and step-by-step usage of the CLI, from initial setup through running the NCN keeper to automate state management.
Before using the Template NCN Program CLI, ensure you have it installed and configured properly, along with the related Jito (Re)Staking tools:
Build and install the NCN Program CLI: If you have the NCN program template repo, compile and install the CLI binary. For example, using Cargo:
# Clone the template repo
git clone git@github.com:jito-foundation/ncn-template.git
cd ncn-template
# Build the CLI from the repository (assuming you're in the repo directory)
cargo build --release
# Install the CLI binary
cargo install --path ./cli --bin ncn-program-cli --locked
After installation, verify it works by running:
ncn-program-cli --help
This should display the general help and list available subcommands.
Install Jito (Re)Staking CLI (if not already): The NCN program operates alongside Jito’s restaking program. You may need the Jito (Re)Staking CLI (jito-restaking-cli
) to manage restaking registry tasks (like registering NCNs, operators, and vaults). Install it using Cargo:
cargo install jito-restaking-cli
Confirm it is installed:
jito-restaking-cli --help
Configure Environment Variables: The ncn-program-cli
accepts configuration through command-line options or environment variables. Optionally, to avoid passing flags every time, you can use a .env
file for convenience:
# NCN Operator & Program CLI Environment Configuration
# Copy this file to `.env` and update the values below
# --------------- REQUIRED --------------------
# Solana cluster (mainnet, devnet, testnet, or localnet)
CLUSTER=devnet
# RPC endpoint for your Solana cluster (must support getBlock and transaction history)
RPC_URL=https://api.devnet.solana.com
# Commitment level for RPC operations (e.g. confirmed or finalized)
COMMITMENT=confirmed
# On-chain NCN instance address (created by the NCN admin)
NCN=<Your_NCN_account_address>
# Path to your Solana keypair file (must have admin/operator authority)
KEYPAIR_PATH=~/.config/solana/id.json
# Operator public key (the account responsible for voting)
OPERATOR=BSia35bXHZx69XzCQeMUnWqZJsUwJURVvuUg8Jup2BcP
# OpenWeather API key (used by the example weather oracle operator)
OPENWEATHER_API_KEY=your_api_key_here
# --------------- PROGRAM IDS --------------------
# Use these only if you are deploying custom programs
# Otherwise, leave them blank to use defaults
# NCN Program ID (default: 7rNw1g2ZUCdTrCyVGZwCJLnbp3ssTRK5mdkH8gm9AKE8)
NCN_PROGRAM_ID=
# Jito Restaking program (default value)
RESTAKING_PROGRAM_ID=RestkWeAVL8fRGgzhfeoqFhsqKRchg6aa1XrcH96z4Q
# Jito Vault program (default value)
VAULT_PROGRAM_ID=Vau1t6sLNxnzB7ZDsef8TLbPLfyZMYXH8WTNqUdm9g8
# --------------- LOGGING --------------------
# Set the Rust log level (e.g., info, debug)
RUST_LOG=info
These variables will be picked up by the CLI, or you can supply equivalent --rpc-url
, --ncn-program-id
, --ncn
, --keypair-path
, etc., flags to each command.
Before running the keeper, some setup and initialization steps are required to configure the NCN program and connect it. Below is a typical workflow for initializing a new NCN:
ncn-program-cli admin-fund-account-payer --amount-in-sol 10
This example funds the account payer with 10 SOL.
ncn_admin
:ncn-program-cli admin-create-config --tie-breaker-admin <ADMIN_ADDRESS>
This creates the NCN’s config account and sets an admin to resolve tied votes or set consensus manually, if needed. You can also override default consensus parameters with options like --epochs-before-stall
, --valid-slots-after-consensus
, etc., but in most cases defaults are fine. Run with --help
to see all available options.
ncn-program-cli create-vault-registry
This sets up an empty VaultRegistry account.
ncn-program-cli admin-register-st-mint --vault <VAULT_MINT_ADDRESS> --weight <WEIGHT_VALUE> --keypair-path <NCN_ADMIN_KEYPAIR>
For example, if you want to include a vault with mint ABC...
at weight 100, you’d put that address and weight. This call authorizes that vault for the NCN. Please note that the vault must have already been approved on the restaking program side via a handshake with this NCN.
The keeper
command automates key tasks for each epoch, including creating epoch state accounts, performing stake snapshots, and handling the voting process. It runs continuously while monitoring the blockchain and executing actions based on the current epoch phase.
To start the keeper, run:
ncn-program-cli keeper
By default, the keeper checks for actions every 10 minutes, retries on errors after 10 seconds, targets the testnet
cluster and reports metrics using the solana_metrics
crate with the local
region label.
Let’s break down the keeper’s workflow step by step.
After registering the stake mints, you need to create entries in the Vault Registry for any vaults that have opted into the NCN. This is a permissionless crank operation: ncn-program-cli crank-register-vaults
.
crank_register_vaults
is a function that registers any unregistered vaults that have been approved by the NCN but not added to the registry yet. It will:
Once all eligible vaults are registered, the keeper continues its loop by checking and updating the current epoch state.
Next, the keeper then reads the current epoch from the Solana cluster using state.fetch(handler, current_keeper_epoch).await
and fetches the corresponding EpochState
account from the NCN program. If the account already exists, it loads it into local memory.
If the epoch has already been marked as complete, the keeper exits the loop early and waits for the next epoch.
The update_epoch_state
method ensures the keeper’s in-memory state reflects the latest on-chain data for the current epoch. It performs the following actions:
get_is_epoch_completed
. If so, it flags the local state and exits earlyEpochState
accountEpochState
struct.update_current_state
.This function acts as the gatekeeper. If the epoch is already finished, the keeper skips further processing for that loop iteration.
At this point in the loop, the keeper enters its core state machine execution phase, where it actively drives the NCN epoch forward based on its current on-chain state.
The NCN program defines a set of epoch phases. Each phase requires actions to be executed before the epoch can progress. The keeper reads the current EpochState
, determines the phase and runs the appropriate handler.
The epoch lifecycle states are:
SetWeight
→ Establishes voting weight structure for the epochSnapshot
→ Captures stake distribution across operatorsVote
→ This is skipped by the NCN keeperPostVoteCooldown
→ Manages post-consensus waiting periodDistribute
→ Distributes rewards to participants based on their contributionsClose
→ Cleans up completed epoch accountsEach state represents a distinct phase in the epoch lifecycle and the keeper automatically transitions between states as on-chain conditions are met. These operations are permissionless meaning any keeper can execute them when the appropriate conditions are satisfied. It is important to note that this is an example of an NCN’s lifecycle. NCNs may have different states to crank through.
Let's examine each state handler, starting with the weight setup phase:
SetWeight
The SetWeight state is the first operational phase of each epoch, responsible for establishing the voting power structure that will be used during consensus. This phase uses the function crank_set_weight
to set up the foundation for stake-weighted voting by creating and populating the weight table.
This function performs two steps:
create_weight_table
– Initializes and sizes the WeightTable
accountset_epoch_weights
– Calculates and stores each vault’s voting weightWeightTable
accountOnce voting weights are set, the epoch transitions to the Snapshot state, where the current stake distribution across all registered operators is captured.
Snapshot
The Snapshot phase records the current stake distribution across all vault-operator pairs for the epoch. This step guarantees a fixed, on-chain snapshot of delegated stake that will be used in the upcoming consensus vote.
The crank_snapshot
function performs several steps:
VaultRegistry
EpochSnapshot
has already been finalized, the function exits early and moves on the next stateOperatorSnapshot
exists for the current epochfull_vault_update()
to update the vault’s state and stake balancessnapshot_vault_operator_delegation()
to record how much stake the vault has delegated to this operatorThis snapshot process creates a record of how much stake is delegated from each vault to each operator. It ensures that consensus voting in the next phase is based on accurate stake amounts.
Vote
This is skipped by the NCN while waiting for the operator to vote.
PostVoteCooldown
The PostVoteCooldown state serves as a buffer between finalizing consensus and performing cleanup. It gives the network time to settle and provides visibility into the outcome of the voting phase.
The crank_post_vote_cooldown
function performs two simple but important steps:
ConsensusResult
account for the epoch from the chain.This phase does not submit any transactions or mutate state. It simply confirms that consensus has been reached and prepares the system for the final cleanup phase.
Once completed, the epoch transitions to the Close state, where all temporary accounts are cleaned up.
Distribute
The Distribute state allocates rewards to operators and vaults based on their contributions during the epoch.
The crank_distribute
function performs the following steps:
distribute_ncn_rewards
to allocate base rewards tied to consensus participation.distribute_jito_rewards
to distribute incentives.route_ncn_rewards
.distribute_ncn_operator_rewards
.distribute_ncn_vault_rewards
.All reward distribution and routing steps are logged. Errors are non-blocking and distribution will be retried in future keeper loops if any step fails.
Once completed, the epoch moves to the Close
state, where the temporary accounts are cleaned up.
Close
The Close state marks the end of an NCN’s epoch lifecycle. During this phase, the keeper performs a full cleanup by closing all temporary accounts created during the epoch. This will reclaim rent, free up state, and prepare the NCN for the next epoch.
The crank_close_epoch_accounts
function performs the following operations:
BallotBox
account that tracked consensus votingOperatorSnapshot
accountEpochSnapshot
that captured the operator-vault stake mappingWeightTable
account that stored epoch voting weightsEpochState
account that tracked progress through the state machineEach closure is attempted independently and any errors are logged. Failures do not block anything. ****The keeper will simply attempt to retry them in subsequent loops.
At the end of each loop, the keeper:
loop_timeout_ms
durationThis ensures the keeper remains responsive during stalled epochs while continuously reporting liveness for monitoring and reward tracking.