Skip to main content

Error Codes

Every Nectar Network contract function returns a Result<T, ContractError>. When a call fails, the contract host aborts the invocation and surfaces the error as a numeric code. Tooling renders these differently depending on the layer:

  • stellar CLI / Horizon — reports Error(Contract, #N), where N is the numeric code from the tables below.
  • @stellar/stellar-sdk (frontend) — the simulation or send result carries the contract error; the numeric code is in the diagnostic events.
  • stellar/go SDK (keeper daemon) — the JSON-RPC simulateTransaction / sendTransaction response includes the host error with the contract code.

:::info Codes are per contract The numeric code is only unique within a single contract. Code 5 means Unauthorized in both KeeperRegistry and NectarVault, but the same number means something different in the LiquidationLab test harness. Always pair a code with the contract that produced it. :::

The enums are defined in source and are authoritative:

  • KeeperRegistry — contracts/keeper-registry/src/types.rs (enum Error)
  • NectarVault — contracts/nectar-vault/src/types.rs (enum VaultError)
  • LiquidationLab — contracts/liquidation-lab/src/types.rs (enum LabError)

Contract addresses referenced below (testnet, Tranche 1 hardened — current):

ContractAddress
KeeperRegistryCDT257SL2IYDZJIDXEVKI67MYLCKE73JY6WGUTGZOEFXJHG26FJHJDRB
NectarVaultCDZR6VDCPQFOFFKKZ2KMVB67Z54LI5OY73NHBFVI6DR6RE6TL7NN7345

:::note 7-decimal amounts All USDC amounts in error conditions are denominated in stroops with 7-decimal precision: 1 USDC = 10_000_000 stroops. On testnet, USDC is a mock Stellar Asset Contract (CD34YC6FFI2KIE2U4ZPCGQIRPH7UPG5YY2QBYNP25ATSFOQSG73J4VBW); mainnet will use Circle USDC (Tranche 3). :::


KeeperRegistry

Defined as enum Error in contracts/keeper-registry/src/types.rs:

#[contracterror]
#[derive(Clone, Debug, PartialEq)]
pub enum Error {
AlreadyInit = 1,
NotInit = 2,
AlreadyRegistered = 3,
NotRegistered = 4,
Unauthorized = 5,
Paused = 6,
InsufficientStake = 7,
ActiveDraw = 8,
SlashTimeout = 9,
}
CodeVariantRaised byTriggerResolution
1AlreadyInitinitializeThe registry already has an admin stored — initialize was called a second time.One-time setup only. Use set_config to change parameters; you cannot re-initialize a live contract.
2NotInitregister, deregister, slash, get_config, require_admin, require_vaultAdmin / config / vault address has not been stored yet — the contract was never initialized.Run initialize(admin, config, vault) before any other call. Confirm you are targeting the current deployment, not a deprecated address.
3AlreadyRegisteredregisterA KeeperInfo record already exists for this operator address.The keeper is already registered. To change details, deregister first (refunds stake), then register again.
4NotRegisteredderegister, get_keeper, avg_response_time_ms, mark_draw, clear_draw, record_execution, slashNo KeeperInfo exists for the supplied operator address.Register the operator with register(operator, name) first. If the keeper was slashed or deregistered, re-register before drawing capital.
5Unauthorizedpause, unpause, set_config (via require_admin); mark_draw, clear_draw, record_execution (via require_vault)Caller is not the stored admin (for admin-only fns) or not the stored vault address (for vault-only fns).Admin functions must be signed by the admin key (GATK27P6LOQBSXMVCYBBSKPUYKX5HVZ5AI4AAKF7UEYNKELSEBH53P7W on testnet). mark_draw / clear_draw / record_execution are called only by NectarVault — keepers must not call them directly.
6PausedregisterThe registry is paused (admin called pause). New registrations are blocked.Wait for the admin to unpause. Existing keepers can still draw and return; only new registration is gated.
7InsufficientStakeregisterThe configured min_stake is <= 0, so no valid stake can be pulled.An admin must set a positive min_stake via set_config. Testnet default is 100 USDC (1_000_000_000 stroops). Note: if the operator lacks the stake balance/allowance, the failure surfaces from the USDC token transfer, not this code.
8ActiveDrawderegisterThe keeper has an outstanding draw (has_active_draw == true) and cannot exit while capital is borrowed.Call NectarVault.return_proceeds to repay the draw (which clears the flag via clear_draw), then retry deregister. If the keeper has timed out, the admin can slash first.
9SlashTimeoutslashEither the keeper has no active draw, or now - last_draw_time <= slash_timeout (the grace window has not elapsed).Slashing is only valid once a keeper holds an active draw and the timeout has passed. Testnet slash_timeout is 3600 s (1 h). Wait for the window to elapse, then retry.

:::tip Stake math The slash amount is stake * slash_rate_bps / 10_000. With the testnet slash_rate_bps = 1000 (10%), a 100 USDC stake yields a 10 USDC slash that is transferred to the vault. slash returns the slashed amount and clears the active draw flag. :::


NectarVault

Defined as enum VaultError in contracts/nectar-vault/src/types.rs:

#[contracterror]
#[derive(Clone, Debug, PartialEq)]
pub enum VaultError {
AlreadyInit = 1,
NotInit = 2,
InsufficientBalance = 3,
InsufficientVault = 4,
Unauthorized = 5,
NoShares = 6,
DepositCapExceeded = 8,
WithdrawalCooldown = 9,
DrawLimitExceeded = 10,
}

:::warning Code 7 is intentionally unused VaultError skips 7 — there is no variant with that code. A returned Error(Contract, #7) from the vault address indicates a version mismatch (wrong contract, or a future build), not a known condition. Verify you are calling the current deployment. :::

CodeVariantRaised byTriggerResolution
1AlreadyInitinitializeThe vault already has an admin stored.One-time setup only. Use set_config to adjust parameters on a live vault.
2NotInitdeposit, withdraw, draw, return_proceeds, get_state, get_config, set_config, and the internal registry helpersThe vault has not been initialized (no admin / state / config / USDC / registry stored).Run initialize(admin, usdc_token, registry, config) first. Confirm the target address is the current vault, not a deprecated deployment.
3InsufficientBalancewithdrawThe requested shares exceed the depositor's own share balance.Query your shares with balance(user) or get_depositor(user) and withdraw <= that amount.
4InsufficientVaultwithdraw, drawwithdraw: total_shares == 0 (no liquidity to redeem). draw: requested amount exceeds available capital (total_usdc - active_liq).For withdraw, wait until the pool holds shares. For draw, request <= the available (un-borrowed) balance; check get_state for total_usdc and active_liq.
5Unauthorizedset_configCaller is not the stored admin.Sign set_config with the admin key. Depositor and keeper flows never need admin auth.
6NoShareswithdraw, get_depositorNo Depositor record exists for this address (never deposited, or already fully withdrawn).Make a deposit first, or query a depositor address that has an active position.
8DepositCapExceededdepositdeposit_cap > 0 and total_usdc + amount would exceed the cap.Deposit a smaller amount, or wait until withdrawals free up headroom. Testnet deposit_cap is 10_000_000 USDC. A cap of 0 disables the check.
9WithdrawalCooldownwithdrawnow - last_deposit_time < withdraw_cooldown — the cooldown since the last deposit has not elapsed.Wait out the cooldown, then retry. Testnet withdraw_cooldown is 3600 s (1 h). Note any new deposit resets last_deposit_time and restarts the cooldown.
10DrawLimitExceededdrawmax_draw_per_keeper > 0 and the requested amount exceeds the per-keeper cap.Draw <= the cap per call, or split a large liquidation across multiple draws. Testnet max_draw_per_keeper is 10_000 USDC. A cap of 0 disables the check.

:::info Cross-contract failures during draw draw verifies the keeper against the registry (get_keeper) and then calls mark_draw on the registry. return_proceeds calls clear_draw and record_execution. If the registry rejects one of these — for example because the keeper was deregistered or the registry address is stale — the failure propagates from the registry as a KeeperRegistry error (commonly NotRegistered / Unauthorized), aborting the whole vault invocation atomically. :::


LiquidationLab (test harness)

LiquidationLab is a local mock of a Blend pool used to simulate reserves, positions, and Dutch auctions when exercising the keeper end to end. It is not part of the deployed protocol — the production keeper monitors the real Blend pool (CCEBVDYM32YNYCVNRXQKDFFPISJJCV557CDZEIRBEE4NCV4KHPQ44HGF on testnet). Its errors appear only in local development and integration tests.

Defined as enum LabError in contracts/liquidation-lab/src/types.rs:

#[contracterror]
#[derive(Clone, Debug, PartialEq)]
pub enum LabError {
AlreadyInit = 1,
NotInit = 2,
Unauthorized = 3,
AuctionNotFound = 4,
AuctionExists = 5,
ReserveNotFound = 6,
PositionNotFound = 7,
}
CodeVariantTriggerResolution
1AlreadyInitThe lab already has an admin stored.Initialize once per deployment.
2NotInitThe lab was never initialized.Call initialize(admin) before seeding reserves, positions, or auctions.
3UnauthorizedA privileged (admin-only) function was called by a non-admin.Sign admin-only calls with the lab's admin key.
4AuctionNotFoundAn auction lookup / fill targeted a user with no active auction.Create the auction first, or target a user that currently has one.
5AuctionExistsAttempted to create an auction for a user that already has one.Clear or fill the existing auction before creating a new one.
6ReserveNotFoundA reserve operation referenced an unregistered asset.Register the reserve (with index, c_factor, l_factor, b_rate, d_rate) first.
7PositionNotFoundA position lookup targeted a user with no recorded UserPositions.Seed the user's collateral / liabilities before querying or auctioning the position.

:::note Not numerically aligned with the protocol contracts LiquidationLab reuses low codes (Unauthorized = 3, not 5). Never map a lab code onto KeeperRegistry/NectarVault semantics — interpret it against the source address that produced it. :::


Handling errors in the keeper

The Go keeper reads contract errors from the JSON-RPC simulation/send response. Match on the contract address and the numeric code, then decide whether to retry, back off, or surface the failure:

// Pseudocode: branch on a NectarVault contract error code.
switch vaultErrCode {
case 4: // InsufficientVault — not enough free capital to draw
slog.Warn("draw blocked: insufficient vault liquidity", "want", amount)
return errSkipAuction // wait for the next cycle
case 9: // WithdrawalCooldown — depositor path, not keeper
return errCooldown
case 10: // DrawLimitExceeded — split the draw
slog.Warn("draw exceeds per-keeper cap", "cap", maxDrawPerKeeper)
return errSplitDraw
default:
return fmt.Errorf("vault error code %d", vaultErrCode)
}

:::tip Transient vs. terminal Treat InsufficientVault (code 4) as transient — retry on the next polling cycle once capital frees up. Treat Unauthorized (code 5) and NotRegistered (KeeperRegistry code 4) as terminal for the keeper's identity — they will not resolve by retrying; fix the keeper's registration or signing key. Pair this with the keeper's exponential-backoff retry policy so transient conditions self-heal without hammering the RPC. :::