Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Spell Syntax

Reference for the Grimoire .spell format.

Structure

Every spell follows this top-level form:

spell MyStrategy {
  version: "1.0.0"
  description: "Description of what this spell does"

  assets: [USDC, WETH]
  params: { amount: 1000000 }
  venues: { dex: @uniswap_v3 }

  on manual: {
    dex.swap(USDC, WETH, params.amount)
  }
}

The spell name and at least one on trigger handler are required. All other sections are optional.

Top-Level Sections

params

Input parameters with optional type annotations and defaults:

params: {
  amount: 1000000
  enabled: true
  target: {
    type: amount
    asset: USDC
    default: 1.5 USDC
    min: 0
    max: 1000000
  }
}

Override at runtime with --params '{"amount": 500000}'.

assets

Declare assets used in the spell. Simple list or with explicit chain/address:

assets: [USDC, WETH]

venues

Map aliases to protocol adapters. The alias is how you call actions in the spell body:

venues: {
  dex: @uniswap_v3
  lender: @aave_v3
  lenders: [@aave_v3, @morpho_blue]
}

limits

Runtime policy constraints applied to every action in the spell:

limits: {
  max_single_move: 500000
  approval_required_above: 100000
}

state

Persistent state that survives across runs:

state: {
  persistent: {
    last_rebalance: 0
    total_swapped: 0
  }
  ephemeral: {
    current_price: 0
  }
}

Persistent state is saved to the local SQLite store after each run. Ephemeral state resets every run. See State Persistence for details.

advisors

AI model configuration for advise decision points:

advisors: {
  risk: {
    model: "anthropic:claude-haiku-4-5-20251001"
    system_prompt: "Return JSON only."
    timeout: 20
    fallback: false
  }
}

guards

Preconditions that halt, revert, or warn before action execution:

guards: {
  enough_balance: balance(USDC) > 1000 with (
    severity="halt",
    message="Insufficient USDC balance",
  )
}

Guard severity options: warn, revert, halt.

Triggers

Every spell has one or more on <trigger>: handlers. Multiple handlers compile to trigger.any — the first matching trigger fires.

Manual

Run on demand via grimoire cast or grimoire simulate:

on manual: {
  dex.swap(USDC, WETH, params.amount)
}

Scheduled

on hourly: { ... }
on daily: { ... }
on "0 9 * * 1-5": { ... }  # weekdays at 9am UTC

Condition-based

Polls every <seconds> and fires when the expression is true:

on condition price(ETH, USDC) < 2000 every 60: {
  lender.lend(USDC, params.amount)
}

Event-based

on event "PriceAlert" where event.asset == "ETH": {
  dex.swap(USDC, ETH, params.amount)
}

Statements

Supported in any trigger body:

StatementExample
Assignmentx = expr
Action calldex.swap(USDC, WETH, amount)
Conditionalif x > 0 { ... } elif ... { ... } else { ... }
Loopfor item in list { ... }
Repeatrepeat 3 { ... }
Loop untilloop until cond max 10 { ... }
Try/catchtry { ... } catch err { ... } finally { ... }
Parallelparallel { branch1: { ... } branch2: { ... } }
Atomicatomic { ... }
Emitemit settled(amount=result)
Halthalt "reason"
Waitwait 60
Advisorydecision = advise risk: "prompt" { ... }

Action Calls

Call protocol actions through your venue aliases:

dex.swap(USDC, WETH, params.amount)
lender.lend(USDC, params.amount)
lender.borrow(USDC, params.amount, WETH)
morpho.supply_collateral(WETH, amount, "weth-usdc-86")
bridge.bridge(USDC, amount, 42161)

Constraints

Apply per-action constraints with the with clause:

dex.swap(USDC, WETH, amount) with max_slippage=50, deadline=300

Constraint aliases: slippagemax_slippage, min_outmin_output, max_inmax_input.

Advisory Decisions

Embed typed AI decision points that always have a fallback:

decision = advise risk: "Should we rebalance given current rates?" {
  context: {
    balance: balance(USDC)
    price: price(ETH, USDC)
  }
  within: "execution"
  output: {
    type: object
    fields: {
      allow: boolean
      reason: string
    }
  }
  on_violation: reject
  timeout: 20
  fallback: { allow: false, reason: "timeout" }
}

if decision.allow {
  dex.swap(USDC, WETH, params.amount)
}

See Advisory Decisions for the full how-to guide.

Expressions

Built-in functions:

FunctionDescription
balance(asset)On-chain token balance of the vault
balance(asset, address)On-chain balance of a specific address
price(base, quote)Token price via Alchemy (requires Alchemy RPC)
min(a, b)Minimum of two values
max(a, b)Maximum of two values

Unit literals: 1.5 USDC, 50 bps, 5m, 1h, 1d, 50%.