> ## Documentation Index
> Fetch the complete documentation index at: https://docs.exponent.finance/llms.txt
> Use this file to discover all available pages before exploring further.

# VaultAssertion

> Pre-flight checks on Strategy Vault and protocol state, returning an allow/warn/block decision

`VaultAssertion` is a standalone assertion runner. It observes Strategy Vault, protocol, and caller-supplied state and returns a structured report you can use to decide whether to build, send, or skip a transaction.

It is complementary to [VaultTransactionBuilder](/developer-strategy-vaults/vault-transaction-builder) — caller-controlled, and entirely outside the transaction pipeline.

<Note>
  `VaultAssertion` does not build, mutate, or send transactions. It does not run a monitoring loop. It does not replace Squads policies or onchain validation hooks. It also does not require any transaction-builder input — you decide what to do with the report.
</Note>

## Quick Start

The two most actionable conditions for most strategy flows are `ClmmPrice` (live CLMM price/APY bounds) and `KaminoLtv` (Kamino obligation LTV bounds). The example below runs both in a single check and uses the result to gate the rest of the flow.

```typescript theme={null}
import {
  VaultAssertion,
  VaultAssertionConditionKind,
} from "@exponent-labs/exponent-sdk";
import { Connection, PublicKey } from "@solana/web3.js";

const connection = new Connection("https://api.mainnet-beta.solana.com");
const vaultAddress = new PublicKey("...");      // strategy vault
const clmmMarket = new PublicKey("...");        // CLMM MarketThree
const kaminoObligation = new PublicKey("...");  // Kamino obligation

const assertion = await VaultAssertion.create({
  connection,
  vault: vaultAddress,                          // PublicKey or a loaded vault
});

const report = await assertion.check({
  conditions: [
    {
      kind: VaultAssertionConditionKind.ClmmPrice,
      market: clmmMarket,
      impliedApy: { blockAboveBps: 1_500 },     // block if implied APY ≥ 15%
    },
    {
      kind: VaultAssertionConditionKind.KaminoLtv,
      obligation: kaminoObligation,
      ltv: { warnAboveBps: 6_800, blockAboveBps: 7_200 },
    },
  ],
});

if (report.decision === "block") {
  return;   // skip — caller decides what to do next
}
```

`VaultAssertion.create` accepts:

| Parameter    | Type                                     | Description                                                              |
| ------------ | ---------------------------------------- | ------------------------------------------------------------------------ |
| `connection` | `Connection`                             | Solana RPC connection                                                    |
| `vault`      | `PublicKey \| VaultAssertionLoadedVault` | Strategy vault address. Pass a loaded vault to skip an RPC fetch         |
| `owner`      | `PublicKey`                              | Optional. Expected owner — defaults from loaded vault state when present |
| `prices`     | `ExponentPrices`                         | Optional. Pre-decoded prices snapshot                                    |
| `commitment` | `Commitment`                             | Optional. Defaults to `confirmed`                                        |
| `env`        | `Environment`                            | Optional. Defaults from the loaded vault environment or `LOCAL_ENV`      |

`assertion.check` accepts a `VaultAssertionCheckPlan`:

| Parameter      | Type                        | Description                                                           |
| -------------- | --------------------------- | --------------------------------------------------------------------- |
| `name`         | `string`                    | Optional report name                                                  |
| `conditions`   | `VaultAssertionCondition[]` | Conditions to evaluate                                                |
| `maxSlotDrift` | `number`                    | Optional. Block-only guard for how many slots may pass during one run |

Invalid configuration throws `VaultAssertionConfigError` *before* any RPC reads or condition evaluation. Missing or undecodable on-chain state becomes a `warn` or `block` entry inside the returned report instead.

***

## Report Shape

```typescript theme={null}
type VaultAssertionReport = {
  name?: string;
  decision: "allow" | "warn" | "block";
  causes: VaultAssertionCause[];
  checks: VaultAssertionCheck[];
  timing: VaultAssertionTiming;
  commitment: string;
  checkedAt: string;
};
```

| Field        | Description                                                                                              |
| ------------ | -------------------------------------------------------------------------------------------------------- |
| `decision`   | Aggregate result. `"block"` if any condition blocks, otherwise `"warn"` if any warn, otherwise `"allow"` |
| `causes`     | Compact list of non-allow reasons — quick "why did this stop?" handling                                  |
| `checks`     | Full audit trail for every evaluated user-supplied condition (including allows)                          |
| `timing`     | Slot freshness metadata for the run                                                                      |
| `commitment` | Commitment level used for reads                                                                          |
| `checkedAt`  | ISO timestamp when the run started                                                                       |

`checks` only contain user-supplied conditions. Slot drift failures are surfaced through `causes` and `timing`; they do not create synthetic checks.

<Tip>
  Use `causes` for control flow (`if (report.decision === "block")` or `for (const cause of report.causes)`). Use `checks` for logging or audit display — every condition you supplied has a corresponding entry.
</Tip>

***

## Timing

```typescript theme={null}
type VaultAssertionTiming = {
  referenceSlot: number;
  latestObservedSlot: number;
  slotDrift: number;
  maxSlotDrift?: number;
  exceededMaxSlotDrift: boolean;
};
```

`check()` captures `referenceSlot` once at the start of the run. Each condition records the most recent slot observed through RPC, and the report tracks the maximum drift across the run.

`maxSlotDrift` is optional and disabled by default. When supplied, drift is checked before and after each condition. If drift exceeds the threshold, the report blocks and the remaining conditions are skipped.

<Warning>
  `referenceSlot` is a freshness and audit baseline. It is not an atomic multi-account snapshot guarantee. Transaction blockhash freshness and confirmation remain outside `VaultAssertion` — the caller's transaction builder/sender owns those.
</Warning>

***

## Thresholds

Built-in conditions accept inclusive thresholds in two flavors:

**Basis-point thresholds** — used for APY-style metrics:

```typescript theme={null}
{ warnAboveBps?: number; blockAboveBps?: number; warnBelowBps?: number; blockBelowBps?: number }
```

**Raw numeric thresholds** — used for prices, slot ages, and exchange rates:

```typescript theme={null}
{ warnAbove?: NumericValue; blockAbove?: NumericValue; warnBelow?: NumericValue; blockBelow?: NumericValue }
```

`NumericValue` accepts `number | string | bigint | BN | Decimal`.

At least one bound must be specified per threshold. Any of the following throws `VaultAssertionConfigError` before evaluation:

* empty threshold objects
* unknown threshold fields, or wrong-unit fields for a metric (e.g., `warnAboveBps` on a price threshold)
* negative values, or non-integer BPS values
* block/warn ordering that makes the block threshold less severe than the warn threshold

When a threshold is breached, the corresponding check evidence includes `breachedThresholds` for inspection.

***

## Built-In Conditions

### VaultCoreState

Asserts strategy vault owner and status-flag invariants. With no inputs, it just records vault state in the audit trail.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.VaultCoreState,
  }],
});
```

| Parameter            | Type        | Description                                                 |
| -------------------- | ----------- | ----------------------------------------------------------- |
| `owner`              | `PublicKey` | Optional. Expected owner. Defaults to assertion-level owner |
| `requireStatusFlags` | `number`    | Optional. Status flag bits that must be set                 |
| `forbidStatusFlags`  | `number`    | Optional. Status flag bits that must not be set             |

### VaultPriceCoverage

Asserts that decoded `ExponentPrices` covers all required price IDs (or pairs) and is within a freshness bound.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.VaultPriceCoverage,
  }],
});
```

| Parameter                   | Type               | Description                                                                           |
| --------------------------- | ------------------ | ------------------------------------------------------------------------------------- |
| `prices`                    | `ExponentPrices`   | Optional. Pre-decoded prices snapshot. Otherwise fetched via the loaded vault fetcher |
| `includeTrackedVaultPrices` | `boolean`          | Optional. Default `true`. Include all price IDs tracked by the strategy vault         |
| `requiredPriceIds`          | `NumericValue[]`   | Optional. Explicit price IDs that must be present                                     |
| `requiredPricePairs`        | `VaultPricePair[]` | Optional. `{ priceMint, underlyingMint }` pairs that must resolve                     |
| `slotAge`                   | `NumberThreshold`  | Optional. Freshness bound on each entry's `lastUpdatedSlot`                           |

### KaminoLtv

Asserts a Kamino obligation's LTV. The calculation is:

```
ltvBps = borrowedAssetsMarketValueSf / depositedValueSf * 10_000
```

`borrowingDisabled` always blocks. A stale obligation warns when LTV and slot-age thresholds are otherwise passing.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.KaminoLtv,
    obligation: new PublicKey("..."),
    ltv: { warnAboveBps: 6_800, blockAboveBps: 7_200 },
  }],
});
```

| Parameter            | Type                       | Description                                                     |
| -------------------- | -------------------------- | --------------------------------------------------------------- |
| `obligation`         | `PublicKey`                | Required. Kamino obligation account                             |
| `ltv`                | `BpsThreshold`             | Required. LTV bound in basis points                             |
| `lendingProgramId`   | `PublicKey`                | Optional. Override the Kamino lending program ID                |
| `obligationSnapshot` | `KaminoObligationSnapshot` | Optional. Pre-fetched snapshot — skips an RPC read              |
| `slotAge`            | `NumberThreshold`          | Optional. Freshness bound on the obligation's `lastUpdatedSlot` |

### AmmPrice

Asserts price evidence on a legacy AMM (Market Two) market. The market is loaded through `Market.load(...)`.

```
impliedApyBps = (exp(lastLnImpliedRate) - 1) * 10_000
ptPrice       = market.currentPtPriceInAsset
ptPriceInSy   = market.currentPtPriceInSy
```

At least one of `impliedApy`, `ptPrice`, or `ptPriceInSy` is required.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.AmmPrice,
    market: new PublicKey("..."),
    impliedApy: { blockBelowBps: 900 },
  }],
});
```

| Parameter     | Type              | Description                                 |
| ------------- | ----------------- | ------------------------------------------- |
| `market`      | `PublicKey`       | Required. Market Two PDA                    |
| `impliedApy`  | `BpsThreshold`    | Optional. Implied-APY bound in basis points |
| `ptPrice`     | `NumberThreshold` | Optional. PT price (in asset)               |
| `ptPriceInSy` | `NumberThreshold` | Optional. PT price denominated in SY        |

<Note>
  The loaded market's `vault.selfAddress` is recorded as `sourceVault` evidence. It identifies the market's underlying source — it is not a check that the strategy vault owns the market.
</Note>

### ClmmPrice

Asserts live CLMM price evidence. The evaluator fetches the market account, validates its program owner against `EXPONENTCLMM_PROGRAM_ID`, decodes it, then fetches the linked ticks account and performs the same validation.

```
spot          = ticks.currentSpotPrice
impliedApyBps = (spot - 1) * 10_000
tau           = max(0, market.financials.expirationTs - nowUnix) / 31_536_000
ptPrice       = spot ** -tau
```

At least one of `impliedApy`, `ptPrice`, or `spotPrice` is required.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.ClmmPrice,
    market: new PublicKey("..."),
    impliedApy: { blockAboveBps: 1_500 },
  }],
});
```

| Parameter    | Type              | Description                     |
| ------------ | ----------------- | ------------------------------- |
| `market`     | `PublicKey`       | Required. CLMM MarketThree PDA  |
| `impliedApy` | `BpsThreshold`    | Optional. Implied-APY bound     |
| `ptPrice`    | `NumberThreshold` | Optional. Derived PT price      |
| `spotPrice`  | `NumberThreshold` | Optional. Spot price from ticks |

### OrderbookPrice

Asserts top-of-book APY and derived prices on an Exponent Orderbook. The evaluator filters expired offers, offers with non-positive amount or APY, and (by default) the assertion owner's own non-virtual offers. Virtual offers are kept by default.

```
ptPrice       = calcPriceInAssetsByAPYPt(selectedRate, secondsRemaining)
ytPrice       = 1 - ptPrice
impliedApyBps = (exp(selectedRate) - 1) * 10_000
```

When both sides exist and `source === "mid"`, the mid is used; with one side only, the available side is used.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.OrderbookPrice,
    orderbook: new PublicKey("..."),
    impliedApy: { blockAboveBps: 1_050 },
  }],
});
```

| Parameter              | Type                              | Description                                                                     |
| ---------------------- | --------------------------------- | ------------------------------------------------------------------------------- |
| `orderbook`            | `PublicKey`                       | Required. Orderbook PDA                                                         |
| `impliedApy`           | `BpsThreshold`                    | Optional. Implied-APY bound                                                     |
| `ptPrice`              | `NumberThreshold`                 | Optional. Derived PT price                                                      |
| `ytPrice`              | `NumberThreshold`                 | Optional. Derived YT price                                                      |
| `source`               | `"mid" \| "bestBid" \| "bestAsk"` | Optional. Default `"mid"`                                                       |
| `owner`                | `PublicKey \| false`              | Optional. Defaults to the assertion owner. `false` disables own-offer filtering |
| `includeVirtualOffers` | `boolean`                         | Optional. Default `true`                                                        |
| `spread`               | `BpsThreshold`                    | Optional. Auxiliary threshold on the bid/ask spread                             |

<Note>
  Like `AmmPrice`, the loaded orderbook's `vault.selfAddress` is recorded as `sourceVault` evidence. It is not a strategy-vault ownership check.
</Note>

### SyExchangeRate

Asserts a core vault's SY/base exchange rate, loaded through `CoreVault.load(...)`.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.SyExchangeRate,
    coreVault: new PublicKey("..."),
    exchangeRate: { warnBelow: "1.03", blockBelow: "1.01" },
  }],
});
```

| Parameter      | Type              | Description                                |
| -------------- | ----------------- | ------------------------------------------ |
| `coreVault`    | `PublicKey`       | Required. Exponent Core vault address      |
| `exchangeRate` | `NumberThreshold` | Required. Bound on `currentSyExchangeRate` |

### KaminoBorrowCapacity

Asserts that a Kamino obligation has the headroom to take on a new borrow at a target LTV. The evaluator loads the obligation, the collateral and borrow reserves, and computes the projected post-borrow LTV against `maxLtvBps` (with optional `ltvBufferBps`).

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.KaminoBorrowCapacity,
    market: new PublicKey("..."),
    collateralReserve: new PublicKey("..."),
    borrowReserve: new PublicKey("..."),
    requestedBorrowAtomic: 50_000_000n,
    maxLtvBps: 7_000,
  }],
});
```

| Parameter                   | Type           | Description                                                                                  |
| --------------------------- | -------------- | -------------------------------------------------------------------------------------------- |
| `market`                    | `PublicKey`    | Required. Kamino lending market                                                              |
| `collateralReserve`         | `PublicKey`    | Required. Reserve holding the collateral side                                                |
| `borrowReserve`             | `PublicKey`    | Required. Reserve being borrowed against                                                     |
| `requestedBorrowAtomic`     | `NumericValue` | Required. Atomic amount of debt to take on                                                   |
| `maxLtvBps`                 | `number`       | Required. Maximum projected LTV in basis points                                              |
| `owner`                     | `PublicKey`    | Optional. Obligation owner — defaults to the assertion owner                                 |
| `obligation`                | `PublicKey`    | Optional. Pre-derived obligation PDA. Otherwise derived from `market` + `owner`              |
| `projectedCollateralAtomic` | `NumericValue` | Optional. Override the projected collateral atomic amount                                    |
| `minimumBorrowAtomic`       | `NumericValue` | Optional. Minimum borrow that still satisfies caller intent                                  |
| `ltvBufferBps`              | `number`       | Optional. Headroom buffer applied to `maxLtvBps`                                             |
| `resizeToCapacity`          | `boolean`      | Optional. Allow the evaluator to suggest a smaller borrow that still fits inside `maxLtvBps` |

### KaminoWithdrawCapacity

Asserts that a Kamino obligation has the headroom to withdraw collateral while still satisfying `maxLtvBps`. Symmetrical to `KaminoBorrowCapacity` on the withdraw side.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.KaminoWithdrawCapacity,
    market: new PublicKey("..."),
    collateralReserve: new PublicKey("..."),
    requestedWithdrawAtomic: 100_000_000n,
    maxLtvBps: 7_000,
  }],
});
```

| Parameter                 | Type                  | Description                                                                                       |
| ------------------------- | --------------------- | ------------------------------------------------------------------------------------------------- |
| `market`                  | `PublicKey`           | Required. Kamino lending market                                                                   |
| `collateralReserve`       | `PublicKey`           | Required. Reserve holding the collateral being withdrawn                                          |
| `requestedWithdrawAtomic` | `NumericValue`        | Required. Atomic collateral amount to withdraw                                                    |
| `maxLtvBps`               | `number`              | Required. Maximum post-withdraw LTV in basis points                                               |
| `owner`                   | `PublicKey`           | Optional. Obligation owner — defaults to the assertion owner                                      |
| `obligation`              | `PublicKey`           | Optional. Pre-derived obligation PDA                                                              |
| `minimumWithdrawAtomic`   | `NumericValue`        | Optional. Minimum withdraw that still satisfies caller intent                                     |
| `projectedDebt`           | `KaminoProjectedDebt` | Optional. Override projected debt as `{ borrowedValueSf }` or `{ borrowReserve, borrowedAtomic }` |
| `ltvBufferBps`            | `number`              | Optional. Headroom buffer applied to `maxLtvBps`                                                  |
| `resizeToCapacity`        | `boolean`             | Optional. Allow the evaluator to suggest a smaller withdraw that still fits inside `maxLtvBps`    |

### ClmmCapacity

Asserts that a CLMM swap of `amountInAtomic` from `inputMint` to `outputMint` is executable against the live tick state.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.ClmmCapacity,
    market: new PublicKey("..."),
    inputMint: new PublicKey("..."),
    outputMint: new PublicKey("..."),
    amountInAtomic: 1_000_000_000n,
  }],
});
```

| Parameter                      | Type                   | Description                                                                                     |
| ------------------------------ | ---------------------- | ----------------------------------------------------------------------------------------------- |
| `market`                       | `PublicKey`            | Required. CLMM `MarketThree` PDA                                                                |
| `inputMint` / `outputMint`     | `PublicKey`            | Required. Swap direction                                                                        |
| `amountInAtomic`               | `NumericValue`         | Required. Atomic input amount                                                                   |
| `minimumExecutableInputAtomic` | `NumericValue`         | Optional. Minimum input that still satisfies caller intent                                      |
| `resizeBelowMaximum`           | `boolean`              | Optional. Allow the evaluator to suggest a smaller input that fits inside live capacity         |
| `slippageBps`                  | `number`               | Optional. Slippage budget applied to expected output                                            |
| `capacitySnapshot`             | `ClmmCapacitySnapshot` | Optional. Pre-fetched `{ maxExecutableInputAtomic, expectedOutputAtomic? }` — skips an RPC read |

### SyBacking

Asserts that an SY mint is sufficiently backed by its underlying. Useful pre-flight before stripping or redeeming SY-denominated positions.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.SyBacking,
    syMint: new PublicKey("..."),
    requiredUnderlyingAtomic: 1_000_000_000n,
  }],
});
```

| Parameter                  | Type           | Description                                                                                         |
| -------------------------- | -------------- | --------------------------------------------------------------------------------------------------- |
| `syMint`                   | `PublicKey`    | Required. SY mint to verify                                                                         |
| `requiredUnderlyingAtomic` | `NumericValue` | Optional. Underlying backing the caller expects to be available                                     |
| `snapshot`                 | object         | Optional. Pre-supplied `{ sySupplyAtomic, underlyingBackingAtomic, exchangeRate, lastUpdatedSlot }` |
| `blockWhenInsufficient`    | `boolean`      | Optional. Default `true` — block (rather than warn) when backing is insufficient                    |

### PolicyCoverage

Asserts that the strategy vault's policies cover a required list of programs. Useful for catching missing policies before a flow that depends on them.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.PolicyCoverage,
    requiredPrograms: [JUPITER_LEND_BORROW_PROGRAM_ID, KAMINO_LENDING_PROGRAM_ID],
  }],
});
```

| Parameter          | Type                  | Description                                                                               |
| ------------------ | --------------------- | ----------------------------------------------------------------------------------------- |
| `requiredPrograms` | `PublicKey[]`         | Required. Program IDs every vault policy set should cover                                 |
| `policySnapshot`   | `VaultPolicySnapshot` | Optional. Pre-supplied snapshot — otherwise read from loaded vault state                  |
| `blockWhenMissing` | `boolean`             | Optional. Default `true` — block (rather than warn) when a required program isn't covered |

### WithdrawalQueue

Asserts the state of the strategy vault's withdrawal queue — useful for managers running fill/execute flows who want to refuse to act when there's an unfilled or past-due withdrawal.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.WithdrawalQueue,
    blockWhenUnfilled: true,
    blockWhenPastDue: true,
  }],
});
```

| Parameter           | Type      | Description                                                                                                                                                                  |
| ------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `blockWhenUnfilled` | `boolean` | Optional. Block when there's at least one unfilled withdrawal                                                                                                                |
| `blockWhenPastDue`  | `boolean` | Optional. Block when there's at least one past-due withdrawal                                                                                                                |
| `snapshot`          | object    | Optional. Pre-supplied `{ accounts, lpLockedForWithdrawalAtomic?, pendingWithdrawalBacklogDueAt?, nowUnix? }` — otherwise the evaluator fetches via the loaded vault fetcher |

***

## Custom Conditions

`Custom` conditions let you express app-specific policy that doesn't belong as a generic SDK condition. They share the same audit-trail and evidence semantics as built-in conditions.

```typescript theme={null}
const report = await assertion.check({
  conditions: [{
    kind: VaultAssertionConditionKind.Custom,
    async evaluate(ctx) {
      const vaultUsdcTokenAccount = new PublicKey("...");
      const balance = await ctx.fetchTokenAmount(vaultUsdcTokenAccount);

      if (balance > 10_000_000n) {
        return ctx.block("Idle USDC exceeds cap", {
          observed: { balance },
          thresholds: { maxBalance: 10_000_000n },
          accounts: { vaultUsdcTokenAccount },
        });
      }

      return ctx.allow("Idle USDC within cap", {
        observed: { balance },
        accounts: { vaultUsdcTokenAccount },
      });
    },
  }],
});
```

The `ctx` (`VaultAssertionContext`) gives the evaluator first-class RPC access and report helpers:

| Field / Method                                         | Description                                                                               |
| ------------------------------------------------------ | ----------------------------------------------------------------------------------------- |
| `connection`                                           | The assertion's `Connection`                                                              |
| `vault` / `vaultAddress`                               | Loaded strategy vault data and its address                                                |
| `owner`                                                | Effective owner used by the assertion                                                     |
| `referenceSlot` / `latestObservedSlot`                 | Slot baseline and the most recent slot observed in this run                               |
| `commitment`                                           | The configured commitment                                                                 |
| `slotAge(lastUpdatedSlot)`                             | Non-negative slot age relative to `referenceSlot`                                         |
| `fetchAccountInfo(address)`                            | Read and cache a raw account for the run                                                  |
| `fetchTokenAmount(tokenAccount)`                       | Read an SPL token amount as `bigint`                                                      |
| `fetchKaminoObligation(obligation, programId?)`        | Fetch and decode a Kamino obligation snapshot                                             |
| `fetchDecodedKaminoObligation(obligation, programId?)` | Fetch and decode a raw Kamino `Obligation` account (lower-level than the snapshot helper) |
| `fetchKaminoReserve(reserve, programId?)`              | Fetch and decode a raw Kamino `Reserve` account                                           |
| `allow(message, evidence?)`                            | Build an allow result                                                                     |
| `warn(message, evidence?)`                             | Build a warn result                                                                       |
| `block(message, evidence?)`                            | Build a block result                                                                      |

Helpers cache reads within a single `check()` run. Evidence is normalized into JSON-safe report data — `BigInt`, `BN`, `Decimal`, `PublicKey`, `Date`, `Buffer`, `Uint8Array`, non-finite numbers, and cyclic objects are all handled.

<Warning>
  Invalid return values from `evaluate(...)` (e.g., a thrown error or a malformed result) are converted into blocking `evaluation_error` entries rather than crashing the whole run.
</Warning>

***

## Gating a Transaction

The expected integration is caller-controlled: assert first, decide second, build/send last.

```typescript theme={null}
import {
  VaultAssertion,
  VaultAssertionConditionKind,
  VaultTransactionBuilder,
} from "@exponent-labs/exponent-sdk";

const assertion = await VaultAssertion.create({ connection, vault });

const report = await assertion.check({
  conditions: [
    { kind: VaultAssertionConditionKind.VaultCoreState },
    {
      kind: VaultAssertionConditionKind.KaminoLtv,
      obligation: kaminoObligation,
      ltv: { warnAboveBps: 6_800, blockAboveBps: 7_200 },
    },
  ],
});

if (report.decision === "block") {
  return;   // skip the run; surface report.causes to your operator/log
}

const result = await VaultTransactionBuilder
  .create({ vault, connection, signer: manager.publicKey })
  .addActions([/* ... */])
  .build();

await result.send({ signers: [manager] });
```

`report.causes` and `report.checks` are JSON-safe — log them, ship them to a dashboard, or surface them to a manager UI. The decision belongs to the caller.
