# Almanak SDK

> Python SDK for developing and deploying autonomous DeFi agents

# Overview

**Production DeFi strategy framework for Quants**

[English](/) | [中文](/zh/) | [Français](/fr/) | [Español](/es/)

______________________________________________________________________

The Almanak SDK provides a comprehensive framework for developing, testing, and deploying autonomous DeFi agents. Built on an intent-based architecture, strategies are expressed as high-level intents with minimal boilerplate.

## Features

- **Intent-Based Architecture** - Express trading logic as high-level intents (Swap, LP, Borrow, etc.). The framework handles compilation and execution.
- **Three-Tier State Management** - Automatic persistence with HOT/WARM/COLD tiers for reliability.
- **Comprehensive Backtesting** - PnL simulation, paper trading on Anvil forks, and parameter sweeps.
- **Multi-Chain Support** - Ethereum, Arbitrum, Optimism, Base, Avalanche, Polygon, BSC, Sonic, Plasma, Blast, Mantle, Berachain, Solana, Monad.
- **Protocol Integration** - Uniswap V3/V4, PancakeSwap V3, SushiSwap V3, TraderJoe V2, Aerodrome, Curve, Balancer, Aave V3, Morpho Blue, GMX V2, Compound V3, Pendle, Polymarket, Kraken, and more.
- **Non-Custodial Design** - Full control over your funds through Safe smart accounts with automatic permission manifest generation for Zodiac Roles.
- **Agentic DeFAI Trading** - Build autonomous LLM-driven agents with 29 built-in tools, policy-enforced safety, and support for OpenAI, MCP, and LangChain.
- **Production-Ready** - Built-in alerting, stuck detection, emergency management, and canary deployments.

## Installation

```
pipx install almanak
```

Anvil fork testing (below) requires [Foundry](https://book.getfoundry.sh/getting-started/installation):

```
curl -L https://foundry.paradigm.xyz | bash
foundryup
```

## Quick Start

```
# Scaffold a new strategy (creates a self-contained Python project with pyproject.toml, .venv/, uv.lock)
almanak strat new

# Run it on a local Anvil fork -- no wallet or API keys required
cd my_strategy
almanak strat run --network anvil --once
```

Each scaffolded strategy is a self-contained Python project with its own `pyproject.toml`, `.venv/`, and `uv.lock`. The same files drive both local development and the platform's cloud Docker build.

Anvil fork testing is the recommended starting point. The SDK auto-starts a local fork, uses a default funded wallet, and runs your strategy with zero configuration. See [Getting Started](https://sdk.docs.almanak.co/getting-started.html) for the full walkthrough.

Two Ways to Build

**Deterministic strategies** (recommended) -- write Python logic in `decide()`. See [Getting Started](https://sdk.docs.almanak.co/getting-started.html).

**Agentic strategies** -- let an LLM decide using Almanak's tools. Requires your own LLM API key. See [Agentic Trading](https://sdk.docs.almanak.co/agentic/index.html).

## Writing a Strategy

Strategies implement the `decide()` method, which receives a `MarketSnapshot` and returns an `Intent` (or `None` to skip the cycle):

```
from decimal import Decimal
from almanak.framework.intents import Intent
from almanak.framework.strategies import IntentStrategy, MarketSnapshot

class MyStrategy(IntentStrategy):
    """A simple mean-reversion strategy."""

    def decide(self, market: MarketSnapshot) -> Intent | None:
        eth_price = market.price("ETH")
        usdc = market.balance("USDC")

        if eth_price < Decimal("2000") and usdc.balance_usd > Decimal("500"):
            return Intent.swap(
                from_token="USDC",
                to_token="ETH",
                amount_usd=Decimal("500"),
            )
        return Intent.hold(reason="Waiting for better conditions")
```

## Architecture

```
almanak/
  framework/           # V2 Strategy Framework
    strategies/        # IntentStrategy base class
    intents/           # Intent vocabulary & compiler
    state/             # Three-tier state management
    execution/         # Transaction orchestration
    backtesting/       # PnL, paper trading, sweeps
    connectors/        # Protocol adapters
    data/              # Price oracles, indicators
    alerting/          # Slack/Telegram notifications
    services/          # Stuck detection, emergency mgmt
  gateway/             # gRPC gateway sidecar
  transaction_builder/ # Low-level tx building
  core/                # Enums, models, utilities
  cli/                 # Command-line interface
```

All strategies run through a **gateway-only architecture** for security. The gateway sidecar holds all secrets and exposes a controlled gRPC API. Strategy containers have no secrets and no direct internet access.

## Feedback & Feature Requests

Have an idea, found a bug, or want to request a feature? Head over to our [Discord](https://discord.gg/yuCMvQv3rN) and post in the appropriate channel. We actively monitor feedback there and use it to shape the SDK roadmap.

## Next Steps

- [Getting Started](https://sdk.docs.almanak.co/getting-started.html) - Installation and first strategy walkthrough
- [Agentic Trading](https://sdk.docs.almanak.co/agentic/index.html) - Build LLM-driven autonomous agents
- [CLI Reference](https://sdk.docs.almanak.co/cli/almanak.html) - All CLI commands
- [API Reference](https://sdk.docs.almanak.co/api/index.html) - Full Python API documentation
- [Gateway](https://sdk.docs.almanak.co/gateway/api-reference.html) - Gateway gRPC API
# Getting Started

# Getting Started

This guide walks you through installing the Almanak SDK, scaffolding your first strategy, and running it locally on an Anvil fork -- no wallet or API keys required.

## Prerequisites

- **Python 3.12+**
- **uv** (Python package manager):

```
curl -LsSf https://astral.sh/uv/install.sh | sh
```

- **Foundry** (provides Anvil for local fork testing):

```
curl -L https://foundry.paradigm.xyz | bash
foundryup
```

## Installation

```
pipx install almanak
```

Or with [uv](https://docs.astral.sh/uv/):

```
uv tool install almanak
```

This installs the `almanak` CLI globally. Each scaffolded strategy also has `almanak` as a local dependency in its own `.venv/` -- this two-install pattern is standard (same as CrewAI, Dagster, etc.).

**Using an AI coding agent?** Teach it the SDK in one command:

```
almanak agent install
```

This auto-detects your platform (Claude Code, Codex, Cursor, Copilot, and [6 more](https://sdk.docs.almanak.co/agent-skills.html)) and installs the strategy builder skill.

## 1. Get a Strategy

### Option A: Copy a working demo (recommended for beginners)

```
almanak strat demo
```

This shows an interactive menu of working demo strategies. Pick one and it gets copied into your current directory, ready to run. You can also skip the menu:

```
almanak strat demo --name uniswap_rsi
```

### Option B: Scaffold from a template

```
almanak strat new
```

Follow the interactive prompts to pick a template, chain, and name. This creates a **self-contained Python project** with:

- `strategy.py` - Your strategy implementation with `decide()` method
- `config.json` - Runtime parameters (tokens, thresholds, funding)
- `pyproject.toml` - Dependencies and `[tool.almanak]` metadata
- `uv.lock` - Locked dependencies (created by `uv sync`)
- `.venv/` - Per-strategy virtual environment (created by `uv sync`)
- `.env` - Environment variables (fill in your keys later)
- `.gitignore` - Git ignore rules
- `.python-version` - Python version pin (3.12)
- `__init__.py` - Package exports
- `tests/` - Test scaffolding
- `AGENTS.md` - AI agent guide

The scaffold runs `uv sync` automatically to install dependencies. To add extra packages later:

```
uv add pandas-ta          # Updates pyproject.toml + uv.lock + .venv/
uv run pytest tests/ -v   # Run tests in the strategy's venv
```

## 2. Run on a Local Anvil Fork

The fastest way to test your strategy -- no wallet keys, no real funds, no risk:

```
cd my_strategy
almanak strat run --network anvil --once
```

This command automatically:

1. **Starts an Anvil fork** of the chain specified in your strategy (free public RPCs are used by default)
1. **Uses a default Anvil wallet** -- no `ALMANAK_PRIVATE_KEY` needed
1. **Starts the gateway** sidecar in the background
1. **Funds your wallet** with tokens listed in `anvil_funding` (see below)
1. **Runs one iteration** of your strategy's `decide()` method

### Wallet Funding on Anvil

Add an `anvil_funding` block to your `config.json` to automatically fund your wallet when the fork starts:

```
{
    "anvil_funding": {
        "ETH": 10,
        "USDC": 10000,
        "WETH": 5
    }
}
```

Native tokens (ETH, AVAX, etc.) are funded via `anvil_setBalance`. ERC-20 tokens are funded via storage slot manipulation. This happens automatically each time the fork starts.

### Better RPC Performance (Optional)

Free public RPCs work but are rate-limited. For faster forking, set an Alchemy key in your `.env`:

```
ALCHEMY_API_KEY=your_alchemy_key
```

This auto-constructs RPC URLs for all supported chains. Any provider works -- see [Environment Variables](https://sdk.docs.almanak.co/environment-variables.html) for the full priority order.

## 3. Run on Mainnet

Warning

Mainnet execution uses **real funds**. Start with small amounts and use a dedicated wallet.

To run against live chains, you need a wallet private key in your `.env`:

```
# .env
ALMANAK_PRIVATE_KEY=0xYOUR_PRIVATE_KEY

# RPC access (pick one)
ALCHEMY_API_KEY=your_alchemy_key
# or: RPC_URL=https://your-rpc-provider.com/v1/your-key
```

Then run without the `--network anvil` flag:

```
almanak strat run --once
```

Tip

Test with `--dry-run` first to simulate without submitting transactions:

```
almanak strat run --dry-run --once
```

See [Environment Variables](https://sdk.docs.almanak.co/environment-variables.html) for the full list of configuration options including protocol-specific API keys.

Before going live

- Always run `--dry-run --once` before your first live execution to verify intent compilation without submitting transactions.
- If swaps revert with "Too little received", switch from `amount_usd=` to `amount=` (token units). `amount_usd=` relies on the gateway price oracle for USD-to-token conversion, which may diverge from the DEX price.
- Start with small amounts, monitor the first few iterations, and note your instance ID for `--id` resume.

## Strategy Structure

A strategy implements the `decide()` method, which receives a `MarketSnapshot` and returns an `Intent`:

```
from decimal import Decimal
from almanak import IntentStrategy, Intent, MarketSnapshot

class MyStrategy(IntentStrategy):
    def decide(self, market: MarketSnapshot) -> Intent | None:
        price = market.price("ETH")
        balance = market.balance("USDC")

        if price < Decimal("2000") and balance.balance_usd > Decimal("500"):
            return Intent.swap(
                from_token="USDC",
                to_token="ETH",
                amount_usd=Decimal("500"),
            )
        return Intent.hold(reason="No opportunity")
```

## Available Intents

| Intent                    | Description                                                                                   |
| ------------------------- | --------------------------------------------------------------------------------------------- |
| `SwapIntent`              | Token swaps on DEXs                                                                           |
| `HoldIntent`              | No action, wait for next cycle                                                                |
| `LPOpenIntent`            | Open liquidity position                                                                       |
| `LPCloseIntent`           | Close liquidity position                                                                      |
| `BorrowIntent`            | Borrow from lending protocols                                                                 |
| `RepayIntent`             | Repay borrowed assets                                                                         |
| `SupplyIntent`            | Supply to lending protocols                                                                   |
| `WithdrawIntent`          | Withdraw from lending protocols                                                               |
| `StakeIntent`             | Stake tokens                                                                                  |
| `UnstakeIntent`           | Unstake tokens                                                                                |
| `PerpOpenIntent`          | Open perpetuals position                                                                      |
| `PerpCloseIntent`         | Close perpetuals position                                                                     |
| `FlashLoanIntent`         | Flash loan operations                                                                         |
| `CollectFeesIntent`       | Collect LP fees                                                                               |
| `PredictionBuyIntent`     | Buy prediction market shares                                                                  |
| `PredictionSellIntent`    | Sell prediction market shares                                                                 |
| `PredictionRedeemIntent`  | Redeem prediction market winnings                                                             |
| `VaultDepositIntent`      | Deposit into a vault                                                                          |
| `VaultRedeemIntent`       | Redeem from a vault                                                                           |
| `WrapNativeIntent`        | Wrap native tokens (e.g., ETH to WETH). Factory: `Intent.wrap()`                              |
| `UnwrapNativeIntent`      | Unwrap native tokens (e.g., WETH to ETH). Factory: `Intent.unwrap()`                          |
| `Intent.bridge()`         | Bridge tokens cross-chain (factory method returning a composite intent)                       |
| `Intent.ensure_balance()` | Ensure minimum token balance on a target chain (factory method resolving to a bridge or hold) |

## State Persistence (Required for Stateful Strategies)

The framework automatically persists runner-level metadata (iteration counts, error counters) after each iteration. However, **strategy-specific state** -- position IDs, trade counts, phase tracking, cooldown timers -- is only saved if you implement two hooks:

```
from typing import Any
from decimal import Decimal

class MyStrategy(IntentStrategy):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._position_id: int | None = None
        self._trades_today: int = 0

    def get_persistent_state(self) -> dict[str, Any]:
        """Return state to save. Called after each iteration."""
        return {
            "position_id": self._position_id,
            "trades_today": self._trades_today,
        }

    def load_persistent_state(self, state: dict[str, Any]) -> None:
        """Restore state on startup. Called when resuming a run."""
        self._position_id = state.get("position_id")
        self._trades_today = state.get("trades_today", 0)
```

Without these hooks, your strategy will lose all internal state on restart. This is especially dangerous for LP strategies where losing the `position_id` means the strategy cannot close its own positions.

What gets lost without persistence

If you store state in instance variables (e.g., `self._position_id`) but don't implement `get_persistent_state()` and `load_persistent_state()`, that state is lost when the process stops. On restart, your strategy starts from scratch with no memory of open positions, completed trades, or internal phase.

Tips

- Use defensive `.get()` with defaults in `load_persistent_state()` so older state dicts don't crash on missing keys.
- Store `Decimal` values as strings (`str(amount)`) and parse them back (`Decimal(state["amount"])`) for safe JSON round-tripping.
- The `on_intent_executed()` callback is the natural place to update state after a trade (e.g., storing a new position ID), and `get_persistent_state()` then picks it up for saving.

## Strategy Teardown (Required)

Every strategy must implement teardown so operators can safely close positions. Without teardown, close-requests are silently ignored and positions remain open. The `almanak strat new` templates include stubs -- fill them in as you build your strategy.

```
class MyStrategy(IntentStrategy):
    def supports_teardown(self) -> bool:
        return True

    def get_open_positions(self) -> "TeardownPositionSummary":
        """Query on-chain state and return open positions."""
        from almanak.framework.teardown import PositionInfo, PositionType, TeardownPositionSummary
        # ... return TeardownPositionSummary with your positions

    def generate_teardown_intents(self, mode: "TeardownMode", market=None) -> list[Intent]:
        """Return ordered intents to unwind all positions."""
        from almanak.framework.teardown import TeardownMode
        max_slippage = Decimal("0.03") if mode == TeardownMode.HARD else Decimal("0.005")
        return [Intent.swap(from_token="WETH", to_token="USDC", amount="all", max_slippage=max_slippage)]
```

If your strategy holds multiple position types, close them in order: **perps -> borrows -> supplies -> LPs -> tokens**. See the [Teardown CLI](https://sdk.docs.almanak.co/cli/strat-teardown.html) for how operators trigger teardown.

## Generating Permissions (Safe Wallets)

When deploying a strategy through a Safe wallet with Zodiac Roles restrictions, the agent needs an explicit set of contract permissions. The SDK can generate this manifest automatically by inspecting which contracts and function selectors your strategy's intents compile to:

```
# From your strategy directory
almanak strat permissions

# Explicit directory
almanak strat permissions -d strategies/demo/uniswap_rsi

# Override chain
almanak strat permissions --chain base

# Write to file
almanak strat permissions -o permissions.json
```

The command reads `supported_protocols` and `intent_types` from your `@almanak_strategy` decorator, compiles synthetic intents through the real compiler, and extracts the minimum set of contract addresses and function selectors needed. The output is a JSON manifest you can apply to a Zodiac Roles module. If the strategy supports multiple chains, the output is a JSON array with one manifest per chain; use `--chain` to generate for a single chain.

Only for Safe/Zodiac deployments

Permission manifests are only needed when running through a Safe wallet with Zodiac Roles. For local Anvil testing or direct-key execution, no permissions are required.

Backtest CLI

Unlike `almanak strat run` which auto-discovers the strategy from the current directory, backtest commands require an explicit strategy name: `almanak strat backtest pnl -s my_strategy`. Use `--list-strategies` to see available strategies.

## Next Steps

- [Environment Variables](https://sdk.docs.almanak.co/environment-variables.html) - All configuration options
- [API Reference](https://sdk.docs.almanak.co/api/index.html) - Full Python API documentation
- [CLI Reference](https://sdk.docs.almanak.co/cli/almanak.html) - All CLI commands
- [Gateway API](https://sdk.docs.almanak.co/gateway/api-reference.html) - Gateway gRPC services

## Want an LLM to Make the Decisions?

The SDK also supports **agentic strategies** where an LLM autonomously decides what to do using Almanak's 29 built-in tools. Instead of writing `decide()` logic in Python, you write a system prompt and let the LLM reason over market data.

This approach requires **your own LLM API key** (OpenAI, Anthropic, or any OpenAI-compatible provider).

|                    | Deterministic (this guide)        | Agentic                             |
| ------------------ | --------------------------------- | ----------------------------------- |
| **You write**      | Python `decide()` method          | System prompt + policy              |
| **Decision maker** | Your code                         | LLM (GPT-4, Claude, etc.)           |
| **Requires**       | Just the SDK                      | SDK + LLM API key                   |
| **Best for**       | Known rules, quantitative signals | Complex reasoning, multi-step plans |

Both paths share the same gateway, connectors, and execution pipeline.

**Get started:** [Agentic Trading Guide](https://sdk.docs.almanak.co/agentic/index.html)
# Environment Variables

# Environment Variables

All strategies run through the **gateway sidecar** (auto-started by `almanak strat run`). The gateway holds secrets, provides RPC access, and executes transactions.

Create a `.env` file in your strategy directory with the variables below.

______________________________________________________________________

## Required

These must be set before running any strategy.

| Variable              | Description                                                                  | Example         |
| --------------------- | ---------------------------------------------------------------------------- | --------------- |
| `ALMANAK_PRIVATE_KEY` | Wallet private key for signing transactions and deriving your wallet address | `0x4c0883a6...` |

### RPC Access (recommended; free public RPCs used if unset)

| Variable                   | Priority     | Description                                                                | Example                                |
| -------------------------- | ------------ | -------------------------------------------------------------------------- | -------------------------------------- |
| `ALMANAK_{CHAIN}_RPC_URL`  | 1 (highest)  | Per-chain RPC URL with ALMANAK prefix                                      | `https://arb-mainnet.infura.io/v3/KEY` |
| `{CHAIN}_RPC_URL`          | 2            | Per-chain RPC URL (e.g. `ARBITRUM_RPC_URL`)                                | `https://arb-mainnet.infura.io/v3/KEY` |
| `ALMANAK_RPC_URL`          | 3            | Generic RPC URL for all chains                                             | `https://your-rpc.com/v1/KEY`          |
| `RPC_URL`                  | 4            | Bare generic RPC URL                                                       | `https://your-rpc.com/v1/KEY`          |
| `ALCHEMY_API_KEY`          | 5 (fallback) | Alchemy API key -- URLs built automatically per chain                      | `abc123def456`                         |
| `TENDERLY_API_KEY_{CHAIN}` | 6 (fallback) | Tenderly API key for chain-specific RPC (e.g. `TENDERLY_API_KEY_ARBITRUM`) | `abc123...`                            |

Any provider works: Infura, QuickNode, self-hosted, Alchemy, etc. `ALCHEMY_API_KEY` is an optional fallback that auto-constructs URLs for all supported chains. If none are set, the gateway falls back to free public RPCs (rate-limited, best-effort).

Warning

Never commit private keys. Use a dedicated testing wallet for development.

**Note:** The gateway also accepts `ALMANAK_GATEWAY_PRIVATE_KEY` (with its own prefix). If set, it takes precedence. Otherwise, the gateway falls back to `ALMANAK_PRIVATE_KEY` -- so you only need one variable.

______________________________________________________________________

## Optional API Keys

Set these based on which protocols and features your strategy uses.

| Variable            | When needed                                                | Get a key                                                |
| ------------------- | ---------------------------------------------------------- | -------------------------------------------------------- |
| `ENSO_API_KEY`      | Swap routing via Enso Finance aggregator                   | [enso.finance](https://enso.finance/)                    |
| `COINGECKO_API_KEY` | Improves rate limits for price data (works without key)    | [coingecko.com/en/api](https://www.coingecko.com/en/api) |
| `ALMANAK_API_KEY`   | Platform features: `strat push`, `strat pull`, deployment  | [app.almanak.co](https://app.almanak.co/)                |
| `THEGRAPH_API_KEY`  | Backtesting with subgraph data (DEX volumes, lending APYs) | [thegraph.com/studio](https://thegraph.com/studio/)      |

______________________________________________________________________

## Protocol-Specific

Only needed if your strategy uses these specific protocols.

### Kraken

| Variable            | Description                                                               |
| ------------------- | ------------------------------------------------------------------------- |
| `KRAKEN_API_KEY`    | Kraken API key ([get credentials](https://www.kraken.com/u/security/api)) |
| `KRAKEN_API_SECRET` | Kraken API secret                                                         |

### Polymarket

| Variable                    | Description               |
| --------------------------- | ------------------------- |
| `POLYMARKET_WALLET_ADDRESS` | Polymarket wallet address |
| `POLYMARKET_PRIVATE_KEY`    | Polymarket signing key    |
| `POLYMARKET_API_KEY`        | CLOB API key              |
| `POLYMARKET_SECRET`         | HMAC secret               |
| `POLYMARKET_PASSPHRASE`     | API passphrase            |

### Pendle

| Variable                         | Description             |
| -------------------------------- | ----------------------- |
| `ALMANAK_GATEWAY_PENDLE_API_KEY` | Pendle protocol API key |

### Solana

| Variable             | Description                                                                                                                             |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `SOLANA_PRIVATE_KEY` | Ed25519 keypair in base58 format (or 64-char hex seed). Required for Solana strategies.                                                 |
| `SOLANA_RPC_URL`     | Solana RPC endpoint. Defaults to `https://api.mainnet-beta.solana.com` (rate-limited). Use Helius, QuickNode, or Triton for production. |
| `JUPITER_API_KEY`    | Jupiter aggregator API key. Free tier is used if unset.                                                                                 |

______________________________________________________________________

## Safe Wallet

For strategies that execute through a Gnosis Safe multisig.

| Variable                               | Description                                           |
| -------------------------------------- | ----------------------------------------------------- |
| `ALMANAK_GATEWAY_SAFE_ADDRESS`         | Safe wallet address                                   |
| `ALMANAK_GATEWAY_SAFE_MODE`            | `direct` (Anvil/threshold-1) or `zodiac` (production) |
| `ALMANAK_GATEWAY_ZODIAC_ROLES_ADDRESS` | Zodiac Roles module address (zodiac mode)             |
| `ALMANAK_GATEWAY_SIGNER_SERVICE_URL`   | Remote signer service URL (zodiac mode)               |
| `ALMANAK_GATEWAY_SIGNER_SERVICE_JWT`   | Remote signer JWT (zodiac mode)                       |

______________________________________________________________________

## Backtesting

### Archive RPC URLs

Required for historical on-chain data (Chainlink prices, TWAP calculations). Standard RPC nodes don't support historical state queries. Use archive-enabled providers like Alchemy (paid), QuickNode, or Infura.

Pattern: `ARCHIVE_RPC_URL_{CHAIN}` (e.g., `ARCHIVE_RPC_URL_ARBITRUM`, `ARCHIVE_RPC_URL_ETHEREUM`, `ARCHIVE_RPC_URL_BASE`, `ARCHIVE_RPC_URL_OPTIMISM`, `ARCHIVE_RPC_URL_POLYGON`, `ARCHIVE_RPC_URL_AVALANCHE`)

### Block Explorer API Keys

Optional, for historical gas price data. Pattern: `{EXPLORER}_API_KEY`

| Variable                       | Explorer                                                        |
| ------------------------------ | --------------------------------------------------------------- |
| `ETHERSCAN_API_KEY`            | [etherscan.io](https://etherscan.io/apis)                       |
| `ARBISCAN_API_KEY`             | [arbiscan.io](https://arbiscan.io/apis)                         |
| `BASESCAN_API_KEY`             | [basescan.org](https://basescan.org/apis)                       |
| `OPTIMISTIC_ETHERSCAN_API_KEY` | [optimistic.etherscan.io](https://optimistic.etherscan.io/apis) |
| `POLYGONSCAN_API_KEY`          | [polygonscan.com](https://polygonscan.com/apis)                 |
| `SNOWTRACE_API_KEY`            | [snowtrace.io](https://snowtrace.io/apis)                       |
| `BSCSCAN_API_KEY`              | [bscscan.com](https://bscscan.com/apis)                         |

______________________________________________________________________

## Quick Start `.env`

```
# Required
ALMANAK_PRIVATE_KEY=0xYOUR_PRIVATE_KEY

# RPC access (pick one)
RPC_URL=https://your-rpc-provider.com/v1/your-key
# ALCHEMY_API_KEY=your_alchemy_key  # alternative: auto-builds URLs per chain

# Recommended
ENSO_API_KEY=your_enso_key
COINGECKO_API_KEY=your_coingecko_key
```

All other gateway and framework settings have sensible defaults and do not need to be set. See [`.env.example`](https://github.com/almanak-co/almanak-sdk/blob/main/.env.example) for the full list of advanced options.
# Agent Skills

# Agent Skills

The Almanak SDK ships with an **agent skill** -- a structured knowledge file that teaches AI coding agents how to build DeFi strategies. Install it into your project so your AI assistant understands the IntentStrategy API, intent vocabulary, market data methods, and CLI commands.

## Quick Install

The fastest way to get started:

```
almanak agent install
```

This auto-detects which AI coding tools you use (by checking for `.claude/`, `.cursor/`, `.github/`, etc.) and installs the skill into each platform's native skills directory.

## Platform-Specific Instructions

### Claude Code

Claude Code discovers skills from `.claude/skills/` subdirectories.

```
almanak agent install -p claude
```

**Result:** `.claude/skills/almanak-strategy-builder/SKILL.md`

______________________________________________________________________

### OpenAI Codex

Codex discovers project skills from `.codex/skills/` subdirectories.

```
almanak agent install -p codex
```

**Result:** `.codex/skills/almanak-strategy-builder/SKILL.md`

______________________________________________________________________

### Cursor

Cursor reads custom rules from `.cursor/rules/` as `.mdc` files with glob-based scoping.

```
almanak agent install -p cursor
```

The installed file includes YAML frontmatter that activates the skill when editing `strategy.py` or `config.json` files:

```
---
globs:
  - "**/strategy.py"
  - "**/config.json"
---
```

**Result:** `.cursor/rules/almanak-strategy-builder.mdc`

______________________________________________________________________

### GitHub Copilot

Copilot reads scoped instructions from `.github/instructions/` with `applyTo` frontmatter.

```
almanak agent install -p copilot
```

The installed file includes frontmatter scoped to strategy files:

```
---
applyTo: "**/strategy.py"
---
```

**Result:** `.github/instructions/almanak-strategy-builder.instructions.md`

______________________________________________________________________

### Windsurf

Windsurf reads rules from `.windsurf/rules/`.

```
almanak agent install -p windsurf
```

**Result:** `.windsurf/rules/almanak-strategy-builder.md`

______________________________________________________________________

### Cline

Cline reads rules from `.clinerules/`.

```
almanak agent install -p cline
```

**Result:** `.clinerules/almanak-strategy-builder.md`

______________________________________________________________________

### Roo Code

Roo Code reads rules from `.roo/rules/`.

```
almanak agent install -p roo
```

**Result:** `.roo/rules/almanak-strategy-builder.md`

______________________________________________________________________

### Aider

Aider discovers skills from `.aider/skills/` subdirectories.

```
almanak agent install -p aider
```

**Result:** `.aider/skills/almanak-strategy-builder/SKILL.md`

______________________________________________________________________

### Amazon Q

Amazon Q reads rules from `.amazonq/rules/`.

```
almanak agent install -p amazonq
```

**Result:** `.amazonq/rules/almanak-strategy-builder.md`

______________________________________________________________________

### OpenClaw

OpenClaw discovers skills from `.openclaw/skills/` subdirectories.

```
almanak agent install -p openclaw
```

**Result:** `.openclaw/skills/almanak-strategy-builder/SKILL.md`

______________________________________________________________________

## Install All Platforms

To install for every supported platform at once:

```
almanak agent install -p all
```

## Global Install

Install into your home directory (`~/`) so the skill is available in every project without per-project setup:

```
almanak agent install -g
```

This writes to `~/.claude/skills/`, `~/.codex/skills/`, `~/.cursor/rules/`, etc. Platforms that only support project-scoped rules (Copilot, Cline, Roo Code, Amazon Q) are automatically skipped.

You can also target specific platforms:

```
almanak agent install -g -p claude
```

To check or update global installs:

```
almanak agent status -g
almanak agent update -g
```

Note

Most platforms let local (project) skills override global ones. If you have both, the project-level skill takes precedence.

## Install via npx (skills.sh)

If you use the [skills.sh](https://skills.sh) registry, you can install directly from GitHub:

```
npx skills add almanak-co/almanak-sdk
```

This discovers the `almanak-strategy-builder` skill from the public repo and installs it for your detected platform.

## Managing Installed Skills

### Check Status

See which platforms have the skill installed and whether they are up to date:

```
almanak agent status
```

Example output:

```
Agent skill status (SDK v2.0.0):

  claude      up to date (v2.0.0)
  codex       not installed
  cursor      outdated (v1.9.0 -> v2.0.0)
  copilot     not installed
  ...

Installed: 1  Outdated: 1  Missing: 8
```

### Update

After upgrading the SDK (`pip install --upgrade almanak`), update your installed skill files to match:

```
almanak agent update
```

This scans for all installed platform files and replaces their content with the current SDK version.

### Dry Run

Preview what `install` would do without writing any files:

```
almanak agent install --dry-run
```

### Custom Directory

Install into a specific project directory:

```
almanak agent install -d /path/to/my-project -p claude
```

## Per-Strategy Guides

When you scaffold a new strategy with `almanak strat new`, an `AGENTS.md` file is automatically generated inside the strategy directory. This lightweight guide is tailored to the template you chose -- it lists only the intent types and patterns relevant to that specific strategy.

Each scaffolded strategy is a self-contained Python project with `pyproject.toml`, `.venv/`, and `uv.lock`, so the per-strategy `AGENTS.md` also documents adding dependencies (`uv add`) and running tests (`uv run pytest`).

```
almanak strat new --template mean_reversion --name my_rsi --chain arbitrum
# Creates my_rsi/AGENTS.md alongside strategy.py, config.json, pyproject.toml, etc.
```

## What the Skill Teaches

The bundled skill covers:

| Section            | What it covers                                                           |
| ------------------ | ------------------------------------------------------------------------ |
| Quick Start        | Install, scaffold, run                                                   |
| Core Concepts      | IntentStrategy, decide(), MarketSnapshot                                 |
| Intent Reference   | All intent types with signatures and examples                            |
| Market Data API    | price(), balance(), rsi(), macd(), bollinger_bands(), and 10+ indicators |
| State Management   | self.state persistence, on_intent_executed callback                      |
| Configuration      | config.json format, .env secrets, anvil_funding                          |
| Token Resolution   | get_token_resolver(), resolve_for_swap()                                 |
| Backtesting        | PnL simulation, paper trading, parameter sweeps                          |
| CLI Commands       | strat new/run/demo/backtest, gateway, agent                              |
| Chains & Protocols | 14 chains, 14 protocols with enum names                                  |
| Common Patterns    | Rebalancing, alerting, teardown, IntentSequence                          |
| Troubleshooting    | Common errors and fixes                                                  |

## Reading the Skill Directly

To view or grep the raw skill content:

```
# Print the file path
almanak docs agent-skill

# Dump the full content
almanak docs agent-skill --dump

# Search for specific topics
almanak docs agent-skill --dump | grep "Intent.swap"
```
# CLI Reference

# almanak ax

Execute DeFi actions directly from the command line. One-shot commands for swaps, balance checks, price queries, and more -- no strategy files needed.

Auto-starts a gateway if none is running. Use `--network anvil` for safe local testing.

## Usage

```
Usage: almanak ax [OPTIONS] COMMAND [ARGS]...
```

`almanak ax` supports two modes:

- **Structured mode** (default) -- deterministic, scriptable, no LLM needed
- **Natural language mode** (`--natural` / `-n`) -- describe what you want in plain English, LLM interprets it

## Options

| Option            | Short | Default     | Description                                                                                        |
| ----------------- | ----- | ----------- | -------------------------------------------------------------------------------------------------- |
| `--gateway-host`  |       | `localhost` | Gateway hostname (env: `GATEWAY_HOST`)                                                             |
| `--gateway-port`  |       | `50051`     | Gateway gRPC port (env: `GATEWAY_PORT`)                                                            |
| `--chain`         | `-c`  | `arbitrum`  | Default chain (env: `ALMANAK_CHAIN`)                                                               |
| `--wallet`        | `-w`  | auto        | Wallet address (env: `ALMANAK_WALLET_ADDRESS`). Auto-derived from `ALMANAK_PRIVATE_KEY` if not set |
| `--max-trade-usd` |       | `10000`     | Max single trade size in USD                                                                       |
| `--dry-run`       |       | `false`     | Simulate only, do not submit transactions                                                          |
| `--json`          |       | `false`     | Output results as JSON instead of human-readable tables                                            |
| `--yes`           | `-y`  | `false`     | Skip confirmation prompts (for non-interactive / AI agent use)                                     |
| `--natural`       | `-n`  |             | Natural language mode (see below)                                                                  |
| `--network`       |       | auto        | `mainnet` or `anvil`. Auto-starts a gateway if none is running                                     |

## Structured Mode

Use subcommands with explicit parameters. Deterministic, scriptable, zero latency overhead.

### `almanak ax price`

Get the current USD price of a token.

```
almanak ax price ETH
almanak ax price USDC --chain base
almanak ax price ETH --json
```

### `almanak ax balance`

Get the balance of a token in your wallet.

```
almanak ax balance USDC
almanak ax balance ETH --chain base
almanak ax balance WETH --json
```

### `almanak ax swap`

Swap tokens on a DEX.

```
almanak ax swap USDC ETH 100                    # Swap 100 USDC to ETH
almanak ax swap USDC ETH 100 --dry-run           # Simulate only
almanak ax swap USDC ETH 100 --slippage 100      # 1% slippage
almanak ax swap USDC ETH 100 --chain base --yes  # Skip confirmation
```

**Options:**

| Option       | Default | Description                                     |
| ------------ | ------- | ----------------------------------------------- |
| `--slippage` | `50`    | Max slippage in basis points (50 = 0.5%)        |
| `--protocol` | auto    | Specific DEX protocol (default: best available) |

### `almanak ax lp-info`

Get details about an existing LP position (range, liquidity, accrued fees, in-range status).

```
almanak ax lp-info 123456                      # View LP position #123456
almanak ax lp-info 123456 --json               # JSON output
almanak ax lp-info 123456 --protocol uniswap_v3
```

### `almanak ax lp-close`

Close (fully withdraw) a liquidity position and collect accrued fees.

```
almanak ax lp-close 123456                     # Close LP #123456
almanak ax lp-close 123456 --dry-run           # Simulate only
almanak ax lp-close 123456 --no-collect-fees   # Skip fee collection
```

**Options:**

| Option              | Default      | Description                  |
| ------------------- | ------------ | ---------------------------- |
| `--protocol`        | `uniswap_v3` | LP protocol                  |
| `--no-collect-fees` | `false`      | Skip collecting accrued fees |

### `almanak ax pool`

Get details about a liquidity pool (current price, tick, liquidity, volume, fees, TVL).

```
almanak ax pool WBTC WETH                      # Pool state
almanak ax pool USDC ETH --fee-tier 500        # 0.05% fee tier
almanak ax pool WBTC WETH --json               # JSON output
```

**Options:**

| Option       | Default      | Description                                        |
| ------------ | ------------ | -------------------------------------------------- |
| `--fee-tier` | `3000`       | Pool fee tier in hundredths of a bip (3000 = 0.3%) |
| `--protocol` | `uniswap_v3` | DEX protocol                                       |

### `almanak ax tools`

List all available tools in the catalog.

```
almanak ax tools                    # List all tools
almanak ax tools --category action  # Only action tools
almanak ax tools --json             # JSON output
```

### `almanak ax run`

Run any tool from the catalog by name. Generic fallback for tools without a dedicated subcommand.

```
almanak ax run get_price '{"token": "ETH"}'
almanak ax run get_balance '{"token": "USDC"}' --json
almanak ax run compile_intent '{"intent_type": "swap", ...}'
```

## Natural Language Mode

Requires LLM API key

Natural language mode uses the same `AGENT_LLM_*` environment variables as the [agentic trading](https://sdk.docs.almanak.co/agentic/index.html) path. Set `AGENT_LLM_API_KEY` before use.

Describe what you want in plain English. The LLM interprets your request into a structured tool call, shows what it understood, then executes through the same pipeline.

```
almanak ax -n "what's the price of ETH?"
almanak ax -n "check my USDC balance on base"
almanak ax -n "swap 5 USDC to WETH on base" --dry-run
almanak ax --natural "open an LP position with 1000 USDC and 0.5 ETH"
```

### How it works

1. Your text is sent to the LLM along with the full tool catalog
1. The LLM returns exactly one tool call (single-shot, not an agent loop)
1. The interpreted action is **always shown** before execution (even with `--yes`)
1. The same safety gate applies -- write actions require confirmation

### Example output

```
$ almanak ax -n "swap about 5 bucks of USDC to WETH on base"

Interpreted as:
  Action:   swap_tokens
  Chain:    base
  Token In: USDC
  Token Out: WETH
  Amount:   5
  Slippage Bps: 50

Execute this transaction? [y/N]
```

### Configuration

| Variable             | Default                     | Description        |
| -------------------- | --------------------------- | ------------------ |
| `AGENT_LLM_API_KEY`  | --                          | API key (required) |
| `AGENT_LLM_BASE_URL` | `https://api.openai.com/v1` | LLM endpoint URL   |
| `AGENT_LLM_MODEL`    | `gpt-4o`                    | Model name         |

Any OpenAI-compatible provider works (OpenAI, Anthropic via proxy, Ollama, etc.).

### Error handling

| Scenario                 | Behavior                                               |
| ------------------------ | ------------------------------------------------------ |
| No `AGENT_LLM_API_KEY`   | Clear error with setup instructions                    |
| LLM unreachable          | Error with structured syntax suggestion as fallback    |
| LLM returns no tool call | Shows what the LLM said and suggests structured syntax |
| LLM returns unknown tool | Error listing available tools                          |

## Safety Model

`almanak ax` enforces a TTY safety matrix for all write actions (swaps, LP, lending):

| Context                         | Behavior                            |
| ------------------------------- | ----------------------------------- |
| Interactive terminal (TTY)      | Simulate, preview, confirm, execute |
| Non-interactive + `--yes`       | Simulate, execute (no prompt)       |
| Non-interactive without `--yes` | Fails with error                    |
| `--dry-run`                     | Simulate only, never submit         |

For natural language mode, the interpreted action is **always displayed** regardless of `--yes`. This is the "Safety Always" guarantee -- you always see what the LLM understood before anything executes.

## Mainnet vs Anvil (Local Testing)

`almanak ax` **auto-starts a gateway** if none is running. Use `--network` to control mainnet vs Anvil:

```
# Auto-start Anvil gateway, swap on local fork (free, safe)
almanak ax --network anvil swap USDC ETH 100

# Auto-start mainnet gateway (real transactions)
almanak ax swap USDC ETH 100

# Or connect to an already-running gateway (skips auto-start)
almanak ax --gateway-port 50051 swap USDC ETH 100
```

Start with Anvil

Always test with `--network anvil` first. Anvil forks mainnet state so balances and prices are real, but transactions are local and free.

The `--chain` flag selects which chain to query/execute on (e.g. `arbitrum`, `base`, `ethereum`). The `--network` flag determines whether those chains resolve to mainnet RPCs or local Anvil forks.

**How auto-start works:**

1. `almanak ax` tries to connect to the gateway at `--gateway-host`:`--gateway-port`
1. If no gateway is running, it starts a `ManagedGateway` in a background thread
1. For `--network anvil`, it also starts an Anvil fork for the selected chain
1. The gateway shuts down automatically when the command finishes

## Workflow Example: Prepare Wallet for Strategy Testing

Real-world scenario: your assets are in a WBTC-WETH LP position on Uniswap V3, but you need 10 USDC for a strategy test.

```
# Step 1: Check your LP position (auto-starts Anvil gateway)
almanak ax --network anvil lp-info 123456 --chain ethereum

# Step 2: Close the LP position (withdraws WBTC + WETH, collects fees)
almanak ax --network anvil lp-close 123456 --chain ethereum

# Step 3: Swap WBTC proceeds to USDC
almanak ax --network anvil swap WBTC USDC 0.0005 --chain ethereum

# Step 4: Swap WETH proceeds to USDC
almanak ax --network anvil swap WETH USDC 0.01 --chain ethereum

# Step 5: Verify you have enough USDC
almanak ax --network anvil balance USDC --chain ethereum
```

Or with natural language mode (single commands, but one at a time):

```
almanak ax --network anvil -n "show me LP position 123456 on ethereum"
almanak ax --network anvil -n "close LP position 123456 on ethereum"
almanak ax --network anvil -n "swap all my WBTC to USDC on ethereum"
almanak ax --network anvil -n "swap all my WETH to USDC on ethereum"
almanak ax --network anvil -n "check my USDC balance on ethereum"
```

Each step shows what will happen and asks for confirmation before executing write actions.

## Structured vs Natural Language

|                   | Structured                     | Natural Language                       |
| ----------------- | ------------------------------ | -------------------------------------- |
| **Syntax**        | `almanak ax swap USDC ETH 100` | `almanak ax -n "swap 100 USDC to ETH"` |
| **LLM required**  | No                             | Yes                                    |
| **Deterministic** | Yes                            | No (LLM interprets)                    |
| **Scriptable**    | Yes                            | Not recommended                        |
| **Latency**       | Instant                        | +1-2s (LLM round-trip)                 |
| **Best for**      | CI/CD, automation, AI agents   | Interactive exploration, humans        |

# almanak dashboard

Start the Almanak Operator Dashboard.

The dashboard provides a web UI for monitoring and managing strategies. It connects to the gateway for all data access.

The gateway must be running before starting the dashboard. Start the gateway first with: `almanak gateway`

## Usage

```
Usage: almanak dashboard [OPTIONS]
```

## Options

- `port`:

  - Type: INT
  - Default: `8501`
  - Env: `DASHBOARD_PORT`
  - Usage: `--port` Streamlit port number (default: 8501).

- `gateway_host`:

  - Type: STRING
  - Default: `localhost`
  - Env: `GATEWAY_HOST`
  - Usage: `--gateway-host` Gateway hostname (default: localhost).

- `gateway_port`:

  - Type: INT
  - Default: `50051`
  - Env: `GATEWAY_PORT`
  - Usage: `--gateway-port` Gateway gRPC port (default: 50051).

- `no_browser`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--no-browser` Don't open browser automatically.

- `help`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--help` Show this message and exit.

## CLI Help

```
Usage: almanak dashboard [OPTIONS]

  Start the Almanak Operator Dashboard.

  The dashboard provides a web UI for monitoring and managing strategies.
  It connects to the gateway for all data access.

  IMPORTANT: A gateway must be running before starting the dashboard.
  Start a standalone gateway with: almanak gateway

  Examples:

      # Start gateway, then dashboard
      almanak gateway &
      almanak dashboard

      # Start dashboard on custom port
      almanak dashboard --port 8502

      # Connect to remote gateway
      almanak dashboard --gateway-host 192.168.1.100 --gateway-port 50051

Options:
  --port INTEGER          Streamlit port number (default: 8501).
  --gateway-host TEXT     Gateway hostname (default: localhost).
  --gateway-port INTEGER  Gateway gRPC port (default: 50051).
  --no-browser            Don't open browser automatically.
  --help                  Show this message and exit.
```

# almanak gateway

Start the Almanak Gateway gRPC server.

The gateway is a sidecar service that mediates all external access for strategy containers. It must be running before any strategy can execute.

## What the Gateway Provides

The gateway exposes gRPC services for:

- **Market data** - prices, balances, technical indicators
- **State persistence** - strategy state load/save
- **Transaction execution** - intent compilation and on-chain execution
- **RPC proxy** - controlled JSON-RPC access to blockchain nodes
- **External integrations** - CoinGecko, Binance, TheGraph, Enso, Polymarket
- **Observability** - logging, alerts, timeline events, metrics

The gateway holds all platform secrets (API keys, private keys, RPC credentials). Strategy containers connect to the gateway via gRPC and have no direct external access.

## Usage

```
Usage: almanak gateway [OPTIONS]
```

## Options

- `port`:

  - Type: INT
  - Default: `50051`
  - Env: `GATEWAY_PORT`
  - Usage: `--port` gRPC port number (default: 50051).

- `network`:

  - Type: Choice
  - Choices: `mainnet`, `anvil`
  - Default: `mainnet`
  - Env: `ALMANAK_GATEWAY_NETWORK`
  - Usage: `--network` Network environment: 'mainnet' for production RPC, 'anvil' for local fork.

- `metrics`:

  - Type: BOOL
  - Default: `True`
  - Env: `GATEWAY_METRICS_ENABLED`
  - Usage: `--metrics` / `--no-metrics` Enable Prometheus metrics endpoint (default: enabled).

- `metrics_port`:

  - Type: INT
  - Default: `9090`
  - Env: `GATEWAY_METRICS_PORT`
  - Usage: `--metrics-port` Prometheus metrics port (default: 9090).

- `log_level`:

  - Type: Choice
  - Choices: `debug`, `info`, `warning`, `error`
  - Default: `info`
  - Usage: `--log-level` Log level.

- `help`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--help` Show this message and exit.

## Environment Variables

The gateway reads additional configuration from environment variables. These are separate from the CLI options above.

### Required (one of these must be set)

| Variable                         | Description                                                                                  |
| -------------------------------- | -------------------------------------------------------------------------------------------- |
| `ALMANAK_GATEWAY_AUTH_TOKEN`     | Shared secret for client authentication. When set, all gRPC clients must provide this token. |
| `ALMANAK_GATEWAY_ALLOW_INSECURE` | Set to `true` to bypass auth requirement (development only).                                 |

### API Keys

| Variable                      | Description                                                                                                                          |
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `ALCHEMY_API_KEY`             | Alchemy API key for RPC access. Optional fallback for blockchain operations (not needed if `RPC_URL` or per-chain RPC URLs are set). |
| `COINGECKO_API_KEY`           | CoinGecko API key. Optional - falls back to free tier (30 req/min).                                                                  |
| `ALMANAK_GATEWAY_PRIVATE_KEY` | Private key for transaction signing. Falls back to `ALMANAK_PRIVATE_KEY` if not set.                                                 |

### Persistence

| Variable                           | Description                             |
| ---------------------------------- | --------------------------------------- |
| `ALMANAK_GATEWAY_DATABASE_URL`     | PostgreSQL URL for state persistence.   |
| `ALMANAK_GATEWAY_TIMELINE_DB_PATH` | SQLite path for timeline event storage. |

### Alerting

| Variable                             | Description                       |
| ------------------------------------ | --------------------------------- |
| `ALMANAK_GATEWAY_SLACK_WEBHOOK_URL`  | Slack webhook for alert delivery. |
| `ALMANAK_GATEWAY_TELEGRAM_BOT_TOKEN` | Telegram bot token for alerts.    |
| `ALMANAK_GATEWAY_TELEGRAM_CHAT_ID`   | Telegram chat ID for alerts.      |

### Audit Logging

| Variable                          | Default | Description                                    |
| --------------------------------- | ------- | ---------------------------------------------- |
| `ALMANAK_GATEWAY_AUDIT_ENABLED`   | `true`  | Enable structured JSON audit logs.             |
| `ALMANAK_GATEWAY_AUDIT_LOG_LEVEL` | `info`  | Audit log level (debug, info, warning, error). |

## CLI Help

```
Usage: almanak gateway [OPTIONS]

  Start the Almanak Gateway gRPC server.

  The gateway is a sidecar service that mediates all external access for
  strategy containers. It provides gRPC services for:

  - Market data (prices, balances, indicators)
  - State persistence
  - Transaction execution
  - RPC proxy to blockchain nodes
  - External integrations (CoinGecko, TheGraph, etc.)

  The gateway holds all platform secrets (API keys, RPC credentials).
  Strategy containers connect to the gateway and have no direct external access.

  Examples:

      # Start gateway with defaults
      almanak gateway

      # Start gateway for Anvil testing
      almanak gateway --network anvil

      # Start gateway on custom port
      almanak gateway --port 50052

Options:
  --port INTEGER          gRPC port number (default: 50051).
  --network [mainnet|anvil]
                          Network environment.
  --metrics / --no-metrics
                          Enable Prometheus metrics endpoint (default: enabled).
  --metrics-port INTEGER  Prometheus metrics port (default: 9090).
  --log-level [debug|info|warning|error]
                          Log level.
  --help                  Show this message and exit.
```

## Examples

```
# Start gateway with defaults (mainnet, port 50051)
almanak gateway

# Start for local Anvil testing
almanak gateway --network anvil

# Custom port with debug logging
almanak gateway --port 50052 --log-level debug

# Disable metrics
almanak gateway --no-metrics

# Set via environment variables
GATEWAY_PORT=50052 ALMANAK_GATEWAY_NETWORK=anvil almanak gateway
```

## Metrics Endpoints

When metrics are enabled (default), the gateway exposes an HTTP server:

| Endpoint       | Description                           |
| -------------- | ------------------------------------- |
| `GET /metrics` | Prometheus metrics in standard format |
| `GET /health`  | Plain text health check ("OK")        |

Default metrics URL: `http://localhost:9090/metrics`

## gRPC Reflection

The gateway supports gRPC reflection for debugging with tools like `grpcurl`:

```
# List available services
grpcurl -plaintext localhost:50051 list

# Describe a service
grpcurl -plaintext localhost:50051 describe almanak.gateway.MarketService

# Call a method
grpcurl -plaintext -d '{"chain": "arbitrum", "token": "ETH"}' \
  localhost:50051 almanak.gateway.MarketService/GetPrice
```

## Typical Development Workflow

```
# Run your strategy (auto-starts a managed gateway in the background)
cd strategies/demo/uniswap_rsi
almanak strat run --once

# Or start a standalone gateway for shared use
almanak gateway --network anvil
```

See also: [Gateway API Reference](https://sdk.docs.almanak.co/gateway/api-reference.html) | [Gateway Troubleshooting](https://sdk.docs.almanak.co/gateway/troubleshooting.html)

# almanak strat

Commands for managing strategies.

## Usage

```
Usage: almanak strat [OPTIONS] COMMAND [ARGS]...
```

## Options

- `help`:
  - Type: BOOL
  - Default: `False`
  - Usage: `--help` Show this message and exit.

## CLI Help

```
Usage: almanak strat [OPTIONS] COMMAND [ARGS]...

  Commands for managing strategies.

Options:
  --help  Show this message and exit.

Commands:
  backtest  Backtesting commands (pnl, sweep, paper, etc.)
  demo      Browse and copy a demo strategy to get started quickly.
  describe  Retrieve the details of a strategy.
  list      List all available strategies on the Almanak platform.
  new       Create a new v2 IntentStrategy from template.
  pull      Downloads your strategy.
  push      Push a new version of your strategy.
  run       Run a strategy from its working directory.
  teardown  Manage strategy teardowns (close all positions safely).
```

## almanak strat demo

Browse and copy a working demo strategy into your directory, ready to run.

### Usage

```
# Interactive arrow-key selection
almanak strat demo

# Copy a specific strategy by name
almanak strat demo --name uniswap_rsi

# Copy into a specific parent directory
almanak strat demo --name aave_borrow --output-dir ./my-strategies

# List available demo strategies
almanak strat demo --list
```

### Options

- `--name`, `-n`: Demo strategy name (skips interactive selection)
- `--output-dir`, `-o`: Parent directory for the copied strategy folder (default: `.`)
- `--list`: List available demo strategies and exit

# almanak

Almanak CLI for managing strategies.

## Usage

```
Usage: almanak [OPTIONS] COMMAND [ARGS]...
```

## Options

- `version`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--version` Show the version and exit.

- `help`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--help` Show this message and exit.

## CLI Help

```
Usage: almanak [OPTIONS] COMMAND [ARGS]...

  Almanak CLI for managing strategies.

Options:
  --version  Show the version and exit.
  --help     Show this message and exit.

Commands:
  auth       Authenticate the Almanak CLI
  ax         Execute DeFi actions directly (swap, balance, price, etc.)
  dashboard  Start the Almanak Operator Dashboard.
  gateway    Start the Almanak Gateway gRPC server.
  strat      Commands for managing strategies.
```

# dashboard test

Test the dashboard locally using Streamlit.

## Usage

```
Usage: almanak dashboard test [OPTIONS]
```

## Arguments

## Options

- `working_dir`:

  - Type: `Path`
  - Default: `.`
  - Usage: `--working-dir` Working directory containing the strategy files. Defaults to the current directory.

- `local_storage`:

  - Type: `Path`
  - Default: `./local_storage`
  - Usage: `--local-storage` Path to the local storage directory for testing.

- `server_port`:

  - Type: INT
  - Default: `8501`
  - Usage: `--server-port` Port to run the Streamlit server on. Defaults to 8501.

- `preset` (REQUIRED):

  - Type: STRING
  - Default: `None`
  - Usage: `--preset` Preset to use for the dashboard.

- `help`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--help` Show this message and exit.

## CLI Help

```
Usage: almanak dashboard test [OPTIONS]

  Test the dashboard locally using Streamlit.

Options:
  --working-dir PATH     Working directory containing the strategy files.
                         Defaults to the current directory.
  --local-storage PATH   Path to the local storage directory for testing.
  --server-port INTEGER  Port to run the Streamlit server on. Defaults to
                         8501.
  --preset TEXT          Preset to use for the dashboard.  [required]
  --help                 Show this message and exit.
```

# strat example

Download the example/tutorial strategies.

## Usage

```
Usage: almanak strat example [OPTIONS]
```

## Arguments

## Options

- `working_dir`:

  - Type: `Path`
  - Default: `.`
  - Usage: `--working-dir` Working directory to download the example strategy. Defaults to the current directory.

- `strategy_name`:

  - Type: Choice(['tutorial_uniswap_swap', 'tutorial_hello_world'])
  - Default: `tutorial_uniswap_swap`
  - Usage: `--strategy-name` The name of the example strategy to download. Defaults to 'tutorial_uniswap_swap'.

- `help`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--help` Show this message and exit.

## CLI Help

```
Usage: almanak strat example [OPTIONS]

  Download the example/tutorial strategies.

Options:
  --working-dir PATH              Working directory to download the example
                                  strategy. Defaults to the current directory.
  --strategy-name [tutorial_uniswap_swap|tutorial_hello_world]
                                  The name of the example strategy to
                                  download. Defaults to
                                  'tutorial_uniswap_swap'.
  --help                          Show this message and exit.
```

# strat new

Create a new v2 IntentStrategy from template.

This creates a self-contained Python project using the v2 intent-based framework with:

- pyproject.toml: Dependencies + [tool.almanak] metadata
- strategy.py: Main strategy with decide() method
- config.json: Runtime configuration
- .venv/ and uv.lock: Per-strategy virtual environment (created by uv sync)
- .gitignore: Git ignore rules
- .python-version: Python version pin (3.12)
- AGENTS.md: Per-strategy AI agent guide
- tests/: Test scaffolding

## Usage

```
Usage: almanak strat new [OPTIONS]
```

## Options

- `name`:

  - Type: STRING
  - Default: `None`
  - Usage: `--name`, `-n` Name for the new strategy. If not provided, will prompt interactively.

- `working_dir`:

  - Type: `Path`
  - Default: `None`
  - Usage: `--working-dir`, `-o` Output directory for the new strategy. Defaults to current directory.

- `template`:

  - Type: Choice
  - Choices: `blank`, `dynamic_lp`, `mean_reversion`, `basis_trade`, `lending_loop`
  - Default: `blank`
  - Usage: `--template`, `-t` Strategy template to use.

- `chain`:

  - Type: Choice
  - Choices: `ethereum`, `arbitrum`, `optimism`, `polygon`, `base`, `avalanche`
  - Default: `arbitrum`
  - Usage: `--chain`, `-c` Target blockchain network.

- `help`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--help` Show this message and exit.

## Templates

| Template         | Description                                 | Protocol   |
| ---------------- | ------------------------------------------- | ---------- |
| `blank`          | Minimal template for custom implementations | custom     |
| `mean_reversion` | RSI-based trading strategy                  | Uniswap V3 |
| `dynamic_lp`     | Volatility-based LP strategy                | Uniswap V3 |
| `basis_trade`    | Spot+perp funding arbitrage                 | GMX V2     |
| `lending_loop`   | Aave/Morpho leverage looping                | Aave V3    |

## CLI Help

```
Usage: almanak strat new [OPTIONS]

  Create a new v2 IntentStrategy from template.

  This creates a self-contained Python project with:
  - pyproject.toml: Dependencies + [tool.almanak] metadata
  - strategy.py: Main strategy with decide() method
  - config.json: Runtime configuration
  - .venv/ and uv.lock: Virtual environment (via uv sync)
  - .gitignore: Git ignore rules
  - .python-version: Python version pin (3.12)
  - AGENTS.md: Per-strategy AI agent guide
  - tests/: Test scaffolding

  Templates:
  - blank: Minimal strategy for custom implementations
  - dynamic_lp: Volatility-based LP strategy
  - mean_reversion: RSI-based trading strategy
  - basis_trade: Spot+perp funding arbitrage
  - lending_loop: Aave/Morpho leverage looping

Options:
  -n, --name TEXT         Name for the new strategy.
  -o, --working-dir PATH  Output directory for the new strategy.
  -t, --template [blank|dynamic_lp|mean_reversion|basis_trade|lending_loop]
                           Strategy template to use (default: blank).
  -c, --chain [ethereum|arbitrum|optimism|polygon|base|avalanche]
                           Target blockchain network (default: arbitrum).
  --help                   Show this message and exit.
```

## Examples

```
# Interactive (prompts for name)
almanak strat new

# Specify all options
almanak strat new --name my_strategy --template mean_reversion --chain arbitrum

# Short form
almanak strat new -n my_lp -t dynamic_lp -c ethereum -o ./strategies/my_lp
```

# strat run

Run a strategy from its working directory.

By default, a managed gateway is auto-started in the background. Use `--no-gateway` to connect to an existing gateway instead.

## Usage

```
Usage: almanak strat run [OPTIONS]
```

## Prerequisites

- Environment variables: `ALMANAK_PRIVATE_KEY` (RPC_URL recommended; free public RPCs used if nothing is set)
- For anvil mode: Anvil is auto-started by the managed gateway (requires Foundry installed)

## Options

- `working_dir`:

  - Type: `Path`
  - Default: `.`
  - Usage: `--working-dir`, `-d` Working directory containing the strategy files. Defaults to the current directory.

- `id`:

  - Type: STRING
  - Default: `None`
  - Usage: `--id` Strategy instance ID to resume a previous run.

- `config_file`:

  - Type: `Path`
  - Default: `None`
  - Usage: `--config`, `-c` Path to strategy config JSON file. Auto-detected from working directory if not provided (looks for config.json, config.yaml, config.yml).

- `once`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--once` Run single iteration then exit.

- `interval`:

  - Type: INT
  - Default: `60`
  - Usage: `--interval`, `-i` Loop interval in seconds (default: 60).

- `dry_run`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--dry-run` Execute decide() but don't submit transactions.

- `verbose`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--verbose`, `-v` Enable verbose output.

- `network`:

  - Type: Choice
  - Choices: `mainnet`, `anvil`
  - Default: `None`
  - Usage: `--network`, `-n` Network environment: 'mainnet' for production RPC, 'anvil' for local fork. When using anvil with a managed gateway, Anvil forks are auto-started for the chains specified in the strategy config.

- `gateway_host`:

  - Type: STRING
  - Default: `localhost`
  - Env: `GATEWAY_HOST`
  - Usage: `--gateway-host` Gateway sidecar hostname.

- `gateway_port`:

  - Type: INT
  - Default: `50051`
  - Env: `GATEWAY_PORT`
  - Usage: `--gateway-port` Gateway sidecar gRPC port.

- `no_gateway`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--no-gateway` Do not auto-start a gateway; connect to an existing one.

- `help`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--help` Show this message and exit.

## CLI Help

```
Usage: almanak strat run [OPTIONS]

  Run a strategy from its working directory.

  By default, a managed gateway is auto-started in the background.
  Use --no-gateway to connect to an existing gateway instead.

  Prerequisites:
      - Environment variables: ALMANAK_PRIVATE_KEY (RPC_URL recommended; public RPCs used if unset)
      - For anvil mode: Foundry installed (Anvil is auto-started)

  Examples:

      # Run from strategy directory
      cd strategies/demo/uniswap_rsi
      almanak strat run --once

      # Run with explicit working directory
      almanak strat run -d strategies/demo/uniswap_rsi --once

      # Run continuously
      almanak strat run --interval 30

      # Dry run (no transactions)
      almanak strat run --dry-run --once

      # Resume a previous run
      almanak strat run --id abc123 --once

Options:
  -d, --working-dir PATH  Working directory containing the strategy files.
                           Defaults to the current directory.
  --id TEXT                Strategy instance ID to resume a previous run.
  -c, --config PATH        Path to strategy config JSON file.
  --once                   Run single iteration then exit.
  -i, --interval INTEGER   Loop interval in seconds (default: 60).
  --dry-run                Execute decide() but don't submit transactions.
  -v, --verbose            Enable verbose output.
  -n, --network [mainnet|anvil]
                           Network environment.
  --gateway-host TEXT      Gateway sidecar hostname.
  --gateway-port INTEGER   Gateway sidecar gRPC port.
  --no-gateway             Do not auto-start a gateway; connect to an existing one.
  --help                   Show this message and exit.
```

# strat teardown

Safely close all positions for a strategy.

The teardown system unwinds positions in a safe order and converts holdings back to stable tokens. Strategies must implement the three teardown methods (`supports_teardown`, `get_open_positions`, `generate_teardown_intents`) for this command to work.

## Usage

```
Usage: almanak strat teardown execute [OPTIONS]
```

## Prerequisites

- A running gateway (auto-started by default, or use an existing one)
- Environment variables: `ALMANAK_PRIVATE_KEY`
- The strategy must implement teardown methods (see [Implementing Teardown](https://sdk.docs.almanak.co/api/strategies.html#implementing-teardown))

## Options

- `working_dir`:

  - Type: `Path`
  - Default: `.`
  - Usage: `--working-dir`, `-d` Working directory containing the strategy files.

- `config_file`:

  - Type: `Path`
  - Default: `None`
  - Usage: `--config`, `-c` Path to strategy config JSON file.

- `mode`:

  - Type: Choice
  - Choices: `graceful`, `emergency`
  - Default: `graceful`
  - Usage: `--mode`, `-m` Teardown mode. `graceful` uses normal slippage tolerance; `emergency` accepts higher slippage (3%) for faster exit.

- `preview`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--preview` Preview what positions will be closed without executing.

- `force`:

  - Type: BOOL
  - Default: `False`
  - Usage: `--force`, `-f` Skip the confirmation prompt.

- `gateway_host`:

  - Type: STRING
  - Default: `localhost`
  - Env: `GATEWAY_HOST`
  - Usage: `--gateway-host` Gateway sidecar hostname.

- `gateway_port`:

  - Type: INT
  - Default: `50051`
  - Env: `GATEWAY_PORT`
  - Usage: `--gateway-port` Gateway sidecar gRPC port.

## Examples

```
# Preview what will be closed (no transactions)
almanak strat teardown execute --preview

# Graceful teardown from strategy directory
cd strategies/demo/uniswap_rsi
almanak strat teardown execute

# Graceful teardown with explicit path
almanak strat teardown execute -d strategies/demo/aerodrome_lp

# Emergency teardown (higher slippage, faster exit)
almanak strat teardown execute -d strategies/demo/aave_borrow --mode emergency

# Skip confirmation prompt
almanak strat teardown execute -d strategies/demo/uniswap_lp --force
```

## How It Works

1. Loads the strategy from its working directory
1. Calls `get_open_positions()` to discover what needs closing
1. Displays a preview of positions and estimated values
1. Calls `generate_teardown_intents(mode)` to build the unwind plan
1. Compiles and executes each intent through the normal execution pipeline

### Position Closing Order

When a strategy holds multiple position types, teardown follows a strict order to avoid liquidation during unwind:

1. **Perps** -- close perpetual positions first (highest risk)
1. **Borrows** -- repay borrows to free collateral
1. **Supplies** -- withdraw supplied collateral
1. **LPs** -- close liquidity positions
1. **Tokens** -- swap remaining tokens to stable

## CLI Help

```
Usage: almanak strat teardown execute [OPTIONS]

  Execute teardown directly from a strategy working directory.

  This command loads a strategy from its working directory and immediately
  executes a teardown to close all open positions. The gateway must be running.

  Examples:

      # Preview what will be closed
      almanak strat teardown execute -d strategies/demo/aerodrome_lp --preview

      # Execute graceful teardown
      almanak strat teardown execute -d strategies/demo/aerodrome_lp

      # Emergency teardown (faster, accepts higher slippage)
      almanak strat teardown execute -d strategies/demo/aave_borrow --mode emergency

      # Skip confirmation
      almanak strat teardown execute -d strategies/demo/uniswap_lp --force

Options:
  -d, --working-dir PATH           Working directory containing the strategy files.
  -c, --config PATH                Path to strategy config JSON file.
  -m, --mode [graceful|emergency]  Teardown mode (default: graceful).
  --preview                        Preview teardown without executing.
  -f, --force                      Skip confirmation prompt.
  --gateway-host TEXT               Gateway sidecar hostname.
  --gateway-port INTEGER            Gateway sidecar gRPC port.
  --help                            Show this message and exit.
```
# Gateway

# Gateway API Reference

This document describes the gRPC API exposed by the Almanak Gateway.

## Services Overview

| Service            | Methods | Description                                                    |
| ------------------ | ------- | -------------------------------------------------------------- |
| Health             | 3       | Standard gRPC health checks and chain registration             |
| MarketService      | 4       | Price data, balances, batch balances, and technical indicators |
| StateService       | 3       | Strategy state persistence with optimistic locking             |
| ExecutionService   | 3       | Intent compilation and transaction execution                   |
| ObserveService     | 4       | Logging, alerts, metrics, and timeline events                  |
| RpcService         | 6       | JSON-RPC proxy to blockchains with typed queries               |
| IntegrationService | 9       | Third-party data (Binance, CoinGecko, TheGraph)                |
| DashboardService   | 10      | Operator dashboard data and actions                            |
| FundingRateService | 2       | Perpetual funding rates and spreads                            |
| SimulationService  | 1       | Transaction bundle simulation (Tenderly/Alchemy)               |
| PolymarketService  | 18      | Polymarket CLOB API proxy (market data, orders, positions)     |
| EnsoService        | 4       | Enso Finance routing and bundling                              |
| TokenService       | 4       | Token resolution and on-chain metadata                         |
| LifecycleService   | 6       | Agent state management, heartbeat, and commands                |

## Health

### Check

Check if the service is healthy (liveness probe).

```
rpc Check(HealthCheckRequest) returns (HealthCheckResponse);
```

### Watch

Watch for health status changes (streaming readiness probe).

```
rpc Watch(HealthCheckRequest) returns (stream HealthCheckResponse);
```

### RegisterChains

Pre-initialize execution orchestrators and compilers for specified chains. Call this at startup to warm up chain-specific resources.

```
rpc RegisterChains(RegisterChainsRequest) returns (RegisterChainsResponse);
```

**Request:**

```
message RegisterChainsRequest {
  repeated string chains = 1;      // Chain names to pre-initialize (e.g., "arbitrum", "base")
  string wallet_address = 2;       // Wallet address for orchestrator initialization
}
```

**Response:**

```
message RegisterChainsResponse {
  bool success = 1;
  repeated string initialized_chains = 2;  // Chains successfully initialized
  string wallet_address = 3;               // Wallet address derived from gateway private key
  string error = 4;
  map<string, string> chain_wallets = 5;   // Per-chain wallet addresses resolved from wallet registry
}
```

## MarketService

### GetPrice

Get the current price of a token.

```
rpc GetPrice(PriceRequest) returns (PriceResponse)
```

**Request:**

```
message PriceRequest {
  string token = 1;      // Token symbol or address
  string quote = 2;      // Quote currency (default: "USD")
}
```

**Response:**

```
message PriceResponse {
  string price = 1;          // Decimal as string (precision preserved)
  int64 timestamp = 2;
  string source = 3;
  double confidence = 4;     // 0.0-1.0
  bool stale = 5;
}
```

**Example:**

```
from almanak.framework.data.price import GatewayPriceOracle

oracle = GatewayPriceOracle(gateway_client)
price = await oracle.get_price("ETH", "USD")
```

### GetBalance

Get token balance for a wallet.

```
rpc GetBalance(BalanceRequest) returns (BalanceResponse)
```

**Request:**

```
message BalanceRequest {
  string token = 1;            // Token symbol or address
  string chain = 2;
  string wallet_address = 3;
}
```

**Response:**

```
message BalanceResponse {
  string balance = 1;          // Human-readable units as string
  string balance_usd = 2;
  string address = 3;
  int32 decimals = 4;
  string raw_balance = 5;      // Wei/raw units as string
  int64 timestamp = 6;
  bool stale = 7;
  string error = 8;
}
```

### BatchGetBalances

Get token balances across multiple chains in a single call.

```
rpc BatchGetBalances(BatchBalanceRequest) returns (BatchBalanceResponse)
```

**Request:**

```
message BatchBalanceRequest {
  repeated BalanceRequest requests = 1;
}
```

**Response:**

```
message BatchBalanceResponse {
  repeated BalanceResponse responses = 1;
}
```

### GetIndicator

Calculate a technical indicator.

```
rpc GetIndicator(IndicatorRequest) returns (IndicatorResponse)
```

## StateService

### LoadState

Load strategy state from storage.

```
rpc LoadState(LoadStateRequest) returns (StateData)
```

**Request:**

```
message LoadStateRequest {
  string strategy_id = 1;
}
```

**Response:**

```
message StateData {
  string strategy_id = 1;
  int64 version = 2;
  bytes data = 3;              // JSON-serialized state
  int32 schema_version = 4;
  string checksum = 5;         // SHA-256 hex
  int64 created_at = 6;
  int64 updated_at = 7;
  string loaded_from = 8;      // "hot", "warm"
}
```

### SaveState

Save strategy state to storage.

```
rpc SaveState(SaveStateRequest) returns (SaveStateResponse)
```

**Request:**

```
message SaveStateRequest {
  string strategy_id = 1;
  int64 expected_version = 2;  // For optimistic locking (0 = new state)
  bytes data = 3;              // JSON-serialized state
  int32 schema_version = 4;
}
```

**Response:**

```
message SaveStateResponse {
  bool success = 1;
  int64 new_version = 2;
  string error = 3;
  string checksum = 4;
}
```

### DeleteState

Delete strategy state.

```
rpc DeleteState(DeleteStateRequest) returns (DeleteStateResponse)
```

## ExecutionService

### CompileIntent

Compile a strategy intent into an action bundle.

```
rpc CompileIntent(CompileIntentRequest) returns (CompilationResult)
```

**Request:**

```
message CompileIntentRequest {
  string intent_type = 1;          // Case-insensitive with aliases: "swap", "lp_open", etc.
  bytes intent_data = 2;           // JSON-serialized intent
  string chain = 3;
  string wallet_address = 4;
  map<string, string> price_map = 5;  // Token symbol -> USD price string (empty = use placeholder prices)
}
```

**Response:**

```
message CompilationResult {
  bool success = 1;
  bytes action_bundle = 2;     // JSON-serialized ActionBundle
  string error = 3;
  string error_code = 4;       // Structured error code
}
```

### Execute

Execute an action bundle (sign, submit, confirm).

```
rpc Execute(ExecuteRequest) returns (ExecutionResult)
```

### GetTransactionStatus

Get the status of a submitted transaction.

```
rpc GetTransactionStatus(TxStatusRequest) returns (TxStatus)
```

## ObserveService

### Log

Send log entries to the platform.

```
rpc Log(LogEntry) returns (Empty)
```

### Alert

Send an alert to configured channels (Slack, Telegram).

```
rpc Alert(AlertRequest) returns (AlertResponse)
```

**Request:**

```
message AlertRequest {
  string severity = 1;     // info, warning, error, critical
  string title = 2;
  string message = 3;
  string strategy_id = 4;
}
```

### RecordMetric

Record a custom metric.

```
rpc RecordMetric(MetricEntry) returns (Empty)
```

### RecordTimelineEvent

Record a timeline event for a strategy (trades, rebalances, errors, state changes).

```
rpc RecordTimelineEvent(RecordTimelineEventRequest) returns (RecordTimelineEventResponse)
```

**Request:**

```
message RecordTimelineEventRequest {
  string strategy_id = 1;
  string event_type = 2;       // "TRADE", "REBALANCE", "ERROR", "STATE_CHANGE", etc.
  string description = 3;
  string tx_hash = 4;          // Optional: transaction hash
  string chain = 5;            // Optional: chain name
  string details_json = 6;     // Optional: JSON-encoded details
  int64 timestamp = 7;         // Optional: uses server time if 0
}
```

**Response:**

```
message RecordTimelineEventResponse {
  bool success = 1;
  string event_id = 2;
  string error = 3;
}
```

## RpcService

### Call

Make a single JSON-RPC call to a blockchain.

```
rpc Call(RpcRequest) returns (RpcResponse)
```

**Request:**

```
message RpcRequest {
  string chain = 1;        // Must be in allowed list
  string method = 2;       // Must be in allowed list
  string params = 3;       // JSON-encoded params
  string id = 4;
}
```

**Response:**

```
message RpcResponse {
  bool success = 1;
  string result = 2;       // JSON-encoded result
  string error = 3;        // JSON-encoded error
  string id = 4;
}
```

**Allowed Chains:**

EVM chains:

- ethereum, arbitrum, base, optimism, polygon, avalanche, bsc, bnb, sonic, plasma, linea, blast, mantle, berachain, monad

Non-EVM chains:

- solana

**Allowed Methods (EVM):**

- `eth_call`
- `eth_getBalance`
- `eth_getTransactionCount`
- `eth_getTransactionReceipt`
- `eth_getBlockByNumber`
- `eth_getBlockByHash`
- `eth_blockNumber`
- `eth_chainId`
- `eth_gasPrice`
- `eth_estimateGas`
- `eth_getLogs`
- `eth_getCode`
- `eth_getStorageAt`
- `eth_sendRawTransaction`
- `net_version`

**Allowed Methods (Solana):**

- `getBalance`
- `getTokenAccountsByOwner`
- `getTokenAccountBalance`
- `getTransaction`
- `getSignaturesForAddress`
- `getAccountInfo`
- `getMultipleAccounts`
- `getLatestBlockhash`
- `getSlot`
- `getBlockHeight`
- `getEpochInfo`
- `getMinimumBalanceForRentExemption`
- `sendTransaction`
- `simulateTransaction`
- `getRecentPrioritizationFees`
- `isBlockhashValid`

**Blocked Methods:**

- `debug_*` - Debugging methods
- `admin_*` - Admin methods
- `personal_*` - Personal key management
- `miner_*` - Mining control
- `txpool_*` - Transaction pool access

### BatchCall

Make multiple JSON-RPC calls in parallel.

```
rpc BatchCall(RpcBatchRequest) returns (RpcBatchResponse)
```

**Request:**

```
message RpcBatchRequest {
  string chain = 1;
  repeated RpcRequest requests = 2;  // Max 100
}
```

### QueryAllowance

Query ERC-20 token allowance (typed convenience method).

```
rpc QueryAllowance(AllowanceRequest) returns (AllowanceResponse)
```

**Solana:** Returns `allowance = MAX_UINT64` and `success = true`. SPL tokens don't use ERC-20-style allowances.

### QueryBalance

Query token balance (typed convenience method).

```
rpc QueryBalance(BalanceQueryRequest) returns (BalanceQueryResponse)
```

**Solana:** Returns an error directing callers to use `MarketService.GetBalance()` instead, which routes to the Solana-native balance provider.

### QueryPositionLiquidity

Query LP position liquidity (typed convenience method).

```
rpc QueryPositionLiquidity(PositionLiquidityRequest) returns (PositionLiquidityResponse)
```

**Solana:** Returns "not applicable for Solana". Solana LP positions use different on-chain structures.

### QueryPositionTokensOwed

Query tokens owed to an LP position (typed convenience method).

```
rpc QueryPositionTokensOwed(PositionTokensOwedRequest) returns (PositionTokensOwedResponse)
```

**Solana:** Returns "not applicable for Solana".

## IntegrationService

### BinanceGetTicker

Get 24-hour ticker data from Binance.

```
rpc BinanceGetTicker(BinanceTickerRequest) returns (BinanceTickerResponse)
```

**Request:**

```
message BinanceTickerRequest {
  string symbol = 1;       // e.g., "BTCUSDT"
}
```

**Response:**

```
message BinanceTickerResponse {
  string symbol = 1;
  string price = 2;
  string price_change = 3;
  string price_change_percent = 4;
  string high_24h = 5;
  string low_24h = 6;
  string volume_24h = 7;
  string quote_volume_24h = 8;
  int64 timestamp = 9;
}
```

### BinanceGetKlines

Get candlestick/kline data from Binance.

```
rpc BinanceGetKlines(BinanceKlinesRequest) returns (BinanceKlinesResponse)
```

**Request:**

```
message BinanceKlinesRequest {
  string symbol = 1;
  string interval = 2;     // 1m, 5m, 15m, 1h, 4h, 1d, etc.
  int32 limit = 3;         // Max 1000
  int64 start_time = 4;
  int64 end_time = 5;
}
```

### BinanceGetOrderBook

Get order book depth from Binance.

```
rpc BinanceGetOrderBook(BinanceOrderBookRequest) returns (BinanceOrderBookResponse)
```

### CoinGeckoGetPrice

Get token price from CoinGecko.

```
rpc CoinGeckoGetPrice(CoinGeckoGetPriceRequest) returns (CoinGeckoGetPriceResponse)
```

**Request:**

```
message CoinGeckoGetPriceRequest {
  string token_id = 1;     // e.g., "bitcoin", "ethereum"
  repeated string vs_currencies = 2;  // e.g., ["usd", "eur"]
}
```

### CoinGeckoGetPrices

Get prices for multiple tokens.

```
rpc CoinGeckoGetPrices(CoinGeckoGetPricesRequest) returns (CoinGeckoGetPricesResponse)
```

### CoinGeckoGetMarkets

Get market data with rankings.

```
rpc CoinGeckoGetMarkets(CoinGeckoGetMarketsRequest) returns (CoinGeckoGetMarketsResponse)
```

### TheGraphQuery

Execute a GraphQL query on a subgraph.

```
rpc TheGraphQuery(TheGraphQueryRequest) returns (TheGraphQueryResponse)
```

**Request:**

```
message TheGraphQueryRequest {
  string subgraph_id = 1;  // e.g., "uniswap-v3-arbitrum"
  string query = 2;        // GraphQL query (max 10KB)
  string variables = 3;    // JSON-encoded variables
}
```

**Note:** Introspection queries (`__schema`, `__type`) are blocked.

### CoinGeckoGetHistoricalPrice

Get historical price at a specific date.

```
rpc CoinGeckoGetHistoricalPrice(CoinGeckoHistoricalPriceRequest) returns (CoinGeckoHistoricalPriceResponse)
```

### CoinGeckoGetMarketChartRange

Get price chart data for a date range.

```
rpc CoinGeckoGetMarketChartRange(CoinGeckoMarketChartRangeRequest) returns (CoinGeckoMarketChartRangeResponse)
```

## DashboardService

Provides data and actions for the operator dashboard.

### ListStrategies

List strategies with optional filters.

```
rpc ListStrategies(ListStrategiesRequest) returns (ListStrategiesResponse)
```

**Request:**

```
message ListStrategiesRequest {
  string status_filter = 1;
  string chain_filter = 2;
  bool include_position = 3;
}
```

### GetStrategyDetails

Get detailed information about a strategy including timeline and PnL history.

```
rpc GetStrategyDetails(GetStrategyDetailsRequest) returns (StrategyDetails)
```

**Request:**

```
message GetStrategyDetailsRequest {
  string strategy_id = 1;
  bool include_timeline = 2;
  bool include_pnl_history = 3;
  int32 timeline_limit = 4;
}
```

### GetTimeline

Get timeline events for a strategy.

```
rpc GetTimeline(GetTimelineRequest) returns (GetTimelineResponse)
```

### GetStrategyConfig

Get strategy configuration.

```
rpc GetStrategyConfig(GetStrategyConfigRequest) returns (StrategyConfigResponse)
```

### GetStrategyState

Get strategy state.

```
rpc GetStrategyState(GetStrategyStateRequest) returns (StrategyStateResponse)
```

### ExecuteAction

Execute an operator action on a strategy.

```
rpc ExecuteAction(ExecuteActionRequest) returns (ExecuteActionResponse)
```

**Request:**

```
message ExecuteActionRequest {
  string strategy_id = 1;
  string action = 2;       // PAUSE, RESUME, BUMP_GAS, CANCEL_TX, EMERGENCY_UNWIND
  string reason = 3;
  map<string, string> params = 4;
}
```

### RegisterStrategyInstance

Register a new strategy instance in the persistent registry.

```
rpc RegisterStrategyInstance(RegisterInstanceRequest) returns (RegisterInstanceResponse)
```

### UpdateStrategyInstanceStatus

Update the status of a registered strategy instance.

```
rpc UpdateStrategyInstanceStatus(UpdateInstanceStatusRequest) returns (UpdateInstanceStatusResponse)
```

### ArchiveStrategyInstance

Archive a strategy instance (soft delete).

```
rpc ArchiveStrategyInstance(ArchiveInstanceRequest) returns (ArchiveInstanceResponse)
```

### PurgeStrategyInstance

Permanently remove a strategy instance from the registry.

```
rpc PurgeStrategyInstance(PurgeInstanceRequest) returns (PurgeInstanceResponse)
```

## FundingRateService

Provides perpetual funding rate data from venues like GMX V2 and Hyperliquid.

### GetFundingRate

Get current funding rate for a market on a specific venue.

```
rpc GetFundingRate(FundingRateRequest) returns (FundingRateResponse)
```

**Request:**

```
message FundingRateRequest {
  string venue = 1;        // gmx_v2, hyperliquid
  string market = 2;       // e.g., ETH-USD, BTC-USD
  string chain = 3;
}
```

**Response:**

```
message FundingRateResponse {
  string venue = 1;
  string market = 2;
  string rate_hourly = 3;
  string rate_8h = 4;
  string rate_annualized = 5;
  int64 next_funding_time = 6;
  string open_interest_long = 7;
  string open_interest_short = 8;
  string mark_price = 9;
  string index_price = 10;
  bool is_live_data = 11;
  bool success = 12;
  string error = 13;
}
```

### GetFundingRateSpread

Get the funding rate spread between two venues.

```
rpc GetFundingRateSpread(FundingRateSpreadRequest) returns (FundingRateSpreadResponse)
```

**Request:**

```
message FundingRateSpreadRequest {
  string market = 1;
  string venue_a = 2;
  string venue_b = 3;
  string chain = 4;
}
```

## SimulationService

Simulate transaction bundles before execution using Tenderly or Alchemy.

### SimulateBundle

```
rpc SimulateBundle(SimulateBundleRequest) returns (SimulateBundleResponse)
```

**Request:**

```
message SimulateBundleRequest {
  string chain = 1;
  repeated SimulateTransaction transactions = 2;
  repeated SimulateStateOverride state_overrides = 3;
  string simulator = 4;   // "tenderly", "alchemy", or empty for auto-select
}
```

**Response:**

```
message SimulateBundleResponse {
  bool success = 1;
  bool simulated = 2;
  repeated int64 gas_estimates = 3;
  string revert_reason = 4;
  repeated string warnings = 5;
  string simulation_url = 6;
  string simulator_used = 7;
  string error = 8;
}
```

## PolymarketService

Proxy for the Polymarket CLOB API. Provides market data, order management, and position tracking.

### Market Data Methods

| Method                 | Description                       |
| ---------------------- | --------------------------------- |
| `GetMarket`            | Get details for a single market   |
| `GetMarkets`           | Get multiple markets with filters |
| `GetSimplifiedMarkets` | Get simplified market summaries   |
| `GetOrderBook`         | Get order book for a token        |
| `GetMidpoint`          | Get midpoint price                |
| `GetPrice`             | Get current price                 |
| `GetSpread`            | Get bid-ask spread                |
| `GetTickSize`          | Get minimum tick size             |

### Order Management Methods

| Method                     | Description                      |
| -------------------------- | -------------------------------- |
| `CreateAndPostOrder`       | Create and submit a limit order  |
| `CreateAndPostMarketOrder` | Create and submit a market order |
| `CancelOrder`              | Cancel a single order            |
| `CancelOrders`             | Cancel multiple orders           |
| `CancelAll`                | Cancel all open orders           |

### Position and History Methods

| Method                | Description               |
| --------------------- | ------------------------- |
| `GetPositions`        | Get current positions     |
| `GetOpenOrders`       | Get open orders           |
| `GetTradesHistory`    | Get trade history         |
| `GetOrder`            | Get order details         |
| `GetBalanceAllowance` | Get balance and allowance |

## EnsoService

Proxy for Enso Finance routing and bundling, supporting cross-chain swaps.

### GetRoute

Get an optimized swap route.

```
rpc GetRoute(EnsoRouteRequest) returns (EnsoRouteResponse)
```

**Request:**

```
message EnsoRouteRequest {
  string chain = 1;
  string token_in = 2;
  string token_out = 3;
  string amount_in = 4;
  string from_address = 5;
  string receiver = 6;
  int32 slippage_bps = 7;
  string routing_strategy = 8;
  int32 max_price_impact_bps = 9;
  int32 destination_chain_id = 10;
  string refund_receiver = 11;
}
```

### GetQuote

Get a price quote without generating calldata.

```
rpc GetQuote(EnsoQuoteRequest) returns (EnsoQuoteResponse)
```

### GetApproval

Get the approval transaction for a token.

```
rpc GetApproval(EnsoApprovalRequest) returns (EnsoApprovalResponse)
```

### GetBundle

Bundle multiple DeFi actions into a single transaction.

```
rpc GetBundle(EnsoBundleRequest) returns (EnsoBundleResponse)
```

## TokenService

Provides token resolution and on-chain metadata lookups.

### ResolveToken

Resolve a token by symbol or address to get its full metadata.

```
rpc ResolveToken(ResolveTokenRequest) returns (TokenMetadataResponse)
```

### GetTokenMetadata

Get metadata for a token by address.

```
rpc GetTokenMetadata(GetTokenMetadataRequest) returns (TokenMetadataResponse)
```

### GetTokenDecimals

Get the decimal precision for a token.

```
rpc GetTokenDecimals(GetTokenDecimalsRequest) returns (GetTokenDecimalsResponse)
```

### BatchResolveTokens

Resolve multiple tokens in a single call.

```
rpc BatchResolveTokens(BatchResolveTokensRequest) returns (BatchResolveTokensResponse)
```

## LifecycleService

Agent state management and command dispatch for V2 deployments.

### WriteState

Write the current agent state (INITIALIZING, RUNNING, PAUSED, ERROR, STOPPING, TERMINATED).

```
rpc WriteState(WriteAgentStateRequest) returns (WriteAgentStateResponse)
```

### ReadState

Read the current agent state.

```
rpc ReadState(ReadAgentStateRequest) returns (ReadAgentStateResponse)
```

### Heartbeat

Send a heartbeat to update the last activity timestamp and increment the iteration count.

```
rpc Heartbeat(HeartbeatRequest) returns (HeartbeatResponse)
```

### ReadCommand

Read the most recent unprocessed command for an agent (PAUSE, RESUME, STOP).

```
rpc ReadCommand(ReadAgentCommandRequest) returns (ReadAgentCommandResponse)
```

### AckCommand

Acknowledge (mark processed) a command.

```
rpc AckCommand(AckAgentCommandRequest) returns (AckAgentCommandResponse)
```

### WriteCommand

Write a command to an agent.

```
rpc WriteCommand(WriteAgentCommandRequest) returns (WriteAgentCommandResponse)
```

## Error Codes

| gRPC Code           | HTTP | Description                     |
| ------------------- | ---- | ------------------------------- |
| INVALID_ARGUMENT    | 400  | Invalid request parameters      |
| NOT_FOUND           | 404  | Resource not found              |
| PERMISSION_DENIED   | 403  | Operation not allowed           |
| RESOURCE_EXHAUSTED  | 429  | Rate limit exceeded             |
| FAILED_PRECONDITION | 412  | Chain not configured            |
| INTERNAL            | 500  | Internal server error           |
| UNAVAILABLE         | 503  | Service temporarily unavailable |

## Rate Limits

| Service                      | Limit                  |
| ---------------------------- | ---------------------- |
| RpcService (EVM chains)      | 300 req/min per chain  |
| RpcService (Solana)          | 100 req/min            |
| IntegrationService.Binance   | 1200 req/min           |
| IntegrationService.CoinGecko | 50 req/min (free tier) |
| IntegrationService.TheGraph  | 100 req/min            |

# Gateway Troubleshooting Guide

This guide helps diagnose and resolve common issues with the Gateway Security Architecture.

## Quick Diagnostics

### Check Gateway Health

```
# Docker
docker-compose -f deploy/docker/docker-compose.yml exec gateway \
  grpc_health_probe -addr=:50051

# Kubernetes
kubectl exec deploy/almanak-gateway -c gateway -- \
  grpc_health_probe -addr=:50051
```

Expected output: `status: SERVING`

### Check Container Logs

```
# Gateway logs
docker-compose logs gateway

# Strategy logs
docker-compose logs strategy

# Kubernetes
kubectl logs deploy/almanak-gateway -c gateway
kubectl logs deploy/almanak-gateway -c strategy
```

### Check Network Connectivity

From the strategy container:

```
# Test gateway connectivity
docker-compose exec strategy \
  python -c "import socket; print(socket.gethostbyname('gateway'))"

# Should return internal IP (172.x.x.x or 10.x.x.x)
```

## Common Issues

### Issue: Strategy Cannot Connect to Gateway

**Symptoms:**

- `connection refused` errors
- `failed to connect to gateway` logs
- Strategy starts but immediately fails

**Causes & Solutions:**

1. **Gateway not ready**

   ```
   # Check gateway status
   docker-compose ps gateway

   # Wait for health check
   docker-compose up -d --wait
   ```

1. **Wrong gateway address**

   ```
   # Check environment variables in strategy
   import os
   print(os.environ.get("GATEWAY_HOST"))  # Should be "gateway" or "localhost"
   print(os.environ.get("GATEWAY_PORT"))  # Should be "50051"
   ```

1. **Network misconfiguration**

   ```
   # Verify both containers are on same network
   docker network inspect deploy_internal
   ```

### Issue: "Method Not Allowed" for RPC Calls

**Symptoms:**

- `ValidationError: method 'debug_traceTransaction' is not allowed`
- RPC calls fail with permission errors

**Cause:** The RPC method is blocked by the gateway allowlist.

**Solution:**

Only these methods are allowed:

- `eth_call`, `eth_getBalance`, `eth_getTransactionCount`
- `eth_getTransactionReceipt`, `eth_getBlockByNumber`, `eth_getBlockByHash`
- `eth_blockNumber`, `eth_chainId`, `eth_gasPrice`, `eth_estimateGas`
- `eth_getLogs`, `eth_getCode`, `eth_getStorageAt`
- `eth_sendRawTransaction`, `net_version`

If you need a blocked method, contact support to discuss alternatives.

### Issue: "Chain Not Configured"

**Symptoms:**

- `Chain 'xyz' is not configured`
- RPC calls fail with precondition error

**Cause:** The chain is not in the allowed list or not configured.

**Solution:**

Allowed chains: `ethereum`, `arbitrum`, `base`, `optimism`, `polygon`, `avalanche`, `bsc`, `bnb`, `sonic`, `plasma`, `linea`, `blast`, `mantle`, `berachain`, `monad`, `solana`

```
# Verify RPC source is configured in gateway
docker-compose exec gateway env | grep -E 'RPC_URL|ALCHEMY'
```

### Issue: Rate Limiting

**Symptoms:**

- `Rate limited, retry after X seconds`
- `RESOURCE_EXHAUSTED` gRPC status

**Cause:** Too many requests to the gateway.

**Solutions:**

1. **Implement caching**

   ```
   from functools import lru_cache
   import time

   @lru_cache(maxsize=100)
   def cached_price(token, timestamp_minute):
       return gateway.get_price(token)

   # Use with minute-level cache key
   price = cached_price("ETH", int(time.time() / 60))
   ```

1. **Batch requests**

   ```
   # Instead of multiple single calls
   prices = gateway.get_prices(["ETH", "BTC", "USDC"])
   ```

1. **Reduce polling frequency**

### Issue: State Not Persisting

**Symptoms:**

- State saved but not retrieved
- Different state between restarts

**Causes & Solutions:**

1. **Wrong strategy_id**

   ```
   # Ensure consistent strategy_id
   state = GatewayStateManager(gateway_client)

   # Save
   await state.save(strategy_id="my-strategy", data=data)

   # Load with same ID
   data = await state.load(strategy_id="my-strategy")  # Same ID!
   ```

1. **State size limit exceeded**

   ```
   # Max state size is 1MB
   import sys
   print(sys.getsizeof(data))  # Check size
   ```

1. **Database not configured**

   ```
   # Verify DATABASE_URL in gateway
   docker-compose exec gateway env | grep DATABASE_URL
   ```

### Issue: External Network Access Blocked

**Symptoms:**

- `socket.gaierror: [Errno -2] Name or service not known`
- `ConnectionError: No route to host`
- HTTP requests fail with timeout

**Cause:** This is expected behavior! Strategy containers have no internet access.

**Solution:**

Use gateway-provided services instead:

```
# Instead of direct HTTP
# requests.get("https://api.coingecko.com/...")

# Use gateway integration
from almanak.framework.integrations import coingecko
prices = await coingecko.get_price("ethereum")
```

### Issue: Container Security Errors

**Symptoms:**

- `Operation not permitted`
- `Read-only file system`
- `Permission denied`

**Cause:** Security hardening is working correctly.

**Solutions:**

1. **Writing files**

   ```
   # Only /tmp is writable
   with open("/tmp/cache.json", "w") as f:
       f.write(data)

   # NOT /app/data.json (read-only)
   ```

1. **Installing packages at runtime**

   ```
   # Not allowed - install in Dockerfile instead
   # pip install some-package  # Will fail
   ```

### Issue: Metrics Not Available

**Symptoms:**

- `/metrics` endpoint returns 404
- Prometheus can't scrape metrics

**Causes & Solutions:**

1. **Metrics disabled**

   ```
   # Check values.yaml or environment
   gateway:
     metrics:
       enabled: true
   ```

1. **Wrong port**

   ```
   # Default metrics port is 9090
   curl http://gateway:9090/metrics
   ```

1. **Network policy blocking**

   ```
   # Ensure monitoring namespace can reach metrics
   networkPolicy:
     allowMetricsScraping: true
   ```

## Debug Tools

### gRPC Reflection

The gateway supports gRPC reflection for debugging:

```
# List services
grpcurl -plaintext localhost:50051 list

# Describe a service
grpcurl -plaintext localhost:50051 describe almanak.gateway.MarketService

# Call a method
grpcurl -plaintext -d '{"chain": "arbitrum", "token": "ETH"}' \
  localhost:50051 almanak.gateway.MarketService/GetPrice
```

### Audit Logs

Gateway operations are logged in JSON format:

```
# View audit logs
docker-compose logs gateway | grep gateway_request

# Parse with jq
docker-compose logs gateway 2>&1 | grep gateway_request | \
  tail -1 | jq -r '.timestamp, .service, .method, .latency_ms'
```

### Network Debugging

Test network isolation from inside the strategy container:

```
# Enter strategy container
docker-compose exec strategy /bin/sh

# Test DNS (should fail for external domains)
nslookup google.com

# Test connectivity (should fail)
ping -c 1 8.8.8.8

# Test gateway (should succeed)
nslookup gateway
nc -zv gateway 50051
```

### Health Endpoints

```
# Gateway gRPC health
grpc_health_probe -addr=:50051

# Gateway HTTP health
curl http://gateway:9090/health

# Gateway metrics
curl http://gateway:9090/metrics
```

## Getting Help

If you're still having issues:

1. **Check logs** for specific error messages
1. **Verify configuration** against the documentation
1. **Run diagnostics** using the commands above
1. **Contact support** with:
1. Error messages from logs
1. Steps to reproduce
1. Gateway and strategy versions
1. Configuration (sanitized of secrets)
# API Reference

# API Reference

This section documents the public Python API of the Almanak SDK.

## Import Cheat Sheet

```
# Top-level exports (most common)
from almanak import (
    Almanak,
    Chain, Network, Protocol, ActionType,
    IntentStrategy, MarketSnapshot,
    SwapIntent, HoldIntent, LPOpenIntent, LPCloseIntent,
    BorrowIntent, RepayIntent,
    StateManager, RiskGuard,
    BacktestMetrics, BacktestResult,
)

# Deep imports for specific functionality
from almanak.framework.strategies import IntentStrategy, StrategyBase
from almanak.framework.intents import SwapIntent, IntentCompiler
from almanak.framework.state import StateManager
from almanak.framework.execution import ExecutionOrchestrator
from almanak.framework.data import MarketSnapshot
from almanak.framework.data.tokens import get_token_resolver, TokenResolver

# Backtesting
from almanak.framework.backtesting import PnLBacktester, PnLBacktestConfig

# Logging
from almanak.framework.utils.logging import configure_logging, get_logger
```

## Module Overview

| Module                                                              | Description                                                      |
| ------------------------------------------------------------------- | ---------------------------------------------------------------- |
| [Enums](https://sdk.docs.almanak.co/api/enums.html)                 | `Chain`, `Network`, `Protocol`, `ActionType`, and more           |
| [Strategies](https://sdk.docs.almanak.co/api/strategies.html)       | `IntentStrategy`, `StrategyBase`, `MarketSnapshot`               |
| [Intents](https://sdk.docs.almanak.co/api/intents.html)             | `SwapIntent`, `LPOpenIntent`, `HoldIntent`, and all intent types |
| [Compiler](https://sdk.docs.almanak.co/api/compiler.html)           | `IntentCompiler` - compiles intents to transactions              |
| [State](https://sdk.docs.almanak.co/api/state.html)                 | `StateManager` - persistence and migrations                      |
| [Execution](https://sdk.docs.almanak.co/api/execution.html)         | `ExecutionOrchestrator` - transaction execution pipeline         |
| [Data](https://sdk.docs.almanak.co/api/data.html)                   | `PriceOracle`, `BalanceProvider`, `MarketSnapshot`               |
| [Tokens](https://sdk.docs.almanak.co/api/tokens.html)               | `TokenResolver` - unified token resolution                       |
| [Indicators](https://sdk.docs.almanak.co/api/indicators.html)       | RSI, MACD, Bollinger Bands, and more                             |
| [Connectors](https://sdk.docs.almanak.co/api/connectors/index.html) | Protocol adapters (Uniswap, Aave, Morpho, etc.)                  |
| [Services](https://sdk.docs.almanak.co/api/services.html)           | `StuckDetector`, `EmergencyManager`                              |
| [Alerting](https://sdk.docs.almanak.co/api/alerting.html)           | `AlertManager`, Slack/Telegram channels                          |
| [Backtesting](https://sdk.docs.almanak.co/api/backtesting.html)     | `PnLBacktester`, `PaperTrader`, parameter sweeps                 |
| [Deployment](https://sdk.docs.almanak.co/api/deployment.html)       | `CanaryDeployment`                                               |
| [Logging](https://sdk.docs.almanak.co/api/logging.html)             | `configure_logging`, `get_logger`                                |

# Alerting

Alert management with Slack and Telegram channels, cooldown tracking, and escalation policies.

## AlertManager

## almanak.framework.alerting.AlertManager

```
AlertManager(
    config: AlertConfig,
    telegram_bot_token: str | None = None,
    slack_webhook_url: str | None = None,
    slack_enable_threading: bool = True,
)
```

Manages alert routing and delivery to configured channels.

The AlertManager is responsible for:

- Evaluating alert rules against incoming events
- Routing alerts to the appropriate channels (Telegram, Slack, etc.)
- Applying cooldown to prevent spam
- Respecting quiet hours
- Logging all sent alerts

Attributes:

| Name               | Type              | Description                      |
| ------------------ | ----------------- | -------------------------------- |
| `config`           |                   | The AlertConfig for this manager |
| `telegram_channel` | \`TelegramChannel | None\`                           |
| `slack_channel`    | \`SlackChannel    | None\`                           |
| `cooldown_tracker` |                   | Tracks cooldown state            |

Initialize the AlertManager.

Parameters:

| Name                     | Type          | Description                                           | Default                                                |
| ------------------------ | ------------- | ----------------------------------------------------- | ------------------------------------------------------ |
| `config`                 | `AlertConfig` | The AlertConfig with channel configurations and rules | *required*                                             |
| `telegram_bot_token`     | \`str         | None\`                                                | Bot token for Telegram (required if using Telegram)    |
| `slack_webhook_url`      | \`str         | None\`                                                | Webhook URL for Slack (overrides config.slack_webhook) |
| `slack_enable_threading` | `bool`        | Whether to enable threading for Slack alerts          | `True`                                                 |

### telegram_channel

```
telegram_channel: TelegramChannel | None
```

Get the Telegram channel if configured.

### slack_channel

```
slack_channel: SlackChannel | None
```

Get the Slack channel if configured.

### send_alert

```
send_alert(
    card: OperatorCard,
    metric_values: dict[AlertCondition, Decimal]
    | None = None,
) -> AlertSendResult
```

Send an alert for the given OperatorCard.

This method:

1. Finds matching alert rules based on the card's event type
1. Checks if alerts should be sent (quiet hours, cooldown)
1. Routes to configured channels
1. Records cooldown state
1. Logs all sent alerts

Parameters:

| Name            | Type                            | Description                  | Default                                                  |
| --------------- | ------------------------------- | ---------------------------- | -------------------------------------------------------- |
| `card`          | `OperatorCard`                  | The OperatorCard to alert on | *required*                                               |
| `metric_values` | \`dict[AlertCondition, Decimal] | None\`                       | Optional dict of metric values for threshold-based rules |

Returns:

| Type              | Description                                |
| ----------------- | ------------------------------------------ |
| `AlertSendResult` | AlertSendResult with status and any errors |

### send_alert_sync

```
send_alert_sync(
    card: OperatorCard,
    metric_values: dict[AlertCondition, Decimal]
    | None = None,
) -> AlertSendResult
```

Synchronous wrapper for send_alert.

Parameters:

| Name            | Type                            | Description                  | Default                                                  |
| --------------- | ------------------------------- | ---------------------------- | -------------------------------------------------------- |
| `card`          | `OperatorCard`                  | The OperatorCard to alert on | *required*                                               |
| `metric_values` | \`dict[AlertCondition, Decimal] | None\`                       | Optional dict of metric values for threshold-based rules |

Returns:

| Type              | Description                                |
| ----------------- | ------------------------------------------ |
| `AlertSendResult` | AlertSendResult with status and any errors |

### send_direct_telegram_alert

```
send_direct_telegram_alert(
    card: OperatorCard,
) -> AlertSendResult
```

Send an alert directly to Telegram, bypassing rule matching.

This is useful for critical system alerts that should always go through regardless of configured rules.

Parameters:

| Name   | Type           | Description                  | Default    |
| ------ | -------------- | ---------------------------- | ---------- |
| `card` | `OperatorCard` | The OperatorCard to alert on | *required* |

Returns:

| Type              | Description                 |
| ----------------- | --------------------------- |
| `AlertSendResult` | AlertSendResult with status |

### send_direct_telegram_alert_sync

```
send_direct_telegram_alert_sync(
    card: OperatorCard,
) -> AlertSendResult
```

Synchronous wrapper for send_direct_telegram_alert.

Parameters:

| Name   | Type           | Description                  | Default    |
| ------ | -------------- | ---------------------------- | ---------- |
| `card` | `OperatorCard` | The OperatorCard to alert on | *required* |

Returns:

| Type              | Description                 |
| ----------------- | --------------------------- |
| `AlertSendResult` | AlertSendResult with status |

### send_direct_slack_alert

```
send_direct_slack_alert(
    card: OperatorCard, thread_ts: str | None = None
) -> AlertSendResult
```

Send an alert directly to Slack, bypassing rule matching.

This is useful for critical system alerts that should always go through regardless of configured rules. Supports threading for related alerts.

Parameters:

| Name        | Type           | Description                  | Default                               |
| ----------- | -------------- | ---------------------------- | ------------------------------------- |
| `card`      | `OperatorCard` | The OperatorCard to alert on | *required*                            |
| `thread_ts` | \`str          | None\`                       | Optional thread timestamp to reply to |

Returns:

| Type              | Description                 |
| ----------------- | --------------------------- |
| `AlertSendResult` | AlertSendResult with status |

### send_direct_slack_alert_sync

```
send_direct_slack_alert_sync(
    card: OperatorCard, thread_ts: str | None = None
) -> AlertSendResult
```

Synchronous wrapper for send_direct_slack_alert.

Parameters:

| Name        | Type           | Description                  | Default                               |
| ----------- | -------------- | ---------------------------- | ------------------------------------- |
| `card`      | `OperatorCard` | The OperatorCard to alert on | *required*                            |
| `thread_ts` | \`str          | None\`                       | Optional thread timestamp to reply to |

Returns:

| Type              | Description                 |
| ----------------- | --------------------------- |
| `AlertSendResult` | AlertSendResult with status |

### set_slack_thread

```
set_slack_thread(strategy_id: str, thread_ts: str) -> None
```

Set the Slack thread timestamp for a strategy.

This enables subsequent alerts for this strategy to be posted as thread replies.

Parameters:

| Name          | Type  | Description                     | Default    |
| ------------- | ----- | ------------------------------- | ---------- |
| `strategy_id` | `str` | The strategy ID                 | *required* |
| `thread_ts`   | `str` | The thread timestamp from Slack | *required* |

### clear_slack_thread

```
clear_slack_thread(strategy_id: str) -> None
```

Clear the Slack thread context for a strategy.

Call this when a strategy issue is resolved to start fresh threads for future alerts.

Parameters:

| Name          | Type  | Description     | Default    |
| ------------- | ----- | --------------- | ---------- |
| `strategy_id` | `str` | The strategy ID | *required* |

### clear_cooldown

```
clear_cooldown(
    strategy_id: str,
    condition: AlertCondition | None = None,
) -> None
```

Clear cooldown state for a strategy.

Parameters:

| Name          | Type             | Description                         | Default                                                   |
| ------------- | ---------------- | ----------------------------------- | --------------------------------------------------------- |
| `strategy_id` | `str`            | The strategy to clear cooldowns for | *required*                                                |
| `condition`   | \`AlertCondition | None\`                              | Optional specific condition to clear (clears all if None) |

## GatewayAlertManager

## almanak.framework.alerting.GatewayAlertManager

```
GatewayAlertManager(
    client: GatewayClient,
    strategy_id: str = "",
    timeout: float = 30.0,
)
```

AlertManager that sends alerts through the gateway.

This implementation routes all alert requests to the gateway sidecar, which has access to the actual alerting channels (Slack, Telegram).

Example

from almanak.framework.gateway_client import GatewayClient from almanak.framework.alerting.gateway_alert_manager import GatewayAlertManager

with GatewayClient() as client: alert_manager = GatewayAlertManager(client, strategy_id="my-strategy") result = await alert_manager.send_alert( message="Strategy executed successfully", severity="info", ) print(f"Alert sent: {result.success}")

Initialize gateway-backed alert manager.

Parameters:

| Name          | Type            | Description                           | Default    |
| ------------- | --------------- | ------------------------------------- | ---------- |
| `client`      | `GatewayClient` | Connected GatewayClient instance      | *required* |
| `strategy_id` | `str`           | Strategy identifier for alert context | `''`       |
| `timeout`     | `float`         | RPC timeout in seconds                | `30.0`     |

### strategy_id

```
strategy_id: str
```

Get the strategy ID.

### send_alert

```
send_alert(
    message: str,
    severity: str = "info",
    channel: str = "slack",
    metadata: dict[str, str] | None = None,
) -> GatewayAlertResult
```

Send an alert through the gateway.

Parameters:

| Name       | Type             | Description                                    | Default                        |
| ---------- | ---------------- | ---------------------------------------------- | ------------------------------ |
| `message`  | `str`            | Alert message text                             | *required*                     |
| `severity` | `str`            | Alert severity ("info", "warning", "critical") | `'info'`                       |
| `channel`  | `str`            | Alert channel ("slack", "telegram")            | `'slack'`                      |
| `metadata` | \`dict[str, str] | None\`                                         | Additional metadata to include |

Returns:

| Type                 | Description                            |
| -------------------- | -------------------------------------- |
| `GatewayAlertResult` | GatewayAlertResult with success status |

### log

```
log(
    message: str,
    level: str = "INFO",
    context: dict[str, str] | None = None,
    logger_name: str = "",
) -> None
```

Send a log message through the gateway.

Parameters:

| Name          | Type             | Description                                     | Default                       |
| ------------- | ---------------- | ----------------------------------------------- | ----------------------------- |
| `message`     | `str`            | Log message text                                | *required*                    |
| `level`       | `str`            | Log level ("DEBUG", "INFO", "WARNING", "ERROR") | `'INFO'`                      |
| `context`     | \`dict[str, str] | None\`                                          | Additional context to include |
| `logger_name` | `str`            | Optional logger name for categorization         | `''`                          |

### record_metric

```
record_metric(
    name: str,
    value: float,
    labels: dict[str, str] | None = None,
    metric_type: str = "gauge",
) -> None
```

Record a metric through the gateway.

Parameters:

| Name          | Type             | Description                                      | Default            |
| ------------- | ---------------- | ------------------------------------------------ | ------------------ |
| `name`        | `str`            | Metric name                                      | *required*         |
| `value`       | `float`          | Metric value                                     | *required*         |
| `labels`      | \`dict[str, str] | None\`                                           | Metric labels/tags |
| `metric_type` | `str`            | Type of metric ("gauge", "counter", "histogram") | `'gauge'`          |

## Channels

### SlackChannel

## almanak.framework.alerting.SlackChannel

```
SlackChannel(
    webhook_url: str,
    dashboard_base_url: str | None = None,
    max_retries: int = 3,
    base_delay: float = 1.0,
    enable_threading: bool = True,
    thread_timeout_seconds: int = 3600,
)
```

Slack notification channel for sending alerts via webhooks.

This class implements Slack incoming webhooks for sending alert notifications to operators. It uses Slack Block Kit for rich formatting and handles rate limiting with exponential backoff.

Supports threading for related alerts - subsequent alerts for the same strategy will be posted as thread replies to the original alert.

Attributes:

| Name                 | Type | Description                                   |
| -------------------- | ---- | --------------------------------------------- |
| `webhook_url`        |      | The Slack incoming webhook URL                |
| `dashboard_base_url` |      | Base URL for dashboard links in messages      |
| `max_retries`        |      | Maximum number of retries for failed sends    |
| `base_delay`         |      | Base delay in seconds for exponential backoff |

Initialize the Slack channel.

Parameters:

| Name                     | Type    | Description                                      | Default                                  |
| ------------------------ | ------- | ------------------------------------------------ | ---------------------------------------- |
| `webhook_url`            | `str`   | The Slack incoming webhook URL                   | *required*                               |
| `dashboard_base_url`     | \`str   | None\`                                           | Base URL for dashboard links in messages |
| `max_retries`            | `int`   | Maximum number of retries for failed sends       | `3`                                      |
| `base_delay`             | `float` | Base delay in seconds for exponential backoff    | `1.0`                                    |
| `enable_threading`       | `bool`  | Whether to enable threading for related alerts   | `True`                                   |
| `thread_timeout_seconds` | `int`   | How long to keep thread context (default 1 hour) | `3600`                                   |

### clear_thread

```
clear_thread(strategy_id: str) -> None
```

Clear the thread context for a strategy.

Call this when a strategy issue is resolved to start fresh threads for future alerts.

Parameters:

| Name          | Type  | Description     | Default    |
| ------------- | ----- | --------------- | ---------- |
| `strategy_id` | `str` | The strategy ID | *required* |

### clear_all_threads

```
clear_all_threads() -> None
```

Clear all thread contexts.

### send_alert

```
send_alert(
    card: OperatorCard, thread_ts: str | None = None
) -> SlackSendResult
```

Send an alert to Slack with exponential backoff retry.

This method formats the OperatorCard using Slack Block Kit and sends it to the configured webhook. It handles rate limiting with exponential backoff and logs all send attempts.

Threading support: If enable_threading is True and a thread_ts is provided (or stored from a previous alert for this strategy), the alert will be sent as a thread reply. Note that incoming webhooks don't return message timestamps, so for full threading support consider using the Slack Web API.

Parameters:

| Name        | Type           | Description                                   | Default                               |
| ----------- | -------------- | --------------------------------------------- | ------------------------------------- |
| `card`      | `OperatorCard` | The OperatorCard containing alert information | *required*                            |
| `thread_ts` | \`str          | None\`                                        | Optional thread timestamp to reply to |

Returns:

| Type              | Description                                                                |
| ----------------- | -------------------------------------------------------------------------- |
| `SlackSendResult` | SlackSendResult indicating success or failure, with thread_ts if available |

### send_alert_sync

```
send_alert_sync(
    card: OperatorCard, thread_ts: str | None = None
) -> SlackSendResult
```

Synchronous wrapper for send_alert.

Parameters:

| Name        | Type           | Description                                   | Default                               |
| ----------- | -------------- | --------------------------------------------- | ------------------------------------- |
| `card`      | `OperatorCard` | The OperatorCard containing alert information | *required*                            |
| `thread_ts` | \`str          | None\`                                        | Optional thread timestamp to reply to |

Returns:

| Type              | Description                                   |
| ----------------- | --------------------------------------------- |
| `SlackSendResult` | SlackSendResult indicating success or failure |

### set_thread_for_strategy

```
set_thread_for_strategy(
    strategy_id: str, thread_ts: str
) -> None
```

Set the thread_ts for a strategy externally.

This allows integration with the Slack Web API which returns message timestamps. After sending a message via Web API, call this method to enable subsequent alerts to be threaded.

Parameters:

| Name          | Type  | Description                             | Default    |
| ------------- | ----- | --------------------------------------- | ---------- |
| `strategy_id` | `str` | The strategy ID                         | *required* |
| `thread_ts`   | `str` | The thread timestamp from Slack Web API | *required* |

### send_custom_message

```
send_custom_message(
    strategy_id: str,
    severity: Severity,
    title: str,
    message: str,
    context: dict[str, Any] | None = None,
    thread_ts: str | None = None,
) -> SlackSendResult
```

Send a custom formatted message.

This method allows sending custom formatted messages that don't come from an OperatorCard. Supports threading for related messages.

Parameters:

| Name          | Type             | Description          | Default                               |
| ------------- | ---------------- | -------------------- | ------------------------------------- |
| `strategy_id` | `str`            | The strategy ID      | *required*                            |
| `severity`    | `Severity`       | Alert severity level | *required*                            |
| `title`       | `str`            | Alert title          | *required*                            |
| `message`     | `str`            | Alert message body   | *required*                            |
| `context`     | \`dict[str, Any] | None\`               | Optional additional context           |
| `thread_ts`   | \`str            | None\`               | Optional thread timestamp to reply to |

Returns:

| Type              | Description                                   |
| ----------------- | --------------------------------------------- |
| `SlackSendResult` | SlackSendResult indicating success or failure |

### TelegramChannel

## almanak.framework.alerting.TelegramChannel

```
TelegramChannel(
    chat_id: str,
    bot_token: str,
    dashboard_base_url: str | None = None,
    max_retries: int = 3,
    base_delay: float = 1.0,
)
```

Telegram notification channel for sending alerts.

This class implements the Telegram Bot API for sending alert notifications to operators. It handles rate limiting with exponential backoff and formats messages with severity indicators.

Attributes:

| Name                 | Type | Description                                   |
| -------------------- | ---- | --------------------------------------------- |
| `chat_id`            |      | The Telegram chat ID to send messages to      |
| `bot_token`          |      | The Telegram bot API token                    |
| `dashboard_base_url` |      | Base URL for dashboard links in messages      |
| `max_retries`        |      | Maximum number of retries for failed sends    |
| `base_delay`         |      | Base delay in seconds for exponential backoff |

Initialize the Telegram channel.

Parameters:

| Name                 | Type    | Description                                   | Default                                  |
| -------------------- | ------- | --------------------------------------------- | ---------------------------------------- |
| `chat_id`            | `str`   | The Telegram chat ID to send messages to      | *required*                               |
| `bot_token`          | `str`   | The Telegram bot API token                    | *required*                               |
| `dashboard_base_url` | \`str   | None\`                                        | Base URL for dashboard links in messages |
| `max_retries`        | `int`   | Maximum number of retries for failed sends    | `3`                                      |
| `base_delay`         | `float` | Base delay in seconds for exponential backoff | `1.0`                                    |

### api_url

```
api_url: str
```

Get the Telegram API URL for this bot.

### send_alert

```
send_alert(card: OperatorCard) -> TelegramSendResult
```

Send an alert to Telegram with exponential backoff retry.

This method formats the OperatorCard as a Telegram message and sends it to the configured chat. It handles rate limiting with exponential backoff and logs all send attempts.

Parameters:

| Name   | Type           | Description                                   | Default    |
| ------ | -------------- | --------------------------------------------- | ---------- |
| `card` | `OperatorCard` | The OperatorCard containing alert information | *required* |

Returns:

| Type                 | Description                                      |
| -------------------- | ------------------------------------------------ |
| `TelegramSendResult` | TelegramSendResult indicating success or failure |

### send_alert_sync

```
send_alert_sync(card: OperatorCard) -> TelegramSendResult
```

Synchronous wrapper for send_alert.

Parameters:

| Name   | Type           | Description                                   | Default    |
| ------ | -------------- | --------------------------------------------- | ---------- |
| `card` | `OperatorCard` | The OperatorCard containing alert information | *required* |

Returns:

| Type                 | Description                                      |
| -------------------- | ------------------------------------------------ |
| `TelegramSendResult` | TelegramSendResult indicating success or failure |

### format_custom_message

```
format_custom_message(
    strategy_id: str,
    severity: Severity,
    title: str,
    message: str,
    context: dict[str, Any] | None = None,
) -> str
```

Format a custom alert message.

This method allows sending custom formatted messages that don't come from an OperatorCard.

Parameters:

| Name          | Type             | Description          | Default                     |
| ------------- | ---------------- | -------------------- | --------------------------- |
| `strategy_id` | `str`            | The strategy ID      | *required*                  |
| `severity`    | `Severity`       | Alert severity level | *required*                  |
| `title`       | `str`            | Alert title          | *required*                  |
| `message`     | `str`            | Alert message body   | *required*                  |
| `context`     | \`dict[str, Any] | None\`               | Optional additional context |

Returns:

| Type  | Description              |
| ----- | ------------------------ |
| `str` | Formatted message string |

### send_custom_message

```
send_custom_message(
    strategy_id: str,
    severity: Severity,
    title: str,
    message: str,
    context: dict[str, Any] | None = None,
) -> TelegramSendResult
```

Send a custom formatted message.

Parameters:

| Name          | Type             | Description          | Default                     |
| ------------- | ---------------- | -------------------- | --------------------------- |
| `strategy_id` | `str`            | The strategy ID      | *required*                  |
| `severity`    | `Severity`       | Alert severity level | *required*                  |
| `title`       | `str`            | Alert title          | *required*                  |
| `message`     | `str`            | Alert message body   | *required*                  |
| `context`     | \`dict[str, Any] | None\`               | Optional additional context |

Returns:

| Type                 | Description                                      |
| -------------------- | ------------------------------------------------ |
| `TelegramSendResult` | TelegramSendResult indicating success or failure |

## Configuration

### AlertConfig

## almanak.framework.alerting.AlertConfig

```
AlertConfig(
    telegram_chat_id: str | None = None,
    slack_webhook: str | None = None,
    email: str | None = None,
    pagerduty_key: str | None = None,
    rules: list[AlertRule] = list(),
    quiet_hours: TimeRange | None = None,
    escalation_timeout_seconds: int = 900,
    dashboard_base_url: str | None = None,
    enabled: bool = True,
)
```

Configuration for a strategy's alerting setup.

This dataclass holds all the configuration needed to send alerts to operators via multiple channels.

**Default behavior** (no configuration):

- Cooldown: 300s (5 min) per rule — alerts for the same condition won't repeat more often than this.
- Quiet hours: None (disabled) — all severities sent 24/7.
- Escalation timeout: 900s (15 min) — unacknowledged alerts escalate.

For production deployments, use :meth:`default_production` which enables quiet hours (22:00-06:00 UTC, CRITICAL-only).

Attributes:

| Name                         | Type              | Description                                  |
| ---------------------------- | ----------------- | -------------------------------------------- |
| `telegram_chat_id`           | \`str             | None\`                                       |
| `slack_webhook`              | \`str             | None\`                                       |
| `email`                      | \`str             | None\`                                       |
| `pagerduty_key`              | \`str             | None\`                                       |
| `rules`                      | `list[AlertRule]` | List of alert rules to evaluate              |
| `quiet_hours`                | \`TimeRange       | None\`                                       |
| `escalation_timeout_seconds` | `int`             | Time before escalating unacknowledged alerts |
| `dashboard_base_url`         | \`str             | None\`                                       |
| `enabled`                    | `bool`            | Global enable/disable for all alerting       |

### configured_channels

```
configured_channels: list[AlertChannel]
```

Get the list of channels that have been configured.

### default_production

```
default_production(**overrides: Any) -> AlertConfig
```

Create an AlertConfig with production-safe defaults.

Differences from the bare default:

- Quiet hours enabled: 22:00-06:00 UTC (only CRITICAL alerts)

Channel credentials and rules still need to be provided via overrides.

Example::

```
config = AlertConfig.default_production(
    telegram_chat_id="123456",
    rules=[AlertRule(...)],
)
```

### has_channel

```
has_channel(channel: AlertChannel) -> bool
```

Check if a specific channel is configured.

### get_rules_for_condition

```
get_rules_for_condition(
    condition: AlertCondition,
) -> list[AlertRule]
```

Get all enabled rules for a specific condition.

### get_rules_for_channel

```
get_rules_for_channel(
    channel: AlertChannel,
) -> list[AlertRule]
```

Get all enabled rules that include a specific channel.

### is_in_quiet_hours

```
is_in_quiet_hours(check_time: time) -> bool
```

Check if the given time is within quiet hours.

### should_send_alert

```
should_send_alert(
    severity: Severity, current_time: time
) -> bool
```

Determine if an alert should be sent based on severity and quiet hours.

During quiet hours, only CRITICAL alerts are sent.

Parameters:

| Name           | Type       | Description                                   | Default    |
| -------------- | ---------- | --------------------------------------------- | ---------- |
| `severity`     | `Severity` | The severity of the alert                     | *required* |
| `current_time` | `time`     | The current time to check against quiet hours | *required* |

Returns:

| Type   | Description                      |
| ------ | -------------------------------- |
| `bool` | True if the alert should be sent |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert the alert config to a dictionary for serialization.

### AlertRule

## almanak.framework.alerting.AlertRule

```
AlertRule(
    condition: AlertCondition,
    threshold: Decimal,
    severity: Severity,
    channels: list[AlertChannel],
    cooldown_seconds: int = 300,
    enabled: bool = True,
    description: str = "",
    custom_message: str | None = None,
)
```

A rule defining when and how to send an alert.

Attributes:

| Name               | Type                 | Description                                                                 |
| ------------------ | -------------------- | --------------------------------------------------------------------------- |
| `condition`        | `AlertCondition`     | The condition that triggers this alert                                      |
| `threshold`        | `Decimal`            | The threshold value for the condition (interpretation depends on condition) |
| `severity`         | `Severity`           | Severity level for alerts triggered by this rule                            |
| `channels`         | `list[AlertChannel]` | List of channels to send alerts to                                          |
| `cooldown_seconds` | `int`                | Minimum seconds between alerts for this rule                                |
| `enabled`          | `bool`               | Whether this rule is active                                                 |
| `description`      | `str`                | Human-readable description of the rule                                      |
| `custom_message`   | \`str                | None\`                                                                      |

### __post_init__

```
__post_init__() -> None
```

Validate the alert rule.

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert the alert rule to a dictionary for serialization.

### AlertChannel

## almanak.framework.alerting.AlertChannel

Bases: `StrEnum`

Supported notification channels for alerts.

## Escalation

### EscalationPolicy

## almanak.framework.alerting.EscalationPolicy

```
EscalationPolicy(
    config: AlertConfig,
    auto_remediation_callback: AutoRemediationCallback
    | None = None,
    emergency_pause_callback: EmergencyPauseCallback
    | None = None,
    custom_thresholds: dict[EscalationLevel, int]
    | None = None,
)
```

Manages escalation of unacknowledged alerts.

The EscalationPolicy tracks alerts and escalates them through multiple levels if they are not acknowledged within time thresholds.

Escalation levels:

- Level 1 (\<5 min): Telegram/Slack
- Level 2 (\<15 min): Add Email
- Level 3 (\<30 min): PagerDuty for HIGH+ severity
- Level 4 (30+ min): Auto-remediation or emergency pause

Attributes:

| Name                        | Type                         | Description                                  |
| --------------------------- | ---------------------------- | -------------------------------------------- |
| `config`                    |                              | The AlertConfig for channel configuration    |
| `escalations`               | `dict[str, EscalationState]` | Dict of active escalation states by alert_id |
| `auto_remediation_callback` |                              | Optional callback for auto-remediation       |
| `emergency_pause_callback`  |                              | Optional callback for emergency pause        |

Initialize the EscalationPolicy.

Parameters:

| Name                        | Type                         | Description                             | Default                                               |
| --------------------------- | ---------------------------- | --------------------------------------- | ----------------------------------------------------- |
| `config`                    | `AlertConfig`                | AlertConfig with channel configurations | *required*                                            |
| `auto_remediation_callback` | \`AutoRemediationCallback    | None\`                                  | Callback to execute auto-remediation                  |
| `emergency_pause_callback`  | \`EmergencyPauseCallback     | None\`                                  | Callback to execute emergency pause                   |
| `custom_thresholds`         | \`dict[EscalationLevel, int] | None\`                                  | Optional custom time thresholds for escalation levels |

### start_escalation

```
start_escalation(
    strategy_id: str,
    card: OperatorCard,
    current_time: datetime | None = None,
) -> EscalationState
```

Start tracking escalation for a new alert.

If an escalation already exists for this alert, returns the existing one.

Parameters:

| Name           | Type           | Description                               | Default                        |
| -------------- | -------------- | ----------------------------------------- | ------------------------------ |
| `strategy_id`  | `str`          | The strategy ID                           | *required*                     |
| `card`         | `OperatorCard` | The OperatorCard that triggered the alert | *required*                     |
| `current_time` | \`datetime     | None\`                                    | Current time (defaults to now) |

Returns:

| Type              | Description                        |
| ----------------- | ---------------------------------- |
| `EscalationState` | The EscalationState for this alert |

### acknowledge

```
acknowledge(
    alert_id: str,
    acknowledged_by: str = "operator",
    current_time: datetime | None = None,
) -> bool
```

Acknowledge an alert and stop its escalation.

Parameters:

| Name              | Type       | Description                      | Default                        |
| ----------------- | ---------- | -------------------------------- | ------------------------------ |
| `alert_id`        | `str`      | The alert ID to acknowledge      | *required*                     |
| `acknowledged_by` | `str`      | Who is acknowledging (for audit) | `'operator'`                   |
| `current_time`    | \`datetime | None\`                           | Current time (defaults to now) |

Returns:

| Type   | Description                                                |
| ------ | ---------------------------------------------------------- |
| `bool` | True if acknowledgment succeeded, False if alert not found |

### acknowledge_by_strategy

```
acknowledge_by_strategy(
    strategy_id: str,
    acknowledged_by: str = "operator",
    current_time: datetime | None = None,
) -> int
```

Acknowledge all active alerts for a strategy.

Parameters:

| Name              | Type       | Description          | Default                        |
| ----------------- | ---------- | -------------------- | ------------------------------ |
| `strategy_id`     | `str`      | The strategy ID      | *required*                     |
| `acknowledged_by` | `str`      | Who is acknowledging | `'operator'`                   |
| `current_time`    | \`datetime | None\`               | Current time (defaults to now) |

Returns:

| Type  | Description                   |
| ----- | ----------------------------- |
| `int` | Number of alerts acknowledged |

### resolve

```
resolve(
    alert_id: str, current_time: datetime | None = None
) -> bool
```

Mark an alert as resolved.

Parameters:

| Name           | Type       | Description             | Default                        |
| -------------- | ---------- | ----------------------- | ------------------------------ |
| `alert_id`     | `str`      | The alert ID to resolve | *required*                     |
| `current_time` | \`datetime | None\`                  | Current time (defaults to now) |

Returns:

| Type   | Description                                            |
| ------ | ------------------------------------------------------ |
| `bool` | True if resolution succeeded, False if alert not found |

### check_escalation

```
check_escalation(
    alert_id: str, current_time: datetime | None = None
) -> EscalationResult
```

Check if an alert needs to be escalated.

This method checks the time elapsed since the alert was created and determines if it should be escalated to the next level.

Parameters:

| Name           | Type       | Description           | Default                        |
| -------------- | ---------- | --------------------- | ------------------------------ |
| `alert_id`     | `str`      | The alert ID to check | *required*                     |
| `current_time` | \`datetime | None\`                | Current time (defaults to now) |

Returns:

| Type               | Description                                     |
| ------------------ | ----------------------------------------------- |
| `EscalationResult` | EscalationResult indicating what action to take |

### process_escalation

```
process_escalation(
    alert_id: str, current_time: datetime | None = None
) -> EscalationResult
```

Process escalation for an alert, including executing Level 4 actions.

This method checks escalation and executes auto-remediation or emergency pause if Level 4 is reached.

Parameters:

| Name           | Type       | Description             | Default                        |
| -------------- | ---------- | ----------------------- | ------------------------------ |
| `alert_id`     | `str`      | The alert ID to process | *required*                     |
| `current_time` | \`datetime | None\`                  | Current time (defaults to now) |

Returns:

| Type               | Description                          |
| ------------------ | ------------------------------------ |
| `EscalationResult` | EscalationResult with action details |

### process_escalation_sync

```
process_escalation_sync(
    alert_id: str, current_time: datetime | None = None
) -> EscalationResult
```

Synchronous wrapper for process_escalation.

Parameters:

| Name           | Type       | Description             | Default                        |
| -------------- | ---------- | ----------------------- | ------------------------------ |
| `alert_id`     | `str`      | The alert ID to process | *required*                     |
| `current_time` | \`datetime | None\`                  | Current time (defaults to now) |

Returns:

| Type               | Description                          |
| ------------------ | ------------------------------------ |
| `EscalationResult` | EscalationResult with action details |

### check_all_escalations

```
check_all_escalations(
    current_time: datetime | None = None,
) -> dict[str, EscalationResult]
```

Check all active escalations.

Parameters:

| Name           | Type       | Description | Default                        |
| -------------- | ---------- | ----------- | ------------------------------ |
| `current_time` | \`datetime | None\`      | Current time (defaults to now) |

Returns:

| Type                          | Description                               |
| ----------------------------- | ----------------------------------------- |
| `dict[str, EscalationResult]` | Dict mapping alert_id to EscalationResult |

### process_all_escalations

```
process_all_escalations(
    current_time: datetime | None = None,
) -> dict[str, EscalationResult]
```

Process all active escalations.

Parameters:

| Name           | Type       | Description | Default                        |
| -------------- | ---------- | ----------- | ------------------------------ |
| `current_time` | \`datetime | None\`      | Current time (defaults to now) |

Returns:

| Type                          | Description                               |
| ----------------------------- | ----------------------------------------- |
| `dict[str, EscalationResult]` | Dict mapping alert_id to EscalationResult |

### get_escalation_state

```
get_escalation_state(
    alert_id: str,
) -> EscalationState | None
```

Get the current escalation state for an alert.

Parameters:

| Name       | Type  | Description  | Default    |
| ---------- | ----- | ------------ | ---------- |
| `alert_id` | `str` | The alert ID | *required* |

Returns:

| Type              | Description |
| ----------------- | ----------- |
| \`EscalationState | None\`      |

### get_active_escalations

```
get_active_escalations() -> list[EscalationState]
```

Get all active escalations.

Returns:

| Type                    | Description                            |
| ----------------------- | -------------------------------------- |
| `list[EscalationState]` | List of active EscalationState objects |

### get_escalations_for_strategy

```
get_escalations_for_strategy(
    strategy_id: str,
) -> list[EscalationState]
```

Get all escalations for a strategy.

Parameters:

| Name          | Type  | Description     | Default    |
| ------------- | ----- | --------------- | ---------- |
| `strategy_id` | `str` | The strategy ID | *required* |

Returns:

| Type                    | Description                                      |
| ----------------------- | ------------------------------------------------ |
| `list[EscalationState]` | List of EscalationState objects for the strategy |

### clear_resolved_escalations

```
clear_resolved_escalations(
    max_age_seconds: int = 86400,
) -> int
```

Clear old resolved escalations to prevent memory buildup.

Parameters:

| Name              | Type  | Description                                             | Default |
| ----------------- | ----- | ------------------------------------------------------- | ------- |
| `max_age_seconds` | `int` | Maximum age for resolved escalations (default 24 hours) | `86400` |

Returns:

| Type  | Description                   |
| ----- | ----------------------------- |
| `int` | Number of escalations cleared |

### EscalationLevel

## almanak.framework.alerting.EscalationLevel

Bases: `IntEnum`

Escalation levels from least to most severe.

## Results

### AlertSendResult

## almanak.framework.alerting.AlertSendResult

```
AlertSendResult(
    success: bool,
    channels_sent: list[AlertChannel] = list(),
    channels_failed: list[AlertChannel] = list(),
    errors: dict[AlertChannel, str] = dict(),
    skipped_reason: str | None = None,
)
```

Result of sending an alert through AlertManager.

# Backtesting

Dual-engine backtesting system: PnL simulation with historical prices and paper trading on Anvil forks.

## PnL Backtester

### PnLBacktester

## almanak.framework.backtesting.PnLBacktester

```
PnLBacktester(
    data_provider: HistoricalDataProvider,
    fee_models: dict[str, FeeModel],
    slippage_models: dict[str, SlippageModel],
    strategy_type: str | None = "auto",
    gas_provider: GasPriceProvider | None = None,
    data_config: BacktestDataConfig | None = None,
    _mev_simulator: MEVSimulator | None = None,
    _current_backtest_id: str = "",
    _adapter: StrategyBacktestAdapter | None = None,
    _detected_strategy_type: StrategyTypeHint | None = None,
    _error_handler: BacktestErrorHandler | None = None,
    _fallback_usage: dict[str, int] | None = None,
    _gas_price_records: list[GasPriceRecord] | None = None,
)
```

Main PnL backtesting engine for historical strategy simulation.

The PnLBacktester simulates strategy execution against historical price data to evaluate performance. It:

1. Iterates through historical market data at configured intervals
1. Calls strategy.decide() with a MarketSnapshot for each time step
1. Simulates intent execution with configurable fee/slippage models
1. Tracks portfolio state and builds an equity curve
1. Calculates comprehensive performance metrics

Attributes:

| Name              | Type                       | Description                                              |
| ----------------- | -------------------------- | -------------------------------------------------------- |
| `data_provider`   | `HistoricalDataProvider`   | Historical data provider (e.g., CoinGeckoDataProvider)   |
| `fee_models`      | `dict[str, FeeModel]`      | Dict mapping protocol -> FeeModel (or "default" for all) |
| `slippage_models` | `dict[str, SlippageModel]` | Dict mapping protocol -> SlippageModel                   |
| `gas_provider`    | \`GasPriceProvider         | None\`                                                   |
| `mev_simulator`   | \`GasPriceProvider         | None\`                                                   |
| `strategy_type`   | \`str                      | None\`                                                   |
| `data_config`     | \`BacktestDataConfig       | None\`                                                   |

Example

backtester = PnLBacktester( data_provider=CoinGeckoDataProvider(), fee_models={"default": DefaultFeeModel()}, slippage_models={"default": DefaultSlippageModel()}, )

result = await backtester.backtest(my_strategy, config) print(result.summary())

### With explicit strategy type:

backtester = PnLBacktester( data_provider=CoinGeckoDataProvider(), fee_models={"default": DefaultFeeModel()}, slippage_models={"default": DefaultSlippageModel()}, strategy_type="lp", # Force LP adapter )

### With BacktestDataConfig for historical data:

from almanak.framework.backtesting.config import BacktestDataConfig

data_config = BacktestDataConfig( use_historical_volume=True, use_historical_funding=True, use_historical_apy=True, ) backtester = PnLBacktester( data_provider=CoinGeckoDataProvider(), fee_models={"default": DefaultFeeModel()}, slippage_models={"default": DefaultSlippageModel()}, data_config=data_config, )

### gas_provider

```
gas_provider: GasPriceProvider | None = None
```

Optional gas price provider for historical gas prices.

When provided and config.use_historical_gas_gwei=True, the engine will fetch historical gas prices at each simulation timestamp instead of using the static config.gas_price_gwei value.

Example

from almanak.framework.backtesting.pnl.providers import EtherscanGasPriceProvider

gas_provider = EtherscanGasPriceProvider( api_keys={"ethereum": "your-key"}, ) backtester = PnLBacktester( data_provider=data_provider, fee_models=fee_models, slippage_models=slippage_models, gas_provider=gas_provider, )

### data_config

```
data_config: BacktestDataConfig | None = None
```

Optional BacktestDataConfig for controlling historical data providers.

When provided, this configuration is passed to strategy-specific adapters (LP, Perp, Lending) to control historical data provider behavior:

- use_historical_volume: Fetch LP fee data from subgraphs
- use_historical_funding: Fetch perp funding rates from APIs
- use_historical_apy: Fetch lending APY from subgraphs
- strict_historical_mode: Fail if historical data unavailable
- Fallback values for when historical data is unavailable
- Rate limiting configuration for CoinGecko and The Graph
- Cache settings for persistent data storage

Example

from almanak.framework.backtesting.config import BacktestDataConfig

data_config = BacktestDataConfig( use_historical_volume=True, use_historical_funding=True, use_historical_apy=True, strict_historical_mode=False, ) backtester = PnLBacktester( data_provider=data_provider, fee_models=fee_models, slippage_models=slippage_models, data_config=data_config, )

### __post_init__

```
__post_init__() -> None
```

Validate configuration after initialization.

### close

```
close() -> None
```

Close data provider and gas provider HTTP sessions.

### run_preflight_validation

```
run_preflight_validation(
    config: PnLBacktestConfig,
) -> PreflightReport
```

Run preflight validation checks before starting a backtest.

Performs validation checks to ensure data requirements can be met:

- Checks price data availability for all tokens in config
- Verifies data provider capabilities match requirements
- Tests archive node accessibility if historical TWAP/Chainlink needed
- Estimates data coverage based on provider capabilities

Parameters:

| Name     | Type                | Description                                                | Default    |
| -------- | ------------------- | ---------------------------------------------------------- | ---------- |
| `config` | `PnLBacktestConfig` | Backtest configuration specifying tokens, time range, etc. | *required* |

Returns:

| Type              | Description                                                       |
| ----------------- | ----------------------------------------------------------------- |
| `PreflightReport` | PreflightReport with pass/fail status and detailed check results. |

Example

preflight = await backtester.run_preflight_validation(config) if not preflight.passed: print(preflight.summary())

# Handle validation failure

else: result = await backtester.backtest(strategy, config)

### backtest

```
backtest(
    strategy: BacktestableStrategy,
    config: PnLBacktestConfig,
) -> BacktestResult
```

Run a backtest for a strategy over the configured period.

This method:

1. Initializes a simulated portfolio with initial capital
1. Creates a HistoricalDataConfig from the backtest config
1. Iterates through historical market states
1. For each time step: a. Creates a MarketSnapshot from MarketState b. Calls strategy.decide(snapshot) to get intent c. Queues intent for execution (with inclusion delay) d. Executes queued intents e. Marks portfolio to market
1. Calculates final metrics and returns BacktestResult

Parameters:

| Name       | Type                   | Description                                                | Default    |
| ---------- | ---------------------- | ---------------------------------------------------------- | ---------- |
| `strategy` | `BacktestableStrategy` | Strategy to backtest (must implement BacktestableStrategy) | *required* |
| `config`   | `PnLBacktestConfig`    | Backtest configuration (time range, capital, models, etc.) | *required* |

Returns:

| Type             | Description                                           |
| ---------------- | ----------------------------------------------------- |
| `BacktestResult` | BacktestResult with metrics, trades, and equity curve |

Raises:

| Type         | Description                                    |
| ------------ | ---------------------------------------------- |
| `ValueError` | If strategy is not compatible with backtesting |

### get_fee_model

```
get_fee_model(protocol: str) -> FeeModel
```

Get the fee model for a protocol.

Parameters:

| Name       | Type  | Description                                   | Default    |
| ---------- | ----- | --------------------------------------------- | ---------- |
| `protocol` | `str` | Protocol name (e.g., "uniswap_v3", "aave_v3") | *required* |

Returns:

| Type       | Description                                        |
| ---------- | -------------------------------------------------- |
| `FeeModel` | FeeModel for the protocol, or default if not found |

### get_slippage_model

```
get_slippage_model(protocol: str) -> SlippageModel
```

Get the slippage model for a protocol.

Parameters:

| Name       | Type  | Description                               | Default    |
| ---------- | ----- | ----------------------------------------- | ---------- |
| `protocol` | `str` | Protocol name (e.g., "uniswap_v3", "gmx") | *required* |

Returns:

| Type            | Description                                             |
| --------------- | ------------------------------------------------------- |
| `SlippageModel` | SlippageModel for the protocol, or default if not found |

### PnLBacktestConfig

## almanak.framework.backtesting.PnLBacktestConfig

```
PnLBacktestConfig(
    start_time: datetime,
    end_time: datetime,
    interval_seconds: int = 3600,
    initial_capital_usd: Decimal = Decimal("10000"),
    fee_model: str = "realistic",
    slippage_model: str = "realistic",
    include_gas_costs: bool = True,
    gas_price_gwei: Decimal = Decimal("30"),
    inclusion_delay_blocks: int = 1,
    chain: str = "arbitrum",
    tokens: list[str] = (lambda: ["WETH", "USDC"])(),
    benchmark_token: str = "WETH",
    risk_free_rate: Decimal = Decimal("0.05"),
    trading_days_per_year: int = 365,
    initial_margin_ratio: Decimal = Decimal("0.1"),
    maintenance_margin_ratio: Decimal = Decimal("0.05"),
    mev_simulation_enabled: bool = False,
    auto_correct_positions: bool = False,
    reconciliation_alert_threshold_pct: Decimal = Decimal(
        "0.05"
    ),
    random_seed: int | None = None,
    strict_reproducibility: bool = False,
    staleness_threshold_seconds: int = 3600,
    institutional_mode: bool = False,
    min_data_coverage: Decimal = Decimal("0.98"),
    allow_hardcoded_fallback: bool = False,
    allow_degraded_data: bool = True,
    require_symbol_mapping: bool = False,
    use_historical_gas_prices: bool = False,
    gas_eth_price_override: Decimal | None = None,
    use_historical_gas_gwei: bool = False,
    track_gas_prices: bool = False,
    preflight_validation: bool = True,
    fail_on_preflight_error: bool = True,
)
```

Configuration for a PnL backtest simulation.

Controls all parameters of the backtest including time range, initial capital, fee/slippage models, gas costs, and execution delay simulation.

Attributes:

| Name                                 | Type        | Description                                                                                                                                                                             |
| ------------------------------------ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `start_time`                         | `datetime`  | Start of the backtest period (inclusive)                                                                                                                                                |
| `end_time`                           | `datetime`  | End of the backtest period (inclusive)                                                                                                                                                  |
| `interval_seconds`                   | `int`       | Time between simulation ticks in seconds (default: 3600 = 1 hour)                                                                                                                       |
| `initial_capital_usd`                | `Decimal`   | Starting capital in USD                                                                                                                                                                 |
| `fee_model`                          | `str`       | Fee model to use - 'realistic', 'zero', or protocol-specific (e.g., 'uniswap_v3', 'aave_v3', 'gmx')                                                                                     |
| `slippage_model`                     | `str`       | Slippage model to use - 'realistic', 'zero', or protocol-specific (e.g., 'liquidity_aware', 'constant')                                                                                 |
| `include_gas_costs`                  | `bool`      | Whether to include gas costs in PnL calculations                                                                                                                                        |
| `gas_price_gwei`                     | `Decimal`   | Gas price to use for cost calculations (default: 30 gwei)                                                                                                                               |
| `inclusion_delay_blocks`             | `int`       | Number of blocks to delay intent execution to simulate realistic trade timing (default: 1). When > 0, intents are queued and executed in the next iteration(s) rather than immediately. |
| `chain`                              | `str`       | Blockchain to simulate execution on (default: 'arbitrum')                                                                                                                               |
| `tokens`                             | `list[str]` | List of tokens to track prices for (default: ['WETH', 'USDC'])                                                                                                                          |
| `benchmark_token`                    | `str`       | Token to use for benchmark comparisons (default: 'WETH')                                                                                                                                |
| `risk_free_rate`                     | `Decimal`   | Annual risk-free rate for Sharpe ratio calculation (default: 0.05)                                                                                                                      |
| `trading_days_per_year`              | `int`       | Number of trading days for annualization (default: 365)                                                                                                                                 |
| `initial_margin_ratio`               | `Decimal`   | Initial margin ratio for opening perp positions (default: 0.1 = 10%)                                                                                                                    |
| `maintenance_margin_ratio`           | `Decimal`   | Maintenance margin ratio for liquidation (default: 0.05 = 5%)                                                                                                                           |
| `mev_simulation_enabled`             | `bool`      | Enable MEV cost simulation for realistic execution costs (default: False)                                                                                                               |
| `auto_correct_positions`             | `bool`      | Enable auto-correction of tracked positions when discrepancies are detected                                                                                                             |
| `reconciliation_alert_threshold_pct` | `Decimal`   | Threshold percentage for triggering reconciliation alerts (default: 5%)                                                                                                                 |

Example

config = PnLBacktestConfig( start_time=datetime(2024, 1, 1), end_time=datetime(2024, 6, 1), initial_capital_usd=Decimal("10000"), ) print(f"Duration: {config.duration_days:.1f} days") print(f"Estimated ticks: {config.estimated_ticks}")

### initial_margin_ratio

```
initial_margin_ratio: Decimal = Decimal('0.1')
```

Initial margin ratio required to open a position (default: 0.1 = 10%). This is the minimum margin/position_value ratio required to open a new perp position.

### maintenance_margin_ratio

```
maintenance_margin_ratio: Decimal = Decimal('0.05')
```

Maintenance margin ratio for liquidation threshold (default: 0.05 = 5%). When margin/position_value falls below this, the position may be liquidated.

### mev_simulation_enabled

```
mev_simulation_enabled: bool = False
```

Enable MEV (Maximal Extractable Value) cost simulation (default: False). When enabled, simulates sandwich attack probability and additional slippage based on trade size and token characteristics. Adds estimated MEV costs to trade records and total MEV cost to backtest metrics.

### auto_correct_positions

```
auto_correct_positions: bool = False
```

Enable automatic position correction when discrepancies are detected (default: False). When enabled, the reconciliation process will update tracked positions to match actual on-chain state when discrepancies exceed the alert threshold. Corrected positions will have auto_corrected=True in their ReconciliationEvent.

### reconciliation_alert_threshold_pct

```
reconciliation_alert_threshold_pct: Decimal = Decimal(
    "0.05"
)
```

Threshold percentage for triggering reconciliation alerts (default: 5%). When discrepancy_pct exceeds this threshold, an alert is emitted. Set to 0 to alert on any discrepancy, or higher values to only alert on significant drift.

### random_seed

```
random_seed: int | None = None
```

Random seed for reproducibility (default: None = no seed). When set, any randomness in the backtest (e.g., Monte Carlo simulations, random sampling) will use this seed for reproducibility. The seed is recorded in the config for re-running identical backtests.

### strict_reproducibility

```
strict_reproducibility: bool = False
```

Enforce strict reproducibility mode (default: False).

When enabled, the backtest will raise errors instead of using fallbacks that could produce non-deterministic results:

- Raises ValueError if simulation timestamp is missing (instead of using datetime.now())
- Raises ValueError if required historical data is unavailable
- Requires all price sources to provide historical data, not just current prices

Use this mode when you need byte-identical results across multiple runs with the same configuration and random_seed. When disabled, the backtester will use reasonable defaults and log warnings instead of failing.

### staleness_threshold_seconds

```
staleness_threshold_seconds: int = 3600
```

Threshold in seconds for marking price data as stale (default: 3600 = 1 hour).

Price data older than this threshold relative to the simulation timestamp will be counted as stale in the data quality report. This helps identify backtests that may be using outdated price information.

Set to 0 to disable staleness tracking.

### institutional_mode

```
institutional_mode: bool = False
```

Enable institutional-grade enforcement mode (default: False).

When enabled, applies stricter data quality requirements suitable for institutional trading operations:

- Fails backtest if data coverage is below min_data_coverage threshold
- Disables hardcoded price fallbacks (allow_hardcoded_fallback=False)
- Requires historical price data from verified sources
- Enforces strict reproducibility (strict_reproducibility=True)

This mode is designed for production-grade backtests where data quality and reproducibility are critical. Use for institutional trading strategies or when accurate PnL calculations are required for compliance/reporting.

### min_data_coverage

```
min_data_coverage: Decimal = Decimal('0.98')
```

Minimum data coverage ratio required in institutional mode (default: 0.98 = 98%).

When institutional_mode is enabled, the backtest will fail if the actual data coverage ratio (successful price lookups / total lookups) falls below this threshold.

When institutional_mode is disabled, this threshold is only used for warnings in the data quality report, not enforcement.

Valid range: 0.0 to 1.0 (0% to 100%)

### allow_hardcoded_fallback

```
allow_hardcoded_fallback: bool = False
```

Allow hardcoded price fallbacks when price data is unavailable (default: False).

When disabled (default): The backtester will raise an error if it cannot find price data, ensuring that all valuations use actual market prices. This is the institutional-grade setting for production backtests.

When enabled: The backtester may use hardcoded fallback prices for tokens when historical price data is unavailable. This can mask data quality issues and should only be used for development/testing where price accuracy is not critical.

Note: This is automatically set to False when institutional_mode=True in **post_init**, as institutional-grade backtests should never use arbitrary hardcoded prices.

Environment variable: Set ALMANAK_ALLOW_HARDCODED_PRICES=1 to override for testing scenarios where you need relaxed defaults.

### allow_degraded_data

```
allow_degraded_data: bool = True
```

Allow backtests to proceed with degraded or incomplete data (default: True).

When enabled, the backtester will continue execution even when:

- Some price data is missing or interpolated
- Data sources return stale information
- Historical data has gaps

When disabled, the backtester will fail fast if data quality issues are detected, ensuring only high-quality data is used for analysis.

Note: This is automatically set to False when institutional_mode=True in **post_init**, as institutional-grade backtests require complete data.

### require_symbol_mapping

```
require_symbol_mapping: bool = False
```

Require all token addresses to be resolved to symbols (default: False).

When enabled, the backtester will fail if any token address cannot be resolved to a human-readable symbol. This ensures all trade records and reports use consistent, recognizable token names.

When disabled, unresolved token addresses are used as-is (checksummed), which may make reports harder to read and audit.

Note: This is automatically set to True when institutional_mode=True in **post_init**, as institutional-grade backtests require clear symbol identification for compliance and reporting purposes.

### use_historical_gas_prices

```
use_historical_gas_prices: bool = False
```

Use historical gas prices for accurate gas cost simulation (default: False).

When enabled, the backtester will attempt to fetch historical ETH prices at each simulation timestamp to calculate gas costs more accurately. This provides realistic gas cost estimates that reflect market conditions at the time of simulated trades.

When disabled, gas costs use the current ETH price or gas_eth_price_override if specified. This is faster but less accurate for historical backtests.

Note: Requires a data provider that supports historical price lookups.

### gas_eth_price_override

```
gas_eth_price_override: Decimal | None = None
```

Override ETH price for gas cost calculations (default: None = use market price).

When set, this value is used as the ETH price for all gas cost calculations, ignoring both historical and current market prices. This is useful for:

- Testing with a fixed ETH price for reproducibility
- Stress testing with extreme ETH price scenarios
- Backtests where gas cost accuracy is not critical

When None, gas costs use:

1. Historical ETH price (if use_historical_gas_prices=True)
1. Current ETH price from data provider
1. Default fallback ($3000) with warning if unavailable

Value should be in USD (e.g., Decimal("3000") for $3000 per ETH).

### use_historical_gas_gwei

```
use_historical_gas_gwei: bool = False
```

Use historical gas prices (gwei) from gas price provider (default: False).

When enabled and a gas_provider is attached to the PnLBacktester, the engine will fetch historical gas prices at each simulation timestamp instead of using the static gas_price_gwei value. This provides more realistic gas cost estimates that reflect network congestion at historical timestamps.

Priority order for gas price (gwei):

1. Historical gas price from gas_provider (if use_historical_gas_gwei=True)
1. MarketState.gas_price_gwei (if populated by data provider)
1. config.gas_price_gwei (static default: 30 gwei)

When disabled, gas costs use the static gas_price_gwei for all trades, which is faster but may not reflect actual network conditions.

Note: Requires a GasPriceProvider (e.g., EtherscanGasPriceProvider) to be passed to the PnLBacktester. If enabled without a provider, falls back to MarketState.gas_price_gwei or config.gas_price_gwei with a warning.

### track_gas_prices

```
track_gas_prices: bool = False
```

Track detailed gas price records for each trade (default: False).

When enabled, the backtester records a GasPriceRecord for each trade, capturing the gas price in gwei, source, and USD cost. These records are stored in BacktestResult.gas_prices_used for detailed analysis.

This is useful for:

- Analyzing gas price volatility impact on strategy performance
- Understanding gas cost breakdown by source (historical vs config)
- Auditing gas costs in institutional-grade backtests

When disabled, only summary statistics (gas_price_summary) are populated from the TradeRecord.gas_price_gwei values, reducing result size.

Note: Gas price summary statistics are always calculated regardless of this setting, since TradeRecord already contains gas_price_gwei.

### preflight_validation

```
preflight_validation: bool = True
```

Enable preflight validation before running backtest (default: True).

When enabled, the backtester performs validation checks before starting the simulation to ensure data requirements can be met:

- Checks price data availability for all tokens in config
- Verifies data provider capabilities match requirements
- Tests archive node accessibility if historical TWAP/Chainlink needed
- Reports estimated data coverage and potential gaps

Results are returned in a PreflightReport with pass/fail and details. This helps identify data issues early, before spending time on a backtest that would fail or produce inaccurate results.

When disabled, the backtest proceeds without validation, which is faster but may encounter data issues during simulation.

### fail_on_preflight_error

```
fail_on_preflight_error: bool = True
```

Fail fast if preflight validation fails (default: True).

When enabled (True): If preflight validation detects critical issues (e.g., missing price data, insufficient data coverage), the backtester raises PreflightValidationError with an actionable error message that includes:

- What failed (specific checks that did not pass)
- Why it failed (the underlying cause)
- How to fix it (recommendations for resolution)

When disabled (False): The backtester logs warnings about preflight issues but continues in degraded mode. This is useful for exploratory backtests where you want to see partial results even with data gaps.

The preflight_passed field in BacktestResult indicates whether preflight validation passed, regardless of this setting.

Note: This setting only applies when preflight_validation=True.

### duration_seconds

```
duration_seconds: int
```

Get the total backtest duration in seconds.

### duration_days

```
duration_days: float
```

Get the total backtest duration in days.

### duration_hours

```
duration_hours: float
```

Get the total backtest duration in hours.

### estimated_ticks

```
estimated_ticks: int
```

Get the estimated number of simulation ticks.

### interval_hours

```
interval_hours: float
```

Get the interval between ticks in hours.

### __post_init__

```
__post_init__() -> None
```

Validate configuration after initialization.

### get_gas_cost_usd

```
get_gas_cost_usd(
    gas_used: int, eth_price_usd: Decimal
) -> Decimal
```

Calculate gas cost in USD for a given amount of gas used.

Parameters:

| Name            | Type      | Description                               | Default    |
| --------------- | --------- | ----------------------------------------- | ---------- |
| `gas_used`      | `int`     | Amount of gas consumed by the transaction | *required* |
| `eth_price_usd` | `Decimal` | Current ETH price in USD                  | *required* |

Returns:

| Type      | Description     |
| --------- | --------------- |
| `Decimal` | Gas cost in USD |

### to_dict

```
to_dict() -> dict[str, Any]
```

Serialize to dictionary.

### to_dict_with_metadata

```
to_dict_with_metadata(
    data_provider_info: dict[str, Any] | None = None,
) -> dict[str, Any]
```

Serialize to dictionary with full metadata for reproducibility.

This method extends to_dict() to include additional metadata needed to reproduce a backtest exactly, such as:

- Data provider versions and timestamps
- SDK/framework versions
- Run timestamp

Parameters:

| Name                 | Type             | Description | Default                                                                                                                                                                                                                                                                                                 |
| -------------------- | ---------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `data_provider_info` | \`dict[str, Any] | None\`      | Optional dict containing data provider information: - name: Provider name (e.g., "coingecko", "chainlink") - version: Provider version if available - data_fetched_at: ISO timestamp when data was fetched - cache_hit_rate: Optional cache hit rate percentage - additional provider-specific metadata |

Returns:

| Type             | Description                                                  |
| ---------------- | ------------------------------------------------------------ |
| `dict[str, Any]` | Dictionary with full config and metadata for reproducibility |

### calculate_config_hash

```
calculate_config_hash() -> str
```

Calculate a deterministic hash of the configuration for verification.

The hash is calculated from all configuration parameters that affect backtest results, excluding runtime metadata like timestamps. This enables verification that a backtest was run with identical config.

The hash uses SHA-256 and includes:

- Time range (start_time, end_time, interval_seconds)
- Capital settings (initial_capital_usd)
- Model settings (fee_model, slippage_model)
- Gas settings (include_gas_costs, gas_price_gwei)
- Execution settings (inclusion_delay_blocks)
- Chain and token settings
- Metrics settings (benchmark_token, risk_free_rate, etc.)
- Margin settings
- Other simulation parameters

Returns:

| Type  | Description                            |
| ----- | -------------------------------------- |
| `str` | 64-character hex string (SHA-256 hash) |

Example

> > > config = PnLBacktestConfig(...) hash1 = config.calculate_config_hash()
> > >
> > > #### Same config produces same hash
> > >
> > > config2 = PnLBacktestConfig(...) # identical params hash2 = config2.calculate_config_hash() assert hash1 == hash2

### from_dict

```
from_dict(data: dict[str, Any]) -> PnLBacktestConfig
```

Deserialize from dictionary.

Parameters:

| Name   | Type             | Description                         | Default    |
| ------ | ---------------- | ----------------------------------- | ---------- |
| `data` | `dict[str, Any]` | Dictionary containing config fields | *required* |

Returns:

| Type                | Description                |
| ------------------- | -------------------------- |
| `PnLBacktestConfig` | PnLBacktestConfig instance |

### __repr__

```
__repr__() -> str
```

Return a human-readable representation.

## Paper Trader

### PaperTrader

## almanak.framework.backtesting.PaperTrader

```
PaperTrader(
    fork_manager: RollingForkManager,
    portfolio_tracker: PaperPortfolioTracker,
    config: PaperTraderConfig,
    event_callback: PaperTradeEventCallback | None = None,
)
```

Main paper trading engine for fork-based strategy simulation.

The PaperTrader executes strategy decisions on local Anvil forks, providing accurate simulation of real DeFi execution. It:

1. Manages fork lifecycle via RollingForkManager
1. Calls strategy.decide() at configured intervals
1. Compiles intents to ActionBundles
1. Executes transactions on the fork via ExecutionOrchestrator
1. Tracks portfolio state and records trades
1. Calculates comprehensive performance metrics

Attributes:

| Name                | Type                      | Description                                 |
| ------------------- | ------------------------- | ------------------------------------------- |
| `fork_manager`      | `RollingForkManager`      | RollingForkManager for Anvil fork lifecycle |
| `portfolio_tracker` | `PaperPortfolioTracker`   | PaperPortfolioTracker for state tracking    |
| `config`            | `PaperTraderConfig`       | PaperTraderConfig with execution parameters |
| `event_callback`    | \`PaperTradeEventCallback | None\`                                      |

Example

trader = PaperTrader( fork_manager=fork_manager, portfolio_tracker=portfolio_tracker, config=PaperTraderConfig(tick_interval_seconds=60), )

### Run for 1 hour

result = await trader.run(my_strategy, duration_seconds=3600)

### Or run indefinitely until stopped

await trader.start(my_strategy)

### ... later ...

await trader.stop()

### __post_init__

```
__post_init__() -> None
```

Validate configuration after initialization.

### run

```
run(
    strategy: PaperTradeableStrategy,
    duration_seconds: float | None = None,
    max_ticks: int | None = None,
) -> BacktestResult
```

Run a paper trading session for the specified duration.

This is the main entry point for paper trading. It:

1. Initializes the fork and orchestrator
1. Runs the trading loop for the specified duration
1. Calculates and returns metrics

Parameters:

| Name               | Type                     | Description             | Default                                             |
| ------------------ | ------------------------ | ----------------------- | --------------------------------------------------- |
| `strategy`         | `PaperTradeableStrategy` | Strategy to paper trade | *required*                                          |
| `duration_seconds` | \`float                  | None\`                  | Maximum duration in seconds (None = config default) |
| `max_ticks`        | \`int                    | None\`                  | Maximum number of ticks (None = no limit)           |

Returns:

| Type             | Description                                          |
| ---------------- | ---------------------------------------------------- |
| `BacktestResult` | BacktestResult with comprehensive metrics and trades |

Raises:

| Type           | Description        |
| -------------- | ------------------ |
| `RuntimeError` | If already running |

### start

```
start(strategy: PaperTradeableStrategy) -> None
```

Start continuous paper trading until stop() is called.

This method runs paper trading indefinitely. Call stop() to end the session gracefully.

Parameters:

| Name       | Type                     | Description             | Default    |
| ---------- | ------------------------ | ----------------------- | ---------- |
| `strategy` | `PaperTradeableStrategy` | Strategy to paper trade | *required* |

Raises:

| Type           | Description        |
| -------------- | ------------------ |
| `RuntimeError` | If already running |

### stop

```
stop() -> None
```

Stop the current paper trading session.

Signals the trading loop to exit gracefully. The current tick will complete before stopping.

### is_running

```
is_running() -> bool
```

Check if paper trading is currently active.

Returns:

| Type   | Description                  |
| ------ | ---------------------------- |
| `bool` | True if a session is running |

### tick

```
tick() -> PaperTrade | None
```

Execute one trading cycle (tick) manually.

This method allows manual tick execution for testing or custom integration. It performs one complete trading cycle:

1. Optionally resets fork to latest block (based on config)
1. Creates MarketSnapshot from current fork state
1. Calls strategy.decide(snapshot) to get intent
1. If intent returned (non-HOLD), executes via orchestrator on fork
1. Records trade result in portfolio_tracker
1. Handles and records errors gracefully

Prerequisites

- PaperTrader must be initialized (call start() or run() first)
- A strategy must be set via \_current_strategy

Returns:

| Type         | Description |
| ------------ | ----------- |
| \`PaperTrade | None\`      |
| \`PaperTrade | None\`      |

Example

#### Manual tick control

trader = PaperTrader(fork_manager, portfolio_tracker, config) await trader.\_initialize_fork() await trader.\_initialize_orchestrator() trader.\_current_strategy = my_strategy trader.\_running = True

#### Execute single tick

trade = await trader.tick() if trade: print(f"Trade executed: {trade.tx_hash}")

### run_loop

```
run_loop(
    strategy: PaperTradeableStrategy,
    max_ticks: int | None = None,
) -> PaperTradingSummary
```

Run a paper trading session with a simple tick loop.

This method implements the classic paper trading loop pattern:

1. Initialize fork and orchestrator
1. Loop: call tick(), sleep for tick_interval_seconds
1. Stop when max_ticks reached or \_running becomes False
1. Cleanup in finally block

Unlike run(), which returns a comprehensive BacktestResult, this method returns a simpler PaperTradingSummary focused on trade statistics.

Parameters:

| Name        | Type                     | Description             | Default                                                                                                        |
| ----------- | ------------------------ | ----------------------- | -------------------------------------------------------------------------------------------------------------- |
| `strategy`  | `PaperTradeableStrategy` | Strategy to paper trade | *required*                                                                                                     |
| `max_ticks` | \`int                    | None\`                  | Maximum number of ticks to run (None = use config.max_ticks, if that's also None, runs until stop() is called) |

Returns:

| Type                  | Description                                                   |
| --------------------- | ------------------------------------------------------------- |
| `PaperTradingSummary` | PaperTradingSummary with session statistics and trade details |

Raises:

| Type           | Description        |
| -------------- | ------------------ |
| `RuntimeError` | If already running |

Example

trader = PaperTrader(fork_manager, portfolio_tracker, config) summary = await trader.run_loop(my_strategy, max_ticks=100) print(summary.summary())

### PaperTraderConfig

## almanak.framework.backtesting.PaperTraderConfig

```
PaperTraderConfig(
    chain: str,
    rpc_url: str,
    strategy_id: str,
    initial_eth: Decimal = Decimal("10"),
    initial_tokens: dict[str, Decimal] = dict(),
    tick_interval_seconds: int = 60,
    max_ticks: int | None = None,
    anvil_port: int = 8546,
    reset_fork_every_tick: bool = True,
    startup_timeout_seconds: float = 30.0,
    auto_impersonate: bool = True,
    block_time: int | None = None,
    wallet_address: str | None = None,
    log_trades: bool = True,
    log_level: str = "INFO",
    price_source: Literal[
        "coingecko", "chainlink", "twap", "auto"
    ] = "auto",
    strict_price_mode: bool = True,
    allow_hardcoded_fallback: bool | None = None,
)
```

Configuration for a paper trading session.

Controls all parameters of the paper trading session including chain, initial balances, tick intervals, and Anvil fork settings.

Paper trading executes real transactions on a local Anvil fork, allowing strategies to be validated with actual DeFi protocol interactions before deployment with real capital.

Attributes:

| Name                      | Type                                                | Description                                                     |
| ------------------------- | --------------------------------------------------- | --------------------------------------------------------------- |
| `chain`                   | `str`                                               | Blockchain to paper trade on (e.g., "arbitrum", "ethereum")     |
| `rpc_url`                 | `str`                                               | Archive RPC URL to fork from (Alchemy, Infura, etc.)            |
| `strategy_id`             | `str`                                               | Identifier of the strategy being tested                         |
| `initial_eth`             | `Decimal`                                           | Initial ETH balance for the paper wallet (default: 10)          |
| `initial_tokens`          | `dict[str, Decimal]`                                | Dict of token symbol to amount for initial balances             |
| `tick_interval_seconds`   | `int`                                               | Time between trading ticks in seconds (default: 60)             |
| `max_ticks`               | \`int                                               | None\`                                                          |
| `anvil_port`              | `int`                                               | Port to run Anvil on (default: 8546)                            |
| `reset_fork_every_tick`   | `bool`                                              | Whether to reset fork to latest block each tick (default: True) |
| `startup_timeout_seconds` | `float`                                             | Timeout for Anvil startup (default: 30)                         |
| `auto_impersonate`        | `bool`                                              | Enable auto-impersonation for any address (default: True)       |
| `block_time`              | \`int                                               | None\`                                                          |
| `wallet_address`          | \`str                                               | None\`                                                          |
| `log_trades`              | `bool`                                              | Whether to log individual trades (default: True)                |
| `log_level`               | `str`                                               | Logging level for paper trader (default: "INFO")                |
| `price_source`            | `Literal['coingecko', 'chainlink', 'twap', 'auto']` | Price source to use ('coingecko', 'chainlink', 'twap', 'auto')  |

Example

config = PaperTraderConfig( chain="arbitrum", rpc_url="https://arb1.arbitrum.io/rpc", strategy_id="momentum_v1", initial_eth=Decimal("10"), initial_tokens={"USDC": Decimal("10000")}, ) print(f"Chain: {config.chain} (ID: {config.chain_id})") print(f"Max duration: {config.max_duration_seconds}s")

### price_source

```
price_source: Literal[
    "coingecko", "chainlink", "twap", "auto"
] = "auto"
```

Price source to use for portfolio valuation.

Options

- 'coingecko': Use CoinGecko API for market prices. Best for: General tokens, off-chain price feeds, no RPC needed.
- 'chainlink': Use Chainlink oracles for on-chain prices. Best for: Major tokens with Chainlink feeds, trustless pricing.
- 'twap': Use time-weighted average price from DEX pools. Best for: On-chain pricing, newer tokens, DEX-native prices.
- 'auto' (default): Automatic fallback chain - tries Chainlink first, falls back to TWAP, then CoinGecko if others fail.

### strict_price_mode

```
strict_price_mode: bool = True
```

Whether to fail when price providers cannot return a price.

When True (default): Raises ValueError if all price providers fail for a token. This is the institutional-grade setting that ensures all prices are from real data sources. Use this for production backtests where accuracy is critical. Error messages include the failed token and chain for debugging.

When False: Falls back to hardcoded prices for common tokens (ETH=$3000, BTC=$60000, etc.) when all price providers fail. This allows backtests to complete but may produce inaccurate results. Only use this for development/testing where price accuracy is not critical.

Note: This is the inverse of the deprecated allow_hardcoded_fallback field. If both are set, strict_price_mode takes precedence.

Environment variable: Set ALMANAK_ALLOW_HARDCODED_PRICES=1 to override strict_price_mode=False for testing scenarios.

### allow_hardcoded_fallback

```
allow_hardcoded_fallback: bool | None = None
```

DEPRECATED: Use strict_price_mode instead.

This field is kept for backward compatibility. If set, it will be converted to the equivalent strict_price_mode value (allow_hardcoded_fallback=False is equivalent to strict_price_mode=True).

Will be removed in a future version.

### chain_id

```
chain_id: int
```

Get the chain ID for the configured chain.

### max_duration_seconds

```
max_duration_seconds: int | None
```

Get the maximum duration in seconds, or None if indefinite.

### max_duration_minutes

```
max_duration_minutes: float | None
```

Get the maximum duration in minutes, or None if indefinite.

### max_duration_hours

```
max_duration_hours: float | None
```

Get the maximum duration in hours, or None if indefinite.

### tick_interval_minutes

```
tick_interval_minutes: float
```

Get the tick interval in minutes.

### fork_rpc_url

```
fork_rpc_url: str
```

Get the local fork RPC URL.

### __post_init__

```
__post_init__() -> None
```

Validate configuration after initialization.

### get_initial_balances

```
get_initial_balances() -> dict[str, Decimal]
```

Get all initial balances including ETH.

Returns:

| Type                 | Description                                          |
| -------------------- | ---------------------------------------------------- |
| `dict[str, Decimal]` | Dictionary of token symbol to initial balance amount |

### to_dict

```
to_dict() -> dict[str, Any]
```

Serialize to dictionary.

### from_dict

```
from_dict(data: dict[str, Any]) -> PaperTraderConfig
```

Deserialize from dictionary.

Parameters:

| Name   | Type             | Description                         | Default    |
| ------ | ---------------- | ----------------------------------- | ---------- |
| `data` | `dict[str, Any]` | Dictionary containing config fields | *required* |

Returns:

| Type                | Description                |
| ------------------- | -------------------------- |
| `PaperTraderConfig` | PaperTraderConfig instance |

### __repr__

```
__repr__() -> str
```

Return a human-readable representation.

## Results

### BacktestResult

## almanak.framework.backtesting.BacktestResult

```
BacktestResult(
    engine: BacktestEngine,
    strategy_id: str,
    start_time: datetime,
    end_time: datetime,
    metrics: BacktestMetrics,
    trades: list[TradeRecord] = list(),
    equity_curve: list[EquityPoint] = list(),
    initial_capital_usd: Decimal = Decimal("10000"),
    final_capital_usd: Decimal = Decimal("10000"),
    chain: str = "arbitrum",
    run_started_at: datetime | None = None,
    run_ended_at: datetime | None = None,
    run_duration_seconds: float = 0.0,
    config: dict[str, Any] = dict(),
    error: str | None = None,
    lending_liquidations: list[
        LendingLiquidationEvent
    ] = list(),
    aggregated_portfolio_view: AggregatedPortfolioView
    | None = None,
    reconciliation_events: list[
        ReconciliationEvent
    ] = list(),
    walk_forward_results: WalkForwardResult | None = None,
    monte_carlo_results: MonteCarloSimulationResult
    | None = None,
    crisis_results: CrisisMetrics | None = None,
    errors: list[dict[str, Any]] = list(),
    backtest_id: str | None = None,
    phase_timings: list[dict[str, Any]] = list(),
    config_hash: str | None = None,
    execution_delayed_at_end: int = 0,
    data_source_capabilities: dict[
        str, HistoricalDataCapability
    ] = dict(),
    data_source_warnings: list[str] = list(),
    data_quality: DataQualityReport | None = None,
    institutional_compliance: bool = True,
    compliance_violations: list[str] = list(),
    fallback_usage: dict[str, int] = dict(),
    preflight_report: PreflightReport | None = None,
    preflight_passed: bool = True,
    gas_prices_used: list[GasPriceRecord] = list(),
    gas_price_summary: GasPriceSummary | None = None,
    parameter_sources: ParameterSourceTracker | None = None,
    accuracy_estimate: AccuracyEstimate | None = None,
    data_coverage_metrics: DataCoverageMetrics
    | None = None,
)
```

Complete results from a backtest run.

This model is used by both the PnL Backtester and Paper Trader to provide consistent result formatting and analysis.

Attributes:

| Name                        | Type                                  | Description                                                                                                                                                                                                                                                                                         |
| --------------------------- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `engine`                    | `BacktestEngine`                      | Which backtesting engine was used (pnl or paper)                                                                                                                                                                                                                                                    |
| `strategy_id`               | `str`                                 | Identifier of the strategy being tested                                                                                                                                                                                                                                                             |
| `start_time`                | `datetime`                            | When the backtest started (simulation time)                                                                                                                                                                                                                                                         |
| `end_time`                  | `datetime`                            | When the backtest ended (simulation time)                                                                                                                                                                                                                                                           |
| `metrics`                   | `BacktestMetrics`                     | Calculated performance metrics                                                                                                                                                                                                                                                                      |
| `trades`                    | `list[TradeRecord]`                   | List of all trade records                                                                                                                                                                                                                                                                           |
| `equity_curve`              | `list[EquityPoint]`                   | Portfolio value over time                                                                                                                                                                                                                                                                           |
| `initial_capital_usd`       | `Decimal`                             | Starting capital in USD                                                                                                                                                                                                                                                                             |
| `final_capital_usd`         | `Decimal`                             | Ending capital in USD                                                                                                                                                                                                                                                                               |
| `chain`                     | `str`                                 | Target blockchain (arbitrum, base, etc.)                                                                                                                                                                                                                                                            |
| `run_started_at`            | \`datetime                            | None\`                                                                                                                                                                                                                                                                                              |
| `run_ended_at`              | \`datetime                            | None\`                                                                                                                                                                                                                                                                                              |
| `run_duration_seconds`      | `float`                               | Wall clock duration of the backtest run                                                                                                                                                                                                                                                             |
| `config`                    | `dict[str, Any]`                      | Configuration used for the backtest                                                                                                                                                                                                                                                                 |
| `error`                     | \`str                                 | None\`                                                                                                                                                                                                                                                                                              |
| `lending_liquidations`      | `list[LendingLiquidationEvent]`       | List of lending liquidation events that occurred                                                                                                                                                                                                                                                    |
| `aggregated_portfolio_view` | \`AggregatedPortfolioView             | None\`                                                                                                                                                                                                                                                                                              |
| `reconciliation_events`     | `list[ReconciliationEvent]`           | List of position reconciliation events (discrepancies detected)                                                                                                                                                                                                                                     |
| `walk_forward_results`      | \`WalkForwardResult                   | None\`                                                                                                                                                                                                                                                                                              |
| `monte_carlo_results`       | \`MonteCarloSimulationResult          | None\`                                                                                                                                                                                                                                                                                              |
| `crisis_results`            | \`CrisisMetrics                       | None\`                                                                                                                                                                                                                                                                                              |
| `errors`                    | `list[dict[str, Any]]`                | List of error records as dictionaries with timestamps and context for debugging and analysis. Each error dict contains: timestamp, error_type, error_message, classification (with error_type, category, is_recoverable, is_fatal, is_non_critical, suggested_action), context, and handled action. |
| `backtest_id`               | \`str                                 | None\`                                                                                                                                                                                                                                                                                              |
| `phase_timings`             | `list[dict[str, Any]]`                | List of phase timing records showing how long each backtest phase took. Each record contains: phase_name, start_time, end_time, duration_seconds, error. Useful for performance analysis and identifying bottlenecks.                                                                               |
| `config_hash`               | \`str                                 | None\`                                                                                                                                                                                                                                                                                              |
| `execution_delayed_at_end`  | `int`                                 | Count of pending intents executed at simulation end. These were queued due to inclusion_delay_blocks > 0 and executed with the last market state when the simulation completed.                                                                                                                     |
| `data_source_capabilities`  | `dict[str, HistoricalDataCapability]` | Dictionary mapping data provider names to their HistoricalDataCapability enum values. Shows which providers were used and their ability to provide accurate historical data (FULL, CURRENT_ONLY, PRE_CACHE). Useful for understanding potential data quality limitations in the backtest.           |
| `data_source_warnings`      | `list[str]`                           | List of warning messages about data source limitations. Generated when providers with CURRENT_ONLY or PRE_CACHE capability are used, as these may affect backtest accuracy.                                                                                                                         |
| `data_quality`              | \`DataQualityReport                   | None\`                                                                                                                                                                                                                                                                                              |
| `institutional_compliance`  | `bool`                                | Whether the backtest run meets institutional standards. Set to False when any strict reproducibility, data quality, or compliance check fails. Use compliance_violations to see which checks failed.                                                                                                |
| `compliance_violations`     | `list[str]`                           | List of compliance violations that caused institutional_compliance to be set to False. Each entry describes a specific compliance failure such as "CURRENT_ONLY data provider used", "Symbol mapping failed for 0x...", "Data coverage below minimum threshold (95% < 98%)".                        |
| `fallback_usage`            | `dict[str, int]`                      | Dictionary tracking count of each fallback type used during the backtest. Keys include: "hardcoded_price", "default_gas_price", "default_usd_amount". Empty dict means no fallbacks were used, which is the desired state for institutional-grade backtests.                                        |
| `preflight_report`          | \`PreflightReport                     | None\`                                                                                                                                                                                                                                                                                              |
| `preflight_passed`          | `bool`                                | Whether preflight validation passed (True) or failed (False). Defaults to True if preflight validation was disabled. This is a convenience field for quick checks - for full details, inspect preflight_report.                                                                                     |
| `parameter_sources`         | \`ParameterSourceTracker              | None\`                                                                                                                                                                                                                                                                                              |
| `accuracy_estimate`         | \`AccuracyEstimate                    | None\`                                                                                                                                                                                                                                                                                              |

### gas_prices_used

```
gas_prices_used: list[GasPriceRecord] = field(
    default_factory=list
)
```

Optional detailed gas price records for each trade during the backtest.

When track_gas_prices=True in config, this list contains a GasPriceRecord for each trade showing the gas price used, its source, and USD cost. Useful for detailed gas cost analysis but may increase result size.

### gas_price_summary

```
gas_price_summary: GasPriceSummary | None = None
```

Summary statistics for gas prices used during the backtest.

Contains min, max, mean, std of gas prices in gwei plus source breakdown. Always populated when trades occurred, regardless of track_gas_prices setting.

### parameter_sources

```
parameter_sources: ParameterSourceTracker | None = None
```

Tracks the source of all configuration parameters for audit purposes.

Contains detailed records of where each configuration value came from:

- Config parameters: default, config_file, env_var, explicit
- Liquidation thresholds: asset_specific, protocol_default, global_default
- APY/funding rates: historical, fixed, provider

This information is critical for institutional compliance and audit trails. When institutional_mode=True, this is always populated. The tracker provides summary dicts (config_sources, liquidation_sources, apy_funding_sources) for quick inspection and a full list of ParameterSourceRecord objects for detailed analysis.

### accuracy_estimate

```
accuracy_estimate: AccuracyEstimate | None = None
```

Estimated accuracy of this backtest based on strategy type and data quality.

Provides a quick reference showing expected accuracy range (e.g., "90-95%") based on the detected strategy type (LP, perp, lending, arbitrage, spot) and the data quality tier used (FULL, PRE_CACHE, CURRENT_ONLY).

The estimate is derived from the ACCURACY_MATRIX which is based on documented accuracy limitations and golden test tolerances. See docs/ACCURACY_LIMITATIONS.md for the full accuracy matrix and methodology.

Example usage

if result.accuracy_estimate: print(f"Expected accuracy: {result.accuracy_estimate.confidence_interval}") print(f"Primary error source: {result.accuracy_estimate.primary_error_source}")

### data_coverage_metrics

```
data_coverage_metrics: DataCoverageMetrics | None = None
```

Data coverage metrics tracking confidence levels across all position types.

Provides detailed breakdown of data quality for LP, Perp, Lending, and Slippage calculations. Includes confidence level breakdowns (high/medium/low) and data sources used for each position type.

The data_coverage_pct property gives overall percentage of HIGH confidence data points across all categories.

Example usage

if result.data_coverage_metrics: print(f"Data coverage: {result.data_coverage_metrics.data_coverage_pct:.1f}%") print(f"LP HIGH: {result.data_coverage_metrics.lp_metrics.high_confidence_pct:.1f}%")

### success

```
success: bool
```

Check if backtest completed successfully.

### simulation_duration_days

```
simulation_duration_days: float
```

Get the simulated duration in days.

### total_return_pct

```
total_return_pct: Decimal
```

Get total return as a percentage.

### used_any_fallback

```
used_any_fallback: bool
```

Check if any fallbacks were used during the backtest.

Returns True if the fallback_usage dict has any non-zero counts. When this is True, the backtest may have reduced accuracy due to using fallback values instead of real market data.

### add_error

```
add_error(error_dict: dict[str, Any]) -> None
```

Add an error record and log it with timestamp and context.

This method is used to track errors that occurred during the backtest, along with their timestamps, classification, and handling.

Parameters:

| Name         | Type             | Description                                                                                                                                             | Default    |
| ------------ | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `error_dict` | `dict[str, Any]` | Serialized error record from ErrorRecord.to_dict() or equivalent dict with keys: timestamp, error_type, error_message, classification, context, handled | *required* |

### summary

```
summary() -> str
```

Generate a human-readable summary of backtest results.

Returns:

| Type  | Description                                       |
| ----- | ------------------------------------------------- |
| `str` | Multi-line string with formatted backtest results |

### to_dict

```
to_dict() -> dict[str, Any]
```

Serialize to dictionary.

### from_dict

```
from_dict(data: dict[str, Any]) -> BacktestResult
```

Deserialize from dictionary.

Parameters:

| Name   | Type             | Description                                    | Default    |
| ------ | ---------------- | ---------------------------------------------- | ---------- |
| `data` | `dict[str, Any]` | Dictionary with serialized BacktestResult data | *required* |

Returns:

| Type             | Description             |
| ---------------- | ----------------------- |
| `BacktestResult` | BacktestResult instance |

### BacktestMetrics

## almanak.framework.backtesting.BacktestMetrics

```
BacktestMetrics(
    total_pnl_usd: Decimal = Decimal("0"),
    net_pnl_usd: Decimal = Decimal("0"),
    sharpe_ratio: Decimal = Decimal("0"),
    max_drawdown_pct: Decimal = Decimal("0"),
    win_rate: Decimal = Decimal("0"),
    total_trades: int = 0,
    profit_factor: Decimal = Decimal("0"),
    total_return_pct: Decimal = Decimal("0"),
    annualized_return_pct: Decimal = Decimal("0"),
    total_fees_usd: Decimal = Decimal("0"),
    total_slippage_usd: Decimal = Decimal("0"),
    total_gas_usd: Decimal = Decimal("0"),
    winning_trades: int = 0,
    losing_trades: int = 0,
    avg_trade_pnl_usd: Decimal = Decimal("0"),
    largest_win_usd: Decimal = Decimal("0"),
    largest_loss_usd: Decimal = Decimal("0"),
    avg_win_usd: Decimal = Decimal("0"),
    avg_loss_usd: Decimal = Decimal("0"),
    volatility: Decimal = Decimal("0"),
    sortino_ratio: Decimal = Decimal("0"),
    calmar_ratio: Decimal = Decimal("0"),
    total_fees_earned_usd: Decimal = Decimal("0"),
    fees_by_pool: dict[str, Decimal] = dict(),
    lp_fee_confidence_breakdown: dict[str, int] = dict(),
    total_funding_paid: Decimal = Decimal("0"),
    total_funding_received: Decimal = Decimal("0"),
    liquidations_count: int = 0,
    liquidation_losses_usd: Decimal = Decimal("0"),
    max_margin_utilization: Decimal = Decimal("0"),
    total_interest_earned: Decimal = Decimal("0"),
    total_interest_paid: Decimal = Decimal("0"),
    min_health_factor: Decimal = Decimal("999"),
    health_factor_warnings: int = 0,
    avg_gas_price_gwei: Decimal = Decimal("0"),
    max_gas_price_gwei: Decimal = Decimal("0"),
    total_gas_cost_usd: Decimal = Decimal("0"),
    total_mev_cost_usd: Decimal = Decimal("0"),
    total_leverage: Decimal = Decimal("0"),
    max_net_delta: dict[str, Decimal] = dict(),
    correlation_risk: Decimal | None = None,
    liquidation_cascade_risk: Decimal = Decimal("0"),
    information_ratio: Decimal | None = None,
    beta: Decimal | None = None,
    alpha: Decimal | None = None,
    benchmark_return: Decimal | None = None,
    pnl_by_protocol: dict[str, Decimal] = dict(),
    pnl_by_intent_type: dict[str, Decimal] = dict(),
    pnl_by_asset: dict[str, Decimal] = dict(),
    realized_pnl: Decimal = Decimal("0"),
    unrealized_pnl: Decimal = Decimal("0"),
)
```

Performance metrics calculated from backtest results.

All financial values are in USD. Ratios are decimal (0.1 = 10%).

Attributes:

| Name                       | Type                 | Description                                                                                 |
| -------------------------- | -------------------- | ------------------------------------------------------------------------------------------- |
| `total_pnl_usd`            | `Decimal`            | Total PnL before execution costs                                                            |
| `net_pnl_usd`              | `Decimal`            | Net PnL after all execution costs                                                           |
| `sharpe_ratio`             | `Decimal`            | Risk-adjusted return (annualized, assuming 0 risk-free rate)                                |
| `max_drawdown_pct`         | `Decimal`            | Maximum peak-to-trough decline as decimal (0.1 = 10%)                                       |
| `win_rate`                 | `Decimal`            | Percentage of profitable trades as decimal (0.6 = 60%)                                      |
| `total_trades`             | `int`                | Total number of trades executed                                                             |
| `profit_factor`            | `Decimal`            | Ratio of gross profit to gross loss                                                         |
| `total_return_pct`         | `Decimal`            | Total return as decimal (0.15 = 15% return)                                                 |
| `annualized_return_pct`    | `Decimal`            | Annualized return as decimal                                                                |
| `total_fees_usd`           | `Decimal`            | Total protocol fees paid                                                                    |
| `total_slippage_usd`       | `Decimal`            | Total slippage incurred                                                                     |
| `total_gas_usd`            | `Decimal`            | Total gas costs                                                                             |
| `winning_trades`           | `int`                | Number of profitable trades                                                                 |
| `losing_trades`            | `int`                | Number of losing trades                                                                     |
| `avg_trade_pnl_usd`        | `Decimal`            | Average PnL per trade                                                                       |
| `largest_win_usd`          | `Decimal`            | Largest single winning trade                                                                |
| `largest_loss_usd`         | `Decimal`            | Largest single losing trade                                                                 |
| `avg_win_usd`              | `Decimal`            | Average winning trade PnL                                                                   |
| `avg_loss_usd`             | `Decimal`            | Average losing trade PnL                                                                    |
| `volatility`               | `Decimal`            | Annualized volatility of returns as decimal                                                 |
| `sortino_ratio`            | `Decimal`            | Downside risk-adjusted return                                                               |
| `calmar_ratio`             | `Decimal`            | Return / max drawdown                                                                       |
| `total_fees_earned_usd`    | `Decimal`            | Total fees earned from LP positions in USD                                                  |
| `fees_by_pool`             | `dict[str, Decimal]` | Dict mapping pool identifier to fees earned in USD                                          |
| `total_funding_paid`       | `Decimal`            | Total funding payments made from perp positions in USD                                      |
| `total_funding_received`   | `Decimal`            | Total funding payments received by perp positions in USD                                    |
| `liquidations_count`       | `int`                | Number of liquidation events that occurred                                                  |
| `liquidation_losses_usd`   | `Decimal`            | Total losses from liquidations in USD                                                       |
| `max_margin_utilization`   | `Decimal`            | Maximum margin utilization ratio observed during backtest (0-1)                             |
| `total_interest_earned`    | `Decimal`            | Total interest earned from lending supply positions in USD                                  |
| `total_interest_paid`      | `Decimal`            | Total interest paid on borrow positions in USD                                              |
| `min_health_factor`        | `Decimal`            | Minimum health factor observed for lending positions during backtest (lower = more risk)    |
| `health_factor_warnings`   | `int`                | Number of times health factor dropped below warning threshold                               |
| `avg_gas_price_gwei`       | `Decimal`            | Average gas price in gwei across all trades (for cost analysis)                             |
| `max_gas_price_gwei`       | `Decimal`            | Maximum gas price in gwei observed during backtest (for peak cost analysis)                 |
| `total_gas_cost_usd`       | `Decimal`            | Total gas costs in USD (same as total_gas_usd, kept for API consistency)                    |
| `total_mev_cost_usd`       | `Decimal`            | Total estimated MEV (sandwich attack) costs in USD across all trades                        |
| `total_leverage`           | `Decimal`            | Total portfolio leverage ratio (sum of all position notionals / equity)                     |
| `max_net_delta`            | `dict[str, Decimal]` | Maximum net delta exposure observed per asset (token symbol -> max delta)                   |
| `correlation_risk`         | \`Decimal            | None\`                                                                                      |
| `liquidation_cascade_risk` | `Decimal`            | Risk of cascading liquidations across protocols (0-1, higher = more risk)                   |
| `information_ratio`        | \`Decimal            | None\`                                                                                      |
| `beta`                     | \`Decimal            | None\`                                                                                      |
| `alpha`                    | \`Decimal            | None\`                                                                                      |
| `benchmark_return`         | \`Decimal            | None\`                                                                                      |
| `pnl_by_protocol`          | `dict[str, Decimal]` | PnL breakdown by protocol (e.g., {"uniswap_v3": Decimal("100"), "aave_v3": Decimal("-50")}) |
| `pnl_by_intent_type`       | `dict[str, Decimal]` | PnL breakdown by intent type (e.g., {"SWAP": Decimal("75"), "LP_OPEN": Decimal("25")})      |
| `pnl_by_asset`             | `dict[str, Decimal]` | PnL breakdown by asset (e.g., {"ETH": Decimal("80"), "USDC": Decimal("20")})                |
| `realized_pnl`             | `Decimal`            | Total realized PnL from closed positions in USD                                             |
| `unrealized_pnl`           | `Decimal`            | Total unrealized PnL from open positions in USD                                             |

### lp_fee_confidence_breakdown

```
lp_fee_confidence_breakdown: dict[str, int] = field(
    default_factory=dict
)
```

Count of LP positions by fee confidence level.

Example: {"high": 2, "medium": 1, "low": 0}

- high: Fees calculated using actual historical volume data from subgraph
- medium: Fees calculated using interpolated or estimated data
- low: Fees calculated using multiplier heuristic

### total_execution_cost_usd

```
total_execution_cost_usd: Decimal
```

Get total execution costs (fees + slippage + gas).

### to_dict

```
to_dict() -> dict[str, Any]
```

Serialize to dictionary.

### PaperTradingSummary

## almanak.framework.backtesting.PaperTradingSummary

```
PaperTradingSummary(
    strategy_id: str,
    start_time: datetime,
    duration: timedelta,
    total_trades: int,
    successful_trades: int,
    failed_trades: int,
    chain: str = "arbitrum",
    initial_balances: dict[str, Decimal] = dict(),
    final_balances: dict[str, Decimal] = dict(),
    total_gas_used: int = 0,
    total_gas_cost_usd: Decimal = Decimal("0"),
    pnl_usd: Decimal | None = None,
    valuation_source: str = "simple",
    error_summary: dict[str, int] = dict(),
    trades: list[PaperTrade] = list(),
    errors: list[PaperTradeError] = list(),
)
```

Summary of a paper trading session.

This dataclass provides an overview of the paper trading session, including trade counts, timing, and basic performance metrics.

Attributes:

| Name                 | Type                    | Description                             |
| -------------------- | ----------------------- | --------------------------------------- |
| `strategy_id`        | `str`                   | Identifier of the strategy being tested |
| `start_time`         | `datetime`              | When the session started                |
| `duration`           | `timedelta`             | How long the session ran                |
| `total_trades`       | `int`                   | Total number of trades attempted        |
| `successful_trades`  | `int`                   | Number of successful trades             |
| `failed_trades`      | `int`                   | Number of failed trades                 |
| `end_time`           | `datetime`              | When the session ended (computed)       |
| `chain`              | `str`                   | Target blockchain                       |
| `initial_balances`   | `dict[str, Decimal]`    | Starting token balances                 |
| `final_balances`     | `dict[str, Decimal]`    | Ending token balances                   |
| `total_gas_used`     | `int`                   | Total gas consumed                      |
| `total_gas_cost_usd` | `Decimal`               | Total gas cost in USD                   |
| `pnl_usd`            | \`Decimal               | None\`                                  |
| `error_summary`      | `dict[str, int]`        | Count of errors by type                 |
| `trades`             | `list[PaperTrade]`      | List of successful trades               |
| `errors`             | `list[PaperTradeError]` | List of trade errors                    |

### end_time

```
end_time: datetime
```

Get the session end time.

### success_rate

```
success_rate: Decimal
```

Calculate the success rate as a decimal (0.0 to 1.0).

### duration_seconds

```
duration_seconds: float
```

Get duration in seconds.

### duration_minutes

```
duration_minutes: float
```

Get duration in minutes.

### duration_hours

```
duration_hours: float
```

Get duration in hours.

### trades_per_hour

```
trades_per_hour: Decimal
```

Calculate average trades per hour.

### avg_gas_per_trade

```
avg_gas_per_trade: int
```

Calculate average gas used per successful trade.

### summary

```
summary() -> str
```

Generate a human-readable summary.

Returns:

| Type  | Description                                      |
| ----- | ------------------------------------------------ |
| `str` | Multi-line string with formatted session summary |

### to_dict

```
to_dict() -> dict[str, Any]
```

Serialize to dictionary.

### from_dict

```
from_dict(data: dict[str, Any]) -> PaperTradingSummary
```

Deserialize from dictionary.

Parameters:

| Name   | Type             | Description                                         | Default    |
| ------ | ---------------- | --------------------------------------------------- | ---------- |
| `data` | `dict[str, Any]` | Dictionary with serialized PaperTradingSummary data | *required* |

Returns:

| Type                  | Description                  |
| --------------------- | ---------------------------- |
| `PaperTradingSummary` | PaperTradingSummary instance |

## Data Providers

### HistoricalDataProvider

## almanak.framework.backtesting.HistoricalDataProvider

Bases: `Protocol`

Protocol defining the interface for historical data providers.

Historical data providers are responsible for fetching price and market data for past time periods. They are used by the PnL backtesting engine to simulate strategy execution.

Implementations should handle:

- Fetching historical prices for specified tokens
- Providing OHLCV data when available
- Rate limiting and caching as needed
- Graceful handling of missing data

Example implementation

class MyDataProvider: async def get_price( self, token: str, timestamp: datetime ) -> Decimal:

# Fetch price from data source

...

```
async def get_ohlcv(
    self, token: str, start: datetime, end: datetime, interval: int
) -> list[OHLCV]:
    # Fetch OHLCV data
    ...

async def iterate(
    self, config: HistoricalDataConfig
) -> AsyncIterator[tuple[datetime, MarketState]]:
    # Yield market states for each time point
    ...
```

### provider_name

```
provider_name: str
```

Return the unique name of this data provider.

### supported_tokens

```
supported_tokens: list[str]
```

Return list of supported token symbols.

### supported_chains

```
supported_chains: list[str]
```

Return list of supported chain identifiers.

### min_timestamp

```
min_timestamp: datetime | None
```

Return the earliest timestamp with available data, or None if unknown.

### max_timestamp

```
max_timestamp: datetime | None
```

Return the latest timestamp with available data, or None if unknown.

### get_price

```
get_price(token: str, timestamp: datetime) -> Decimal
```

Get the price of a token at a specific timestamp.

Parameters:

| Name        | Type       | Description                                | Default    |
| ----------- | ---------- | ------------------------------------------ | ---------- |
| `token`     | `str`      | Token symbol (e.g., "WETH", "USDC", "ARB") | *required* |
| `timestamp` | `datetime` | The historical point in time               | *required* |

Returns:

| Type      | Description                             |
| --------- | --------------------------------------- |
| `Decimal` | Price in USD at the specified timestamp |

Raises:

| Type                    | Description                                            |
| ----------------------- | ------------------------------------------------------ |
| `ValueError`            | If price data is not available for the token/timestamp |
| `DataSourceUnavailable` | If the data source is unavailable                      |

### get_ohlcv

```
get_ohlcv(
    token: str,
    start: datetime,
    end: datetime,
    interval_seconds: int = 3600,
) -> list[OHLCV]
```

Get OHLCV data for a token over a time range.

Parameters:

| Name               | Type       | Description                                         | Default    |
| ------------------ | ---------- | --------------------------------------------------- | ---------- |
| `token`            | `str`      | Token symbol (e.g., "WETH", "USDC", "ARB")          | *required* |
| `start`            | `datetime` | Start of the time range (inclusive)                 | *required* |
| `end`              | `datetime` | End of the time range (inclusive)                   | *required* |
| `interval_seconds` | `int`      | Candle interval in seconds (default: 3600 = 1 hour) | `3600`     |

Returns:

| Type          | Description                                              |
| ------------- | -------------------------------------------------------- |
| `list[OHLCV]` | List of OHLCV data points, sorted by timestamp ascending |

Raises:

| Type                    | Description                                  |
| ----------------------- | -------------------------------------------- |
| `ValueError`            | If data is not available for the token/range |
| `DataSourceUnavailable` | If the data source is unavailable            |

### iterate

```
iterate(
    config: HistoricalDataConfig,
) -> AsyncIterator[tuple[datetime, MarketState]]
```

Iterate through historical market states.

This is the primary method used by the backtesting engine. It yields market state snapshots at regular intervals throughout the configured time range.

Parameters:

| Name     | Type                   | Description                                               | Default    |
| -------- | ---------------------- | --------------------------------------------------------- | ---------- |
| `config` | `HistoricalDataConfig` | Configuration specifying time range, interval, and tokens | *required* |

Yields:

| Type                                          | Description                                            |
| --------------------------------------------- | ------------------------------------------------------ |
| `AsyncIterator[tuple[datetime, MarketState]]` | Tuples of (timestamp, MarketState) for each time point |

Raises:

| Type                    | Description                       |
| ----------------------- | --------------------------------- |
| `DataSourceUnavailable` | If the data source is unavailable |

Example

async for timestamp, market_state in provider.iterate(config): eth_price = market_state.get_price("WETH")

# Process market state

### HistoricalDataConfig

## almanak.framework.backtesting.HistoricalDataConfig

```
HistoricalDataConfig(
    start_time: datetime,
    end_time: datetime,
    interval_seconds: int = 3600,
    tokens: list[str] = (lambda: ["WETH", "USDC"])(),
    chains: list[str] = (lambda: ["arbitrum"])(),
    include_ohlcv: bool = True,
    include_gas_prices: bool = False,
)
```

Configuration for historical data retrieval.

Specifies the time range, interval, and tokens to fetch for a backtest simulation.

Attributes:

| Name                 | Type        | Description                                                         |
| -------------------- | ----------- | ------------------------------------------------------------------- |
| `start_time`         | `datetime`  | Start of the historical period (inclusive)                          |
| `end_time`           | `datetime`  | End of the historical period (inclusive)                            |
| `interval_seconds`   | `int`       | Time between data points in seconds (default: 3600 = 1 hour)        |
| `tokens`             | `list[str]` | List of token symbols to fetch prices for                           |
| `chains`             | `list[str]` | List of chain identifiers to fetch data for (default: ["arbitrum"]) |
| `include_ohlcv`      | `bool`      | Whether to fetch OHLCV data (default: True)                         |
| `include_gas_prices` | `bool`      | Whether to fetch historical gas prices (default: False)             |

### duration_seconds

```
duration_seconds: int
```

Get the total duration in seconds.

### duration_days

```
duration_days: float
```

Get the total duration in days.

### estimated_data_points

```
estimated_data_points: int
```

Get the estimated number of data points.

### __post_init__

```
__post_init__() -> None
```

Validate configuration after initialization.

### to_dict

```
to_dict() -> dict[str, Any]
```

Serialize to dictionary.

## Crisis Scenarios

### CrisisScenario

## almanak.framework.backtesting.CrisisScenario

```
CrisisScenario(
    name: str,
    start_date: datetime,
    end_date: datetime,
    description: str,
    warmup_days: int = DEFAULT_WARMUP_DAYS,
)
```

A historical crisis scenario for backtesting.

This dataclass represents a period of significant market stress that can be used for stress-testing trading strategies.

Attributes:

| Name          | Type       | Description                                                                                                                                                                                                                                                          |
| ------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name`        | `str`      | Unique identifier for the scenario (lowercase, underscores)                                                                                                                                                                                                          |
| `start_date`  | `datetime` | Beginning of the crisis period                                                                                                                                                                                                                                       |
| `end_date`    | `datetime` | End of the crisis period                                                                                                                                                                                                                                             |
| `description` | `str`      | Human-readable description of the crisis event                                                                                                                                                                                                                       |
| `warmup_days` | `int`      | Number of days of price history to pre-load before the crisis window starts. This allows indicator-based strategies (RSI, MACD, etc.) to compute their values before the crisis period begins. Performance metrics are calculated only for the crisis window itself. |

Properties

duration_days: Number of days in the crisis period warmup_start_date: Start date including warmup window

Example

> > > scenario = CrisisScenario( ... name="custom_crisis", ... start_date=datetime(2023, 3, 10), ... end_date=datetime(2023, 3, 15), ... description="SVB collapse", ... ) scenario.duration_days 5

### duration_days

```
duration_days: int
```

Calculate the duration of the crisis in days.

### warmup_start_date

```
warmup_start_date: datetime
```

Start date including the warmup window for indicator pre-loading.

### __post_init__

```
__post_init__() -> None
```

Validate warmup_days is non-negative.

### to_dict

```
to_dict() -> dict[str, Any]
```

Serialize to dictionary.

Returns:

| Type             | Description                                                    |
| ---------------- | -------------------------------------------------------------- |
| `dict[str, Any]` | Dictionary with scenario data suitable for JSON serialization. |

### from_dict

```
from_dict(data: dict[str, Any]) -> CrisisScenario
```

Deserialize from dictionary.

Parameters:

| Name   | Type             | Description                                    | Default    |
| ------ | ---------------- | ---------------------------------------------- | ---------- |
| `data` | `dict[str, Any]` | Dictionary with serialized CrisisScenario data | *required* |

Returns:

| Type             | Description             |
| ---------------- | ----------------------- |
| `CrisisScenario` | CrisisScenario instance |

### __str__

```
__str__() -> str
```

Human-readable string representation.

## Parallel Execution

## almanak.framework.backtesting.run_parallel_backtests

```
run_parallel_backtests(
    configs: list[PnLBacktestConfig],
    strategy_factory: Callable[[], Any],
    data_provider_factory: Callable[[], Any],
    backtester_factory: Callable[
        [Any, dict[str, Any], dict[str, Any]], Any
    ],
    fee_models: dict[str, Any] | None = None,
    slippage_models: dict[str, Any] | None = None,
    workers: int | None = None,
) -> list[ParallelBacktestResult]
```

Run multiple backtests in parallel using a process pool.

This function distributes backtest execution across multiple processes for improved performance on multi-core systems. Each backtest runs in its own process with its own instances of strategy, data provider, and backtester created via factory functions.

Parameters:

| Name                    | Type                                                   | Description                                                                                                         | Default                                                         |
| ----------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- |
| `configs`               | `list[PnLBacktestConfig]`                              | List of PnLBacktestConfig objects to run                                                                            | *required*                                                      |
| `strategy_factory`      | `Callable[[], Any]`                                    | Factory function that returns a new strategy instance. Must be picklable (e.g., a module-level function).           | *required*                                                      |
| `data_provider_factory` | `Callable[[], Any]`                                    | Factory function that returns a new data provider. Must be picklable (e.g., a module-level function).               | *required*                                                      |
| `backtester_factory`    | `Callable[[Any, dict[str, Any], dict[str, Any]], Any]` | Factory function that returns a new PnLBacktester. Takes (data_provider, fee_models, slippage_models) as arguments. | *required*                                                      |
| `fee_models`            | \`dict[str, Any]                                       | None\`                                                                                                              | Optional dict of fee models to pass to backtester factory.      |
| `slippage_models`       | \`dict[str, Any]                                       | None\`                                                                                                              | Optional dict of slippage models to pass to backtester factory. |
| `workers`               | \`int                                                  | None\`                                                                                                              | Number of worker processes. Defaults to CPU count - 1.          |

Returns:

| Type                           | Description                                                        |
| ------------------------------ | ------------------------------------------------------------------ |
| `list[ParallelBacktestResult]` | List of ParallelBacktestResult in the same order as input configs. |
| `list[ParallelBacktestResult]` | Each result indicates success or failure with associated data.     |

Raises:

| Type         | Description              |
| ------------ | ------------------------ |
| `ValueError` | If configs list is empty |

Example

def create_strategy(): return MyStrategy(param1=10, param2=0.5)

def create_data_provider(): return CoinGeckoDataProvider()

def create_backtester(provider, fee_models, slippage_models): return PnLBacktester(provider, fee_models, slippage_models)

results = await run_parallel_backtests( configs=[config1, config2, config3], strategy_factory=create_strategy, data_provider_factory=create_data_provider, backtester_factory=create_backtester, workers=4, )

Note

- Factory functions must be picklable (module-level functions, not lambdas)
- Each worker process creates its own instances to avoid sharing state
- Results are returned in the same order as input configs

# Intent Compiler

The compiler transforms high-level intents into executable transaction bundles.

## IntentCompiler

## almanak.framework.intents.compiler.IntentCompiler

```
IntentCompiler(
    chain: str = "arbitrum",
    wallet_address: str = "0x0000000000000000000000000000000000000000",
    default_protocol: str = "uniswap_v3",
    price_oracle: dict[str, Decimal] | None = None,
    default_deadline_seconds: int = 300,
    rpc_url: str | None = None,
    rpc_timeout: float = 10.0,
    default_lp_slippage: Decimal = Decimal("0.99"),
    config: IntentCompilerConfig | None = None,
    gateway_client: GatewayClient | None = None,
    token_resolver: TokenResolver | None = None,
    chain_wallets: dict[str, str] | None = None,
)
```

Compiles Intents into executable ActionBundles.

The IntentCompiler takes high-level trading intents and converts them into low-level transaction data ready for execution on-chain.

Example

compiler = IntentCompiler( chain="arbitrum", wallet_address="0x...", rpc_url="https://arb1.arbitrum.io/rpc", ) intent = Intent.swap("USDC", "ETH", amount_usd=Decimal("1000")) result = compiler.compile(intent) if result.status == CompilationStatus.SUCCESS:

# Execute result.action_bundle

pass

Initialize the compiler.

Parameters:

| Name                       | Type                   | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                      | Default                                                                                                                                                                                                                                                                |
| -------------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chain`                    | `str`                  | Target blockchain (ethereum, arbitrum, etc.)                                                                                                                                                                                                                                                                                                                                                                                                                                     | `'arbitrum'`                                                                                                                                                                                                                                                           |
| `wallet_address`           | `str`                  | Address that will execute transactions                                                                                                                                                                                                                                                                                                                                                                                                                                           | `'0x0000000000000000000000000000000000000000'`                                                                                                                                                                                                                         |
| `default_protocol`         | `str`                  | Default DEX protocol for swaps                                                                                                                                                                                                                                                                                                                                                                                                                                                   | `'uniswap_v3'`                                                                                                                                                                                                                                                         |
| `price_oracle`             | \`dict[str, Decimal]   | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Price oracle dict (token -> USD price). Required for production use to calculate accurate slippage amounts.                                                                                                                                                            |
| `default_deadline_seconds` | `int`                  | Default transaction deadline                                                                                                                                                                                                                                                                                                                                                                                                                                                     | `300`                                                                                                                                                                                                                                                                  |
| `rpc_url`                  | \`str                  | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | RPC URL for on-chain queries (needed for LP close). DEPRECATED: Use gateway_client instead for production deployments.                                                                                                                                                 |
| `rpc_timeout`              | `float`                | HTTP timeout for direct RPC calls in seconds.                                                                                                                                                                                                                                                                                                                                                                                                                                    | `10.0`                                                                                                                                                                                                                                                                 |
| `default_lp_slippage`      | `Decimal`              | Default slippage for LP operations (0.99 = 99%). This controls the minimum acceptable amounts when adding/removing liquidity. LP operations differ from swaps - for concentrated liquidity, the actual deposit ratio depends heavily on where the current price is relative to your tick range. A price near the range edge means most liquidity is in one token. Default 99% allows nearly full flexibility for this behavior. Can be lowered for tighter protection if needed. | `Decimal('0.99')`                                                                                                                                                                                                                                                      |
| `config`                   | \`IntentCompilerConfig | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Optional configuration. If not provided, defaults to IntentCompilerConfig() which requires price_oracle.                                                                                                                                                               |
| `gateway_client`           | \`GatewayClient        | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Optional gateway client for RPC queries. When provided, all on-chain queries (allowance, balance, position liquidity) go through the gateway instead of direct RPC. This is the preferred mode for production deployments where strategies run in isolated containers. |
| `token_resolver`           | \`TokenResolver        | None\`                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | Optional TokenResolver instance for token resolution. If not provided, uses the singleton instance from get_token_resolver(). The resolver provides unified token lookup with caching and on-chain discovery support.                                                  |

Raises:

| Type         | Description                                                           |
| ------------ | --------------------------------------------------------------------- |
| `ValueError` | If no price_oracle is provided and allow_placeholder_prices is False. |

### polymarket_adapter

```
polymarket_adapter: PolymarketAdapter | None
```

Get the Polymarket adapter for prediction market intents.

Returns:

| Type                | Description |
| ------------------- | ----------- |
| \`PolymarketAdapter | None\`      |

### update_prices

```
update_prices(prices: dict[str, Decimal]) -> None
```

Update the price oracle with real prices, clearing placeholder state.

### restore_prices

```
restore_prices(
    original_oracle: dict[str, Decimal] | None,
    original_using_placeholders: bool,
) -> None
```

Restore prices to a previous state (used after temporary override).

### compile

```
compile(intent: AnyIntent) -> CompilationResult
```

Compile an intent into an ActionBundle.

This is the main entry point for compiling intents. It dispatches to the appropriate handler based on intent type.

Parameters:

| Name     | Type        | Description           | Default    |
| -------- | ----------- | --------------------- | ---------- |
| `intent` | `AnyIntent` | The intent to compile | *required* |

Returns:

| Type                | Description                                      |
| ------------------- | ------------------------------------------------ |
| `CompilationResult` | CompilationResult with ActionBundle and metadata |

### set_allowance

```
set_allowance(
    token_address: str, spender: str, amount: int
) -> None
```

Set cached allowance (for testing or after on-chain approval).

Parameters:

| Name            | Type  | Description            | Default    |
| --------------- | ----- | ---------------------- | ---------- |
| `token_address` | `str` | Token contract address | *required* |
| `spender`       | `str` | Spender address        | *required* |
| `amount`        | `int` | Allowance amount       | *required* |

### clear_allowance_cache

```
clear_allowance_cache() -> None
```

Clear the allowance cache.

## IntentCompilerConfig

## almanak.framework.intents.compiler.IntentCompilerConfig

```
IntentCompilerConfig(
    allow_placeholder_prices: bool = False,
    polymarket_config: PolymarketConfig | None = None,
    swap_pool_selection_mode: Literal[
        "auto", "fixed"
    ] = "auto",
    fixed_swap_fee_tier: int | None = None,
    max_price_impact_pct: Decimal = Decimal("0.30"),
)
```

Configuration for IntentCompiler.

Attributes:

| Name                       | Type                       | Description                                                                                                                                                                                                                                                                      |
| -------------------------- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `allow_placeholder_prices` | `bool`                     | If False (default), raises ValueError when no price_oracle is given. Set to True ONLY for unit tests. NEVER set to True in production - placeholder prices will cause incorrect slippage calculations and swap reverts.                                                          |
| `polymarket_config`        | \`PolymarketConfig         | None\`                                                                                                                                                                                                                                                                           |
| `swap_pool_selection_mode` | `Literal['auto', 'fixed']` | Pool selection mode for V3-style swaps. - "auto" (default): Try all supported fee tiers and pick best quote when RPC is available. - "fixed": Use fixed_swap_fee_tier for deterministic execution.                                                                               |
| `fixed_swap_fee_tier`      | \`int                      | None\`                                                                                                                                                                                                                                                                           |
| `max_price_impact_pct`     | `Decimal`                  | Maximum acceptable price impact as a fraction (0.0 to 1.0). If the on-chain quoter returns an amount deviating more than this from the oracle estimate, compilation fails with a clear error. Default: 0.30 (30%). Can be overridden per-intent via SwapIntent.max_price_impact. |

### __post_init__

```
__post_init__() -> None
```

Validate swap pool selection settings.

## CompilationResult

## almanak.framework.intents.compiler.CompilationResult

```
CompilationResult(
    status: CompilationStatus,
    action_bundle: ActionBundle | None = None,
    transactions: list[TransactionData] = list(),
    total_gas_estimate: int = 0,
    error: str | None = None,
    warnings: list[str] = list(),
    intent_id: str = "",
    compiled_at: datetime = (lambda: datetime.now(UTC))(),
)
```

Result of compiling an intent to an ActionBundle.

Attributes:

| Name                 | Type                    | Description                                     |
| -------------------- | ----------------------- | ----------------------------------------------- |
| `status`             | `CompilationStatus`     | Compilation status                              |
| `action_bundle`      | \`ActionBundle          | None\`                                          |
| `transactions`       | `list[TransactionData]` | List of transaction data                        |
| `total_gas_estimate` | `int`                   | Sum of all gas estimates                        |
| `error`              | \`str                   | None\`                                          |
| `warnings`           | `list[str]`             | List of warnings encountered during compilation |
| `intent_id`          | `str`                   | ID of the intent that was compiled              |
| `compiled_at`        | `datetime`              | Timestamp of compilation                        |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

## CompilationStatus

## almanak.framework.intents.compiler.CompilationStatus

Bases: `Enum`

Status of intent compilation.

## TransactionData

## almanak.framework.intents.compiler.TransactionData

```
TransactionData(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
    tx_type: str,
)
```

Represents a single transaction in an ActionBundle.

Attributes:

| Name           | Type  | Description                                     |
| -------------- | ----- | ----------------------------------------------- |
| `to`           | `str` | Target contract address                         |
| `value`        | `int` | ETH value to send (in wei)                      |
| `data`         | `str` | Encoded calldata                                |
| `gas_estimate` | `int` | Estimated gas for this transaction              |
| `description`  | `str` | Human-readable description of what this TX does |
| `tx_type`      | `str` | Type of transaction (approve, swap, etc.)       |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

## TokenInfo

## almanak.framework.intents.compiler.TokenInfo

```
TokenInfo(
    symbol: str,
    address: str,
    decimals: int = 18,
    is_native: bool = False,
)
```

Information about a token.

Attributes:

| Name        | Type   | Description                                         |
| ----------- | ------ | --------------------------------------------------- |
| `symbol`    | `str`  | Token symbol (e.g., "USDC")                         |
| `address`   | `str`  | Token contract address                              |
| `decimals`  | `int`  | Token decimals                                      |
| `is_native` | `bool` | Whether this is the native token (ETH, MATIC, etc.) |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

## PriceInfo

## almanak.framework.intents.compiler.PriceInfo

```
PriceInfo(
    token: str,
    price_usd: Decimal,
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)
```

Price information for amount calculations.

Attributes:

| Name        | Type       | Description                 |
| ----------- | ---------- | --------------------------- |
| `token`     | `str`      | Token symbol                |
| `price_usd` | `Decimal`  | Price in USD                |
| `timestamp` | `datetime` | When this price was fetched |

# Data Layer

Market data providers, price oracles, balance providers, and the unified `MarketSnapshot` interface.

## MarketSnapshot

The primary data interface passed to `decide()`. Provides lazy access to prices, balances, indicators, and more.

## almanak.framework.data.MarketSnapshot

```
MarketSnapshot(
    chain: str,
    wallet_address: str,
    price_oracle: PriceOracle | None = None,
    balance_provider: BalanceProvider | None = None,
    rsi_calculator: RSICalculator | None = None,
    ohlcv_module: Optional[OHLCVModule] = None,
    gas_oracle: Optional[GasOracle] = None,
    pool_reader: Optional[UniswapV3PoolReader] = None,
    rate_monitor: Optional[RateMonitor] = None,
    funding_rate_provider: Optional[
        FundingRateProvider
    ] = None,
    multi_dex_service: Optional[
        MultiDexPriceService
    ] = None,
    il_calculator: Optional[ILCalculator] = None,
    prediction_provider: Optional[
        PredictionMarketDataProvider
    ] = None,
    stablecoin_config: StablecoinConfig | None = None,
    freshness_config: FreshnessConfig | None = None,
    timestamp: datetime | None = None,
    pool_reader_registry: Optional[
        PoolReaderRegistry
    ] = None,
    price_aggregator: Optional[PriceAggregator] = None,
    data_router: Optional[DataRouter] = None,
    ohlcv_router: Optional[OHLCVRouter] = None,
    pool_history_reader: Optional[PoolHistoryReader] = None,
    rate_history_reader: Optional[RateHistoryReader] = None,
    liquidity_depth_reader: Optional[
        LiquidityDepthReader
    ] = None,
    slippage_estimator: Optional[SlippageEstimator] = None,
    volatility_calculator: Optional[
        RealizedVolatilityCalculator
    ] = None,
    risk_calculator: Optional[
        PortfolioRiskCalculator
    ] = None,
    pool_analytics_reader: Optional[
        PoolAnalyticsReader
    ] = None,
    yield_aggregator: Optional[YieldAggregator] = None,
    solana_lst_provider: Optional[SolanaLSTProvider] = None,
    wallet_activity_provider: Optional[
        WalletActivityProvider
    ] = None,
    gateway_client: Optional[GatewayClient] = None,
)
```

Data-layer MarketSnapshot — DEPRECATED, use almanak.framework.strategies.intent_strategy.MarketSnapshot.

.. deprecated:: This class is the internal data-layer variant with different return types (e.g., balance() -> Decimal, rsi() -> float) than the canonical strategy-facing MarketSnapshot (balance() -> TokenBalance, rsi() -> RSIData). New code should import from `almanak.framework.strategies` instead.

```
The canonical MarketSnapshot now supports lending_rate() and best_lending_rate()
directly — see VIB-437 for details.
```

This class is retained for backward compatibility with paper trading engine and data-layer tests. It will be fully consolidated in a future release.

Initialize the MarketSnapshot.

Parameters:

| Name                     | Type                                     | Description                                                 | Default                                                                                                                    |
| ------------------------ | ---------------------------------------- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `chain`                  | `str`                                    | Blockchain network name (e.g., "arbitrum", "ethereum")      | *required*                                                                                                                 |
| `wallet_address`         | `str`                                    | Wallet address for balance queries                          | *required*                                                                                                                 |
| `price_oracle`           | \`PriceOracle                            | None\`                                                      | PriceOracle implementation for price data                                                                                  |
| `balance_provider`       | \`BalanceProvider                        | None\`                                                      | BalanceProvider implementation for balance data                                                                            |
| `rsi_calculator`         | \`RSICalculator                          | None\`                                                      | RSICalculator implementation for RSI indicator                                                                             |
| `ohlcv_module`           | `Optional[OHLCVModule]`                  | OHLCVModule for historical candlestick data                 | `None`                                                                                                                     |
| `gas_oracle`             | `Optional[GasOracle]`                    | GasOracle implementation for gas price data                 | `None`                                                                                                                     |
| `pool_reader`            | `Optional[UniswapV3PoolReader]`          | UniswapV3PoolReader for DEX pool data                       | `None`                                                                                                                     |
| `rate_monitor`           | `Optional[RateMonitor]`                  | RateMonitor for lending protocol rates                      | `None`                                                                                                                     |
| `funding_rate_provider`  | `Optional[FundingRateProvider]`          | FundingRateProvider for perpetual funding rates             | `None`                                                                                                                     |
| `multi_dex_service`      | `Optional[MultiDexPriceService]`         | MultiDexPriceService for cross-DEX price comparison         | `None`                                                                                                                     |
| `il_calculator`          | `Optional[ILCalculator]`                 | ILCalculator for impermanent loss calculations              | `None`                                                                                                                     |
| `prediction_provider`    | `Optional[PredictionMarketDataProvider]` | PredictionMarketDataProvider for prediction market data     | `None`                                                                                                                     |
| `stablecoin_config`      | \`StablecoinConfig                       | None\`                                                      | Configuration for stablecoin pricing behavior. Default is StablecoinConfig(mode='market') which uses actual market prices. |
| `freshness_config`       | \`FreshnessConfig                        | None\`                                                      | Configuration for data freshness thresholds. Default is FreshnessConfig(price_warn_sec=30, price_error_sec=300).           |
| `timestamp`              | \`datetime                               | None\`                                                      | Optional snapshot timestamp (defaults to now)                                                                              |
| `pool_reader_registry`   | `Optional[PoolReaderRegistry]`           | PoolReaderRegistry for on-chain pool price reads            | `None`                                                                                                                     |
| `price_aggregator`       | `Optional[PriceAggregator]`              | PriceAggregator for TWAP/LWAP aggregation                   | `None`                                                                                                                     |
| `data_router`            | `Optional[DataRouter]`                   | DataRouter for provider selection and failover              | `None`                                                                                                                     |
| `ohlcv_router`           | `Optional[OHLCVRouter]`                  | OHLCVRouter for multi-provider OHLCV with CEX/DEX awareness | `None`                                                                                                                     |
| `pool_history_reader`    | `Optional[PoolHistoryReader]`            | PoolHistoryReader for historical pool state data            | `None`                                                                                                                     |
| `rate_history_reader`    | `Optional[RateHistoryReader]`            | RateHistoryReader for historical lending/funding rate data  | `None`                                                                                                                     |
| `liquidity_depth_reader` | `Optional[LiquidityDepthReader]`         | LiquidityDepthReader for tick-level liquidity reads         | `None`                                                                                                                     |
| `slippage_estimator`     | `Optional[SlippageEstimator]`            | SlippageEstimator for swap slippage estimation              | `None`                                                                                                                     |
| `volatility_calculator`  | `Optional[RealizedVolatilityCalculator]` | RealizedVolatilityCalculator for vol metrics                | `None`                                                                                                                     |
| `risk_calculator`        | `Optional[PortfolioRiskCalculator]`      | PortfolioRiskCalculator for portfolio risk metrics          | `None`                                                                                                                     |
| `pool_analytics_reader`  | `Optional[PoolAnalyticsReader]`          | PoolAnalyticsReader for pool TVL, volume, fee APR           | `None`                                                                                                                     |
| `yield_aggregator`       | `Optional[YieldAggregator]`              | YieldAggregator for cross-protocol yield comparison         | `None`                                                                                                                     |
| `solana_lst_provider`    | `Optional[SolanaLSTProvider]`            | SolanaLSTProvider for Solana LST exchange rates and APY     | `None`                                                                                                                     |

### chain

```
chain: str
```

Get the blockchain network name.

### wallet_address

```
wallet_address: str
```

Get the wallet address.

### timestamp

```
timestamp: datetime
```

Get the snapshot creation timestamp.

### fork_rpc_url

```
fork_rpc_url: str | None
```

Get the Anvil fork RPC URL for on-chain reads (paper trading only).

Returns the fork's JSON-RPC endpoint when running in paper trading mode, allowing strategies to perform protocol-level reads (e.g., Aave getReserveData, pool state queries) directly against the fork.

Returns None when not in paper trading mode.

VIB-1956: Previously, strategies couldn't access on-chain data during paper trading because no gateway client was available.

Example

def decide(self, market: MarketSnapshot) -> Intent: if market.fork_rpc_url: from web3 import Web3 w3 = Web3(Web3.HTTPProvider(market.fork_rpc_url))

# Read Aave pool data, DEX reserves, etc.

### fork_block

```
fork_block: int | None
```

Get the current fork block number (paper trading only).

Returns None when not in paper trading mode.

### wallet_activity

```
wallet_activity(
    leader_address: str | None = None,
    action_types: list[str] | None = None,
    min_usd_value: Decimal | None = None,
    protocols: list[str] | None = None,
) -> list
```

Get leader wallet activity signals for copy trading.

Returns filtered signals from the WalletActivityProvider. If no provider is configured, returns an empty list (graceful degradation).

Parameters:

| Name             | Type        | Description | Default                                         |
| ---------------- | ----------- | ----------- | ----------------------------------------------- |
| `leader_address` | \`str       | None\`      | Filter by specific leader wallet address        |
| `action_types`   | \`list[str] | None\`      | Filter by action types (e.g., ["SWAP"])         |
| `min_usd_value`  | \`Decimal   | None\`      | Minimum USD value filter                        |
| `protocols`      | \`list[str] | None\`      | Filter by protocol names (e.g., ["uniswap_v3"]) |

Returns:

| Type   | Description                                     |
| ------ | ----------------------------------------------- |
| `list` | List of CopySignal objects matching the filters |

### price

```
price(token: str, quote: str = 'USD') -> Decimal
```

Get the aggregated price for a token.

Fetches the price from the configured PriceOracle, which may aggregate prices from multiple sources.

For stablecoins, the behavior depends on the configured StablecoinConfig:

- mode='market' (default): Returns actual market price
- mode='pegged': Returns Decimal('1.00') for configured stablecoins
- mode='hybrid': Returns $1.00 if within tolerance, else market price

Parameters:

| Name    | Type  | Description                                | Default    |
| ------- | ----- | ------------------------------------------ | ---------- |
| `token` | `str` | Token symbol (e.g., "WETH", "ETH", "USDC") | *required* |
| `quote` | `str` | Quote currency (default "USD")             | `'USD'`    |

Returns:

| Type      | Description                      |
| --------- | -------------------------------- |
| `Decimal` | Price as a Decimal for precision |

Raises:

| Type                    | Description                      |
| ----------------------- | -------------------------------- |
| `PriceUnavailableError` | If price cannot be determined    |
| `ValueError`            | If no price oracle is configured |

Example

eth_price = snapshot.price("WETH")

#### Returns: Decimal("2500.50")

#### With mode='pegged'

usdc_price = snapshot.price("USDC")

#### Returns: Decimal("1.00")

### balance

```
balance(token: str) -> Decimal
```

Get the wallet balance for a token.

Queries the balance from the configured BalanceProvider.

Parameters:

| Name    | Type  | Description                                             | Default    |
| ------- | ----- | ------------------------------------------------------- | ---------- |
| `token` | `str` | Token symbol (e.g., "WETH", "USDC") or "ETH" for native | *required* |

Returns:

| Type      | Description                                            |
| --------- | ------------------------------------------------------ |
| `Decimal` | Balance as a Decimal in human-readable units (not wei) |

Raises:

| Type                      | Description                          |
| ------------------------- | ------------------------------------ |
| `BalanceUnavailableError` | If balance cannot be determined      |
| `ValueError`              | If no balance provider is configured |

Example

usdc_balance = snapshot.balance("USDC")

#### Returns: Decimal("1000.50")

### rsi

```
rsi(
    token: str, period: int = 14, timeframe: str = "4h"
) -> RSIData
```

Get the RSI (Relative Strength Index) for a token.

Calculates RSI using the configured RSI calculator with historical price data. Returns RSIData for compatibility with strategies that use .value attribute.

Parameters:

| Name        | Type  | Description                                                                                                                                           | Default    |
| ----------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `token`     | `str` | Token symbol (e.g., "WETH", "ETH")                                                                                                                    | *required* |
| `period`    | `int` | RSI calculation period (default 14)                                                                                                                   | `14`       |
| `timeframe` | `str` | OHLCV candle timeframe (default "4h") Supported: "1m", "5m", "15m", "1h", "4h", "1d" Note: 1m/5m/15m may return 30-min candles (CoinGecko limitation) | `'4h'`     |

Returns:

| Type      | Description                                                                                 |
| --------- | ------------------------------------------------------------------------------------------- |
| `RSIData` | RSIData object with .value (Decimal, 0-100), .period, .signal, .is_oversold, .is_overbought |

Raises:

| Type                  | Description                        |
| --------------------- | ---------------------------------- |
| `RSIUnavailableError` | If RSI cannot be calculated        |
| `ValueError`          | If no RSI calculator is configured |

Example

#### Default 4-hour candles

rsi = snapshot.rsi("WETH", period=14)

#### 1-hour candles for shorter-term analysis

rsi_1h = snapshot.rsi("WETH", period=14, timeframe="1h")

#### Daily candles for longer-term analysis

rsi_1d = snapshot.rsi("WETH", period=14, timeframe="1d")

#### Multi-timeframe analysis

if snapshot.rsi("WETH", timeframe="1h") < 30 and snapshot.rsi("WETH", timeframe="1d") < 50:

# Short-term oversold, long-term not overbought

return SwapIntent(...)

### sma

```
sma(
    token: str, period: int = 20, timeframe: str = "1h"
) -> float
```

Get the Simple Moving Average (SMA) for a token.

SMA is the unweighted mean of the last N closing prices.

Parameters:

| Name        | Type  | Description                                    | Default    |
| ----------- | ----- | ---------------------------------------------- | ---------- |
| `token`     | `str` | Token symbol (e.g., "WETH", "ETH")             | *required* |
| `period`    | `int` | Number of periods for the average (default 20) | `20`       |
| `timeframe` | `str` | OHLCV candle timeframe (default "1h")          | `'1h'`     |

Returns:

| Type    | Description        |
| ------- | ------------------ |
| `float` | SMA value as float |

Example

sma_20 = snapshot.sma("WETH", period=20, timeframe="1h") sma_200 = snapshot.sma("WETH", period=200, timeframe="1d")

#### Trading logic

if current_price > sma_20: print("Price above 20-period SMA - bullish trend")

### ema

```
ema(
    token: str,
    period: int = 12,
    timeframe: str = "1h",
    smoothing: float = 2.0,
) -> float
```

Get the Exponential Moving Average (EMA) for a token.

EMA gives more weight to recent prices using exponential decay.

Parameters:

| Name        | Type    | Description                           | Default    |
| ----------- | ------- | ------------------------------------- | ---------- |
| `token`     | `str`   | Token symbol (e.g., "WETH", "ETH")    | *required* |
| `period`    | `int`   | Number of periods (default 12)        | `12`       |
| `timeframe` | `str`   | OHLCV candle timeframe (default "1h") | `'1h'`     |
| `smoothing` | `float` | Smoothing factor (default 2.0)        | `2.0`      |

Returns:

| Type    | Description        |
| ------- | ------------------ |
| `float` | EMA value as float |

Example

ema_12 = snapshot.ema("WETH", period=12, timeframe="1h") ema_26 = snapshot.ema("WETH", period=26, timeframe="1h")

#### Golden cross check

if ema_12 > ema_26: print("Golden cross - bullish signal")

### bollinger_bands

```
bollinger_bands(
    token: str,
    period: int = 20,
    std_dev: float = 2.0,
    timeframe: str = "1h",
) -> BollingerBandsResult
```

Get Bollinger Bands for a token.

Bollinger Bands consist of a middle band (SMA) with upper and lower bands at a specified number of standard deviations away.

Parameters:

| Name        | Type    | Description                                 | Default    |
| ----------- | ------- | ------------------------------------------- | ---------- |
| `token`     | `str`   | Token symbol (e.g., "WETH", "ETH")          | *required* |
| `period`    | `int`   | SMA period (default 20)                     | `20`       |
| `std_dev`   | `float` | Standard deviation multiplier (default 2.0) | `2.0`      |
| `timeframe` | `str`   | OHLCV candle timeframe (default "1h")       | `'1h'`     |

Returns:

| Type                   | Description                                                                                                                                                                                                          |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BollingerBandsResult` | BollingerBandsResult with: - upper_band: Upper band value - middle_band: Middle band (SMA) value - lower_band: Lower band value - bandwidth: Band width as percentage - percent_b: Price position (0=lower, 1=upper) |

Example

bb = snapshot.bollinger_bands("WETH", period=20, std_dev=2.0, timeframe="1h")

if bb.percent_b < 0: print("Price below lower band - oversold!") elif bb.percent_b > 1: print("Price above upper band - overbought!")

if bb.bandwidth < 0.05: print("Low volatility - squeeze detected")

### macd

```
macd(
    token: str,
    fast_period: int = 12,
    slow_period: int = 26,
    signal_period: int = 9,
    timeframe: str = "1h",
) -> MACDResult
```

Get MACD (Moving Average Convergence Divergence) for a token.

MACD is a trend-following momentum indicator showing the relationship between two exponential moving averages.

Parameters:

| Name            | Type  | Description                           | Default    |
| --------------- | ----- | ------------------------------------- | ---------- |
| `token`         | `str` | Token symbol (e.g., "WETH", "ETH")    | *required* |
| `fast_period`   | `int` | Fast EMA period (default 12)          | `12`       |
| `slow_period`   | `int` | Slow EMA period (default 26)          | `26`       |
| `signal_period` | `int` | Signal line EMA period (default 9)    | `9`        |
| `timeframe`     | `str` | OHLCV candle timeframe (default "1h") | `'1h'`     |

Returns:

| Type         | Description                                                                                                                                                       |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MACDResult` | MACDResult with: - macd_line: MACD line (fast EMA - slow EMA) - signal_line: Signal line (EMA of MACD line) - histogram: MACD histogram (macd_line - signal_line) |

Example

macd = snapshot.macd("WETH", timeframe="4h")

if macd.histogram > 0: print("MACD above signal - bullish momentum") elif macd.histogram < 0: print("MACD below signal - bearish momentum")

### stochastic

```
stochastic(
    token: str,
    k_period: int = 14,
    d_period: int = 3,
    timeframe: str = "1h",
) -> StochasticResult
```

Get Stochastic Oscillator for a token.

The Stochastic Oscillator is a momentum indicator comparing a token's closing price to its price range over a given period.

Parameters:

| Name        | Type  | Description                           | Default    |
| ----------- | ----- | ------------------------------------- | ---------- |
| `token`     | `str` | Token symbol (e.g., "WETH", "ETH")    | *required* |
| `k_period`  | `int` | Lookback period for %K (default 14)   | `14`       |
| `d_period`  | `int` | SMA period for %D (default 3)         | `3`        |
| `timeframe` | `str` | OHLCV candle timeframe (default "1h") | `'1h'`     |

Returns:

| Type               | Description                                                                                                      |
| ------------------ | ---------------------------------------------------------------------------------------------------------------- |
| `StochasticResult` | StochasticResult with: - k_value: %K (fast stochastic, 0-100 scale) - d_value: %D (slow stochastic, 0-100 scale) |

Example

stoch = snapshot.stochastic("WETH", k_period=14, d_period=3)

if stoch.k_value < 20: print("Oversold territory") elif stoch.k_value > 80: print("Overbought territory")

#### Crossover signals

if stoch.k_value > stoch.d_value: print("Bullish - %K crossed above %D")

### atr

```
atr(
    token: str, period: int = 14, timeframe: str = "1h"
) -> float
```

Get Average True Range (ATR) for a token.

ATR is a volatility indicator showing how much an asset moves on average.

Parameters:

| Name        | Type  | Description                           | Default    |
| ----------- | ----- | ------------------------------------- | ---------- |
| `token`     | `str` | Token symbol (e.g., "WETH", "ETH")    | *required* |
| `period`    | `int` | ATR period (default 14)               | `14`       |
| `timeframe` | `str` | OHLCV candle timeframe (default "1h") | `'1h'`     |

Returns:

| Type    | Description                                      |
| ------- | ------------------------------------------------ |
| `float` | ATR value (in the same units as the token price) |

Example

atr = snapshot.atr("WETH", period=14, timeframe="4h") current_price = float(snapshot.price("WETH"))

#### Stop-loss placement

stop_loss = current_price - (2 * atr) print(f"Stop loss at ${stop_loss:.2f} (2 ATR below)")

#### Position sizing with 1% risk

risk_amount = 10000 * 0.01 # $100 position_size = risk_amount / atr print(f"Position size: {position_size:.4f} units")

### ohlcv

```
ohlcv(
    token: str | Instrument,
    timeframe: str = "1h",
    limit: int = 100,
    quote: str = "USD",
    gap_strategy: GapStrategy = "nan",
    *,
    pool_address: str | None = None,
) -> pd.DataFrame
```

Get OHLCV (candlestick) data for a token.

Fetches historical candlestick data from the configured OHLCV providers. When an OHLCVRouter is configured, automatically classifies instruments as CEX-primary or DeFi-primary and routes to the appropriate provider.

Accepts plain token symbols (e.g. "WETH"), pair strings (e.g. "WETH/USDC"), or Instrument objects.

Parameters:

| Name           | Type          | Description                                                                                                                                                                       | Default                                             |
| -------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
| `token`        | \`str         | Instrument\`                                                                                                                                                                      | Token symbol, "BASE/QUOTE" string, or Instrument.   |
| `timeframe`    | `str`         | Candle timeframe (1m, 5m, 15m, 1h, 4h, 1d). Default "1h".                                                                                                                         | `'1h'`                                              |
| `limit`        | `int`         | Maximum number of candles to return. Default 100.                                                                                                                                 | `100`                                               |
| `quote`        | `str`         | Quote currency (default "USD")                                                                                                                                                    | `'USD'`                                             |
| `gap_strategy` | `GapStrategy` | How to handle gaps in data: - 'nan': Fill gaps with NaN values (default) - 'ffill': Forward-fill gaps with last known values - 'drop': Remove gaps (returns only continuous data) | `'nan'`                                             |
| `pool_address` | \`str         | None\`                                                                                                                                                                            | Explicit pool address for DEX providers (optional). |

Returns:

| Type        | Description                                                                                                                                                                                                                                            |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `DataFrame` | pandas DataFrame with columns: - timestamp: datetime - open: float64 - high: float64 - low: float64 - close: float64 - volume: float64 (may contain NaN if unavailable)                                                                                |
| `DataFrame` | DataFrame.attrs includes metadata: - base: Token symbol - quote: Quote currency - timeframe: Candle timeframe - source: Provider source name - chain: Chain identifier - fetched_at: When the data was fetched - confidence: Data confidence (0.0-1.0) |
| `DataFrame` | Returns empty DataFrame with correct schema if no data available.                                                                                                                                                                                      |

Raises:

| Type                    | Description                                                  |
| ----------------------- | ------------------------------------------------------------ |
| `OHLCVUnavailableError` | If OHLCV data cannot be retrieved                            |
| `ValueError`            | If no OHLCV module/router is configured or invalid timeframe |

Example

#### Get 1-hour candles for WETH

df = snapshot.ohlcv("WETH", timeframe="1h", limit=100) print(df.columns) # timestamp, open, high, low, close, volume

#### Use Instrument for explicit routing

from almanak.framework.data.models import Instrument inst = Instrument(base="WETH", quote="USDC", chain="arbitrum") df = snapshot.ohlcv(inst, timeframe="1h")

#### Use with pandas-ta for indicators

import pandas_ta as ta df['rsi'] = ta.rsi(df['close'], length=14) df['macd'] = ta.macd(df['close'])['MACD_12_26_9']

#### Handle gaps with forward-fill

df = snapshot.ohlcv("WETH", gap_strategy="ffill")

### gas_price

```
gas_price(chain: str | None = None) -> GasPrice
```

Get current gas price for a chain.

Fetches the current gas price data from the configured GasOracle. Results are cached for 12 seconds (approximately 1 block) to avoid excessive RPC calls.

Parameters:

| Name    | Type  | Description | Default                                                                                                                                   |
| ------- | ----- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `chain` | \`str | None\`      | Chain identifier (e.g., "ethereum", "arbitrum", "optimism"). If not specified, uses the strategy's primary chain from the MarketSnapshot. |

Returns:

| Type       | Description                                                                                                                                                                                                                                                                                                                                                                                                                 |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `GasPrice` | GasPrice dataclass with: - chain: Chain identifier - base_fee_gwei: Network base fee in gwei - priority_fee_gwei: Priority/tip fee in gwei - max_fee_gwei: Maximum fee (base + priority) in gwei - l1_base_fee_gwei: L1 base fee for L2 chains (optional) - l1_data_cost_gwei: L1 data cost for L2 chains (optional) - estimated_cost_usd: Estimated cost in USD for 21000 gas - timestamp: When the gas price was observed |

Raises:

| Type                  | Description                      |
| --------------------- | -------------------------------- |
| `GasUnavailableError` | If gas price cannot be retrieved |
| `ValueError`          | If no gas oracle is configured   |

Example

#### Get gas for strategy's primary chain

gas = snapshot.gas_price() print(f"Base fee: {gas.base_fee_gwei} gwei") print(f"Estimated cost: ${gas.estimated_cost_usd}")

#### Get gas for specific chain

arb_gas = snapshot.gas_price("arbitrum") if arb_gas.is_l2: print(f"L1 data cost: {arb_gas.l1_data_cost_gwei} gwei")

### pool_price

```
pool_price(
    pool_address: str, chain: str | None = None
) -> DataEnvelope[PoolPrice]
```

Get the live price from an on-chain DEX pool.

Reads slot0() from the pool contract and decodes sqrtPriceX96 into a human-readable price using token decimals.

Returns a DataEnvelope[PoolPrice] with EXECUTION_GRADE classification (fail-closed: raises on any error, no off-chain fallback).

Parameters:

| Name           | Type  | Description            | Default                                                                         |
| -------------- | ----- | ---------------------- | ------------------------------------------------------------------------------- |
| `pool_address` | `str` | Pool contract address. | *required*                                                                      |
| `chain`        | \`str | None\`                 | Chain name (e.g. "arbitrum", "base"). Defaults to the snapshot's primary chain. |

Returns:

| Type                      | Description                                       |
| ------------------------- | ------------------------------------------------- |
| `DataEnvelope[PoolPrice]` | DataEnvelope[PoolPrice] with provenance metadata. |

Raises:

| Type                        | Description                               |
| --------------------------- | ----------------------------------------- |
| `PoolPriceUnavailableError` | If pool price cannot be retrieved.        |
| `ValueError`                | If no pool reader registry is configured. |

### pool_price_by_pair

```
pool_price_by_pair(
    token_a: str,
    token_b: str,
    chain: str | None = None,
    protocol: str | None = None,
    fee_tier: int = 3000,
) -> DataEnvelope[PoolPrice]
```

Get the live pool price for a token pair.

Resolves the pool address for the given pair and reads the price. This is a convenience method that wraps pool address resolution and price reading.

Parameters:

| Name       | Type  | Description                                     | Default                                                                     |
| ---------- | ----- | ----------------------------------------------- | --------------------------------------------------------------------------- |
| `token_a`  | `str` | Token A symbol or address.                      | *required*                                                                  |
| `token_b`  | `str` | Token B symbol or address.                      | *required*                                                                  |
| `chain`    | \`str | None\`                                          | Chain name. Defaults to the snapshot's primary chain.                       |
| `protocol` | \`str | None\`                                          | Protocol name (e.g. "uniswap_v3"). If None, tries all registered protocols. |
| `fee_tier` | `int` | Fee tier in basis points (default 3000 = 0.3%). | `3000`                                                                      |

Returns:

| Type                      | Description                                       |
| ------------------------- | ------------------------------------------------- |
| `DataEnvelope[PoolPrice]` | DataEnvelope[PoolPrice] with provenance metadata. |

Raises:

| Type                        | Description                                      |
| --------------------------- | ------------------------------------------------ |
| `PoolPriceUnavailableError` | If pool cannot be found or price cannot be read. |
| `ValueError`                | If no pool reader registry is configured.        |

### twap

```
twap(
    token_pair: str | Instrument,
    chain: str | None = None,
    window_seconds: int = 300,
    pool_address: str | None = None,
    protocol: str = "uniswap_v3",
) -> DataEnvelope[AggregatedPrice]
```

Get the time-weighted average price (TWAP) for a token pair.

Uses the Uniswap V3 oracle's observe() function to compute the TWAP over the specified time window.

Classification: EXECUTION_GRADE (fail-closed, no off-chain fallback).

Parameters:

| Name             | Type  | Description                                   | Default                                                                         |
| ---------------- | ----- | --------------------------------------------- | ------------------------------------------------------------------------------- |
| `token_pair`     | \`str | Instrument\`                                  | Token pair as "BASE/QUOTE" string (e.g. "WETH/USDC") or an Instrument instance. |
| `chain`          | \`str | None\`                                        | Chain name. Defaults to the snapshot's primary chain.                           |
| `window_seconds` | `int` | Time window in seconds (default 300 = 5 min). | `300`                                                                           |
| `pool_address`   | \`str | None\`                                        | Explicit pool address. If None, resolves from pair.                             |
| `protocol`       | `str` | Protocol to use (default "uniswap_v3").       | `'uniswap_v3'`                                                                  |

Returns:

| Type                            | Description                                                   |
| ------------------------------- | ------------------------------------------------------------- |
| `DataEnvelope[AggregatedPrice]` | DataEnvelope[AggregatedPrice] with TWAP price and provenance. |

Raises:

| Type                        | Description                           |
| --------------------------- | ------------------------------------- |
| `PoolPriceUnavailableError` | If TWAP cannot be calculated.         |
| `ValueError`                | If no price aggregator is configured. |

### lwap

```
lwap(
    token_pair: str | Instrument,
    chain: str | None = None,
    fee_tiers: list[int] | None = None,
    protocols: list[str] | None = None,
) -> DataEnvelope[AggregatedPrice]
```

Get the liquidity-weighted average price (LWAP) for a token pair.

Reads live prices from all known pools for the pair, filters by minimum liquidity, and computes a liquidity-weighted average.

Classification: EXECUTION_GRADE (fail-closed, no off-chain fallback).

Parameters:

| Name         | Type        | Description  | Default                                                                         |
| ------------ | ----------- | ------------ | ------------------------------------------------------------------------------- |
| `token_pair` | \`str       | Instrument\` | Token pair as "BASE/QUOTE" string (e.g. "WETH/USDC") or an Instrument instance. |
| `chain`      | \`str       | None\`       | Chain name. Defaults to the snapshot's primary chain.                           |
| `fee_tiers`  | \`list[int] | None\`       | Fee tiers to search (default: [100, 500, 3000, 10000]).                         |
| `protocols`  | \`list[str] | None\`       | Protocols to search (default: all registered for chain).                        |

Returns:

| Type                            | Description                                                   |
| ------------------------------- | ------------------------------------------------------------- |
| `DataEnvelope[AggregatedPrice]` | DataEnvelope[AggregatedPrice] with LWAP price and provenance. |

Raises:

| Type                        | Description                           |
| --------------------------- | ------------------------------------- |
| `PoolPriceUnavailableError` | If LWAP cannot be calculated.         |
| `ValueError`                | If no price aggregator is configured. |

### pool_history

```
pool_history(
    pool_address: str,
    chain: str | None = None,
    start_date: datetime | None = None,
    end_date: datetime | None = None,
    resolution: str = "1h",
) -> DataEnvelope[list[PoolSnapshot]]
```

Get historical pool state snapshots for backtesting and analytics.

Fetches TVL, volume, fee revenue, and reserve data from The Graph, DeFi Llama, or GeckoTerminal with graceful fallback between providers. Results are cached in VersionedDataCache for deterministic replay.

Parameters:

| Name           | Type       | Description                                         | Default                                                               |
| -------------- | ---------- | --------------------------------------------------- | --------------------------------------------------------------------- |
| `pool_address` | `str`      | Pool contract address.                              | *required*                                                            |
| `chain`        | \`str      | None\`                                              | Chain name (e.g. "arbitrum", "ethereum"). Defaults to strategy chain. |
| `start_date`   | \`datetime | None\`                                              | Start of the history window (UTC). Defaults to 90 days ago.           |
| `end_date`     | \`datetime | None\`                                              | End of the history window (UTC). Defaults to now.                     |
| `resolution`   | `str`      | Data resolution: "1h", "4h", or "1d". Default "1h". | `'1h'`                                                                |

Returns:

| Type                               | Description                                                           |
| ---------------------------------- | --------------------------------------------------------------------- |
| `DataEnvelope[list[PoolSnapshot]]` | DataEnvelope\[list[PoolSnapshot]\] with INFORMATIONAL classification. |

Raises:

| Type                          | Description                              |
| ----------------------------- | ---------------------------------------- |
| `PoolHistoryUnavailableError` | If historical data cannot be retrieved.  |
| `ValueError`                  | If no pool history reader is configured. |

### liquidity_depth

```
liquidity_depth(
    pool_address: str, chain: str | None = None
) -> DataEnvelope[LiquidityDepth]
```

Get tick-level liquidity depth for a concentrated-liquidity pool.

Reads the tick bitmap and individual tick liquidity values from the pool contract to build a picture of liquidity distribution around the current price. Essential for slippage estimation and position sizing.

Classification: EXECUTION_GRADE (fails closed, no off-chain fallback).

Parameters:

| Name           | Type  | Description            | Default                                                               |
| -------------- | ----- | ---------------------- | --------------------------------------------------------------------- |
| `pool_address` | `str` | Pool contract address. | *required*                                                            |
| `chain`        | \`str | None\`                 | Chain name (e.g. "arbitrum", "ethereum"). Defaults to strategy chain. |

Returns:

| Type                           | Description                                                  |
| ------------------------------ | ------------------------------------------------------------ |
| `DataEnvelope[LiquidityDepth]` | DataEnvelope[LiquidityDepth] with tick-level liquidity data. |

Raises:

| Type                             | Description                                 |
| -------------------------------- | ------------------------------------------- |
| `LiquidityDepthUnavailableError` | If liquidity data cannot be read.           |
| `ValueError`                     | If no liquidity depth reader is configured. |

### estimate_slippage

```
estimate_slippage(
    token_in: str,
    token_out: str,
    amount: Decimal,
    chain: str | None = None,
    protocol: str | None = None,
) -> DataEnvelope[SlippageEstimate]
```

Estimate price impact and slippage for a potential swap.

Simulates the swap through tick ranges using actual on-chain liquidity data to compute the expected execution price and slippage. Logs a warning if estimated slippage exceeds the configured threshold (default 1%).

Classification: EXECUTION_GRADE (fails closed, no off-chain fallback).

Parameters:

| Name        | Type      | Description                                        | Default                                                   |
| ----------- | --------- | -------------------------------------------------- | --------------------------------------------------------- |
| `token_in`  | `str`     | Input token symbol or address.                     | *required*                                                |
| `token_out` | `str`     | Output token symbol or address.                    | *required*                                                |
| `amount`    | `Decimal` | Amount of token_in to swap (human-readable units). | *required*                                                |
| `chain`     | \`str     | None\`                                             | Chain name. Defaults to strategy chain.                   |
| `protocol`  | \`str     | None\`                                             | Protocol name (e.g. "uniswap_v3"). Auto-detected if None. |

Returns:

| Type                             | Description                                            |
| -------------------------------- | ------------------------------------------------------ |
| `DataEnvelope[SlippageEstimate]` | DataEnvelope[SlippageEstimate] with price impact data. |

Raises:

| Type                               | Description                             |
| ---------------------------------- | --------------------------------------- |
| `SlippageEstimateUnavailableError` | If slippage cannot be estimated.        |
| `ValueError`                       | If no slippage estimator is configured. |

### pool_reserves

```
pool_reserves(
    pool_address: str, chain: str | None = None
) -> PoolReserves
```

Get DEX pool reserves and state.

Fetches the current state of a DEX liquidity pool from the blockchain. Auto-detects the pool type (Uniswap V2 vs V3) by checking the contract interface.

Results are cached for 12 seconds (approximately 1 block) to avoid excessive RPC calls.

Parameters:

| Name           | Type  | Description           | Default                                                                                                                                   |
| -------------- | ----- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
| `pool_address` | `str` | Pool contract address | *required*                                                                                                                                |
| `chain`        | \`str | None\`                | Chain identifier (e.g., "ethereum", "arbitrum", "optimism"). If not specified, uses the strategy's primary chain from the MarketSnapshot. |

Returns:

| Type           | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `PoolReserves` | PoolReserves dataclass with: - pool_address: Pool contract address - dex: DEX type ('uniswap_v2', 'uniswap_v3', 'sushiswap') - token0: First token in the pair (ChainToken) - token1: Second token in the pair (ChainToken) - reserve0: Reserve of token0 (human-readable Decimal) - reserve1: Reserve of token1 (human-readable Decimal) - fee_tier: Pool fee in basis points - sqrt_price_x96: V3 sqrt price (None for V2) - tick: V3 current tick (None for V2) - liquidity: V3 in-range liquidity (None for V2) - tvl_usd: Total value locked in USD - last_updated: When the data was fetched |

Raises:

| Type                           | Description                      |
| ------------------------------ | -------------------------------- |
| `PoolReservesUnavailableError` | If pool data cannot be retrieved |
| `ValueError`                   | If no pool reader is configured  |

Example

#### Get pool reserves for USDC/WETH pool

pool = snapshot.pool_reserves( "0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640" ) print(f"Reserve0: {pool.reserve0} {pool.token0.symbol}") print(f"Reserve1: {pool.reserve1} {pool.token1.symbol}") print(f"TVL: ${pool.tvl_usd}")

#### Check if V3 pool

if pool.is_v3: print(f"Current tick: {pool.tick}")

### prices

```
prices(
    tokens: list[str], quote: str = "USD"
) -> dict[str, Decimal]
```

Get prices for multiple tokens in a single batch call.

Fetches prices for all specified tokens in parallel using asyncio.gather. Returns partial results if some tokens fail (errors are logged).

Parameters:

| Name     | Type        | Description                                           | Default    |
| -------- | ----------- | ----------------------------------------------------- | ---------- |
| `tokens` | `list[str]` | List of token symbols (e.g., ["WETH", "USDC", "ARB"]) | *required* |
| `quote`  | `str`       | Quote currency (default "USD")                        | `'USD'`    |

Returns:

| Type                 | Description                                                  |
| -------------------- | ------------------------------------------------------------ |
| `dict[str, Decimal]` | Dictionary mapping token symbols to their prices as Decimal. |
| `dict[str, Decimal]` | Only includes tokens that were successfully fetched.         |

Raises:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `ValueError` | If no price oracle is configured |

Example

prices = snapshot.prices(["WETH", "USDC", "ARB"])

#### Returns:

#### If ARB fails to fetch:

#### Returns:

#### (Error is logged)

### balances

```
balances(tokens: list[str]) -> dict[str, Decimal]
```

Get balances for multiple tokens in a single batch call.

Fetches balances for all specified tokens in parallel using asyncio.gather. Returns partial results if some tokens fail (errors are logged).

Parameters:

| Name     | Type        | Description                                           | Default    |
| -------- | ----------- | ----------------------------------------------------- | ---------- |
| `tokens` | `list[str]` | List of token symbols (e.g., ["WETH", "USDC", "ARB"]) | *required* |

Returns:

| Type                 | Description                                                    |
| -------------------- | -------------------------------------------------------------- |
| `dict[str, Decimal]` | Dictionary mapping token symbols to their balances as Decimal. |
| `dict[str, Decimal]` | Only includes tokens that were successfully fetched.           |

Raises:

| Type         | Description                          |
| ------------ | ------------------------------------ |
| `ValueError` | If no balance provider is configured |

Example

balances = snapshot.balances(["WETH", "USDC", "ARB"])

#### Returns:

#### If ARB fails to fetch:

#### Returns:

#### (Error is logged)

### health

```
health() -> HealthReport
```

Get a health report for all registered data providers.

Aggregates health metrics from all configured providers including price oracle, balance provider, OHLCV module, gas oracle, and pool reader.

The health report includes:

- Individual source health (success rate, latency, errors)
- Cache statistics (hits, misses, hit rate)
- Overall system status (healthy, degraded, unhealthy)

Returns:

| Type           | Description                                                                                                                                                                                                                                         |
| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `HealthReport` | HealthReport dataclass with: - timestamp: When the report was generated - sources: Dictionary mapping source names to SourceHealth - cache_stats: CacheStats with cache performance metrics - overall_status: "healthy", "degraded", or "unhealthy" |

Example

report = snapshot.health() print(f"Overall status: {report.overall_status}") print(f"Sources: {list(report.sources.keys())}")

for name, health in report.sources.items(): print(f" {name}: {health.success_rate:.1%} success rate")

if report.failing_sources: print(f"Warning: failing sources: {report.failing_sources}")

### balance_usd

```
balance_usd(token: str) -> Decimal
```

Get the wallet balance value in USD terms.

Calculates the USD value by multiplying the token balance by its current price.

Parameters:

| Name    | Type  | Description                         | Default    |
| ------- | ----- | ----------------------------------- | ---------- |
| `token` | `str` | Token symbol (e.g., "WETH", "USDC") | *required* |

Returns:

| Type      | Description                       |
| --------- | --------------------------------- |
| `Decimal` | Balance value in USD as a Decimal |

Raises:

| Type                      | Description                     |
| ------------------------- | ------------------------------- |
| `PriceUnavailableError`   | If price cannot be determined   |
| `BalanceUnavailableError` | If balance cannot be determined |

Example

eth_value = snapshot.balance_usd("WETH")

#### If balance is 2 WETH at $2500, returns: Decimal("5000.00")

### collateral_value_usd

```
collateral_value_usd(
    token: str, amount: Decimal
) -> Decimal
```

Get the USD value of a given amount of collateral.

Convenience helper for perp position sizing. Multiplies the given amount by the token's current price. For stablecoins, the price behavior follows the configured StablecoinConfig mode (market, pegged, or hybrid).

Parameters:

| Name     | Type      | Description                                    | Default    |
| -------- | --------- | ---------------------------------------------- | ---------- |
| `token`  | `str`     | Token symbol (e.g., "WETH", "USDC", "WBTC")    | *required* |
| `amount` | `Decimal` | Token amount in human-readable units (not wei) | *required* |

Returns:

| Type      | Description            |
| --------- | ---------------------- |
| `Decimal` | USD value as a Decimal |

Raises:

| Type                    | Description                   |
| ----------------------- | ----------------------------- |
| `PriceUnavailableError` | If price cannot be determined |

Example

#### USDC collateral (stablecoin, ~$1)

usd = snapshot.collateral_value_usd("USDC", Decimal("5000"))

#### Returns: ~Decimal("5000.00")

#### WETH collateral

usd = snapshot.collateral_value_usd("WETH", Decimal("2"))

#### If ETH price is $2500, returns: Decimal("5000.00")

### total_portfolio_usd

```
total_portfolio_usd(
    tokens: list[str] | None = None,
) -> Decimal
```

Get the total portfolio value in USD.

When called with a token list, sums the USD value of those tokens. When called without arguments, sums all cached balance values (tokens that have been queried via balance() or balances() in this snapshot).

Parameters:

| Name     | Type        | Description | Default                                                              |
| -------- | ----------- | ----------- | -------------------------------------------------------------------- |
| `tokens` | \`list[str] | None\`      | List of token symbols to include. If None, uses all cached balances. |

Returns:

| Type      | Description                               |
| --------- | ----------------------------------------- |
| `Decimal` | Total portfolio value in USD as a Decimal |

Raises:

| Type                      | Description                         |
| ------------------------- | ----------------------------------- |
| `PriceUnavailableError`   | If any price cannot be determined   |
| `BalanceUnavailableError` | If any balance cannot be determined |

Example

#### Explicit token list

total = snapshot.total_portfolio_usd(["WETH", "USDC", "ARB"])

#### All cached balances

total = snapshot.total_portfolio_usd()

### lending_rate

```
lending_rate(
    protocol: str, token: str, side: str = "supply"
) -> LendingRate
```

Get the lending rate for a specific protocol and token.

Fetches the current supply or borrow APY from the specified lending protocol. Rates are cached for efficiency (typically 12s = ~1 block).

Parameters:

| Name       | Type  | Description                                             | Default    |
| ---------- | ----- | ------------------------------------------------------- | ---------- |
| `protocol` | `str` | Protocol identifier (aave_v3, morpho_blue, compound_v3) | *required* |
| `token`    | `str` | Token symbol (e.g., "USDC", "WETH")                     | *required* |
| `side`     | `str` | Rate side - "supply" or "borrow" (default "supply")     | `'supply'` |

Returns:

| Type          | Description                                                                                                                                                                                                                                                                                                                                                      |
| ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `LendingRate` | LendingRate dataclass with: - protocol: Protocol identifier - token: Token symbol - side: Rate side - apy_ray: APY in ray units (1e27 scale) - apy_percent: APY as percentage (e.g., 5.25 for 5.25%) - utilization_percent: Pool utilization percentage - timestamp: When rate was fetched - chain: Blockchain network - market_id: Market identifier (optional) |

Raises:

| Type                          | Description                      |
| ----------------------------- | -------------------------------- |
| `LendingRateUnavailableError` | If rate cannot be retrieved      |
| `ValueError`                  | If no rate monitor is configured |

Example

#### Get Aave USDC supply rate

rate = snapshot.lending_rate("aave_v3", "USDC", "supply") print(f"Aave USDC Supply APY: {rate.apy_percent:.2f}%")

#### Get Morpho WETH borrow rate

rate = snapshot.lending_rate("morpho_blue", "WETH", "borrow") print(f"Morpho WETH Borrow APY: {rate.apy_percent:.2f}%")

### best_lending_rate

```
best_lending_rate(
    token: str,
    side: str = "supply",
    protocols: list[str] | None = None,
) -> BestRateResult
```

Get the best lending rate for a token across protocols.

Compares rates from all available lending protocols and returns the optimal one. For supply rates, returns highest APY. For borrow rates, returns lowest APY.

Parameters:

| Name        | Type        | Description                                         | Default                                                |
| ----------- | ----------- | --------------------------------------------------- | ------------------------------------------------------ |
| `token`     | `str`       | Token symbol (e.g., "USDC", "WETH")                 | *required*                                             |
| `side`      | `str`       | Rate side - "supply" or "borrow" (default "supply") | `'supply'`                                             |
| `protocols` | \`list[str] | None\`                                              | Protocols to compare (default: all available on chain) |

Returns:

| Type             | Description                                                                                                                                                                                                                          |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `BestRateResult` | BestRateResult dataclass with: - token: Token symbol - side: Rate side - best_rate: The best LendingRate found (or None if all failed) - all_rates: List of all rates from different protocols - timestamp: When comparison was made |

Raises:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `ValueError` | If no rate monitor is configured |

Example

#### Find best USDC supply rate

result = snapshot.best_lending_rate("USDC", "supply") if result.best_rate: print(f"Best rate: {result.best_rate.protocol} at {result.best_rate.apy_percent:.2f}%")

```
# Compare all protocols
for rate in result.all_rates:
    print(f"  {rate.protocol}: {rate.apy_percent:.2f}%")
```

### funding_rate

```
funding_rate(venue: str, market: str) -> FundingRate
```

Get the funding rate for a perpetual venue and market.

Fetches the current funding rate from the specified venue. Funding rates indicate the cost of holding perpetual positions:

- Positive rate: Longs pay shorts (bullish market)
- Negative rate: Shorts pay longs (bearish market)

Parameters:

| Name     | Type  | Description                                | Default    |
| -------- | ----- | ------------------------------------------ | ---------- |
| `venue`  | `str` | Venue identifier (gmx_v2, hyperliquid)     | *required* |
| `market` | `str` | Market symbol (e.g., "ETH-USD", "BTC-USD") | *required* |

Returns:

| Type          | Description                                                                                                                                                                                                                                                                                                                                                                                                               |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FundingRate` | FundingRate dataclass with: - venue: Venue identifier - market: Market symbol - rate_hourly: Hourly funding rate - rate_8h: 8-hour funding rate (typical display) - rate_annualized: Annualized rate for comparison - next_funding_time: Next settlement time - open_interest_long: Total long OI in USD - open_interest_short: Total short OI in USD - mark_price: Current mark price - index_price: Current index price |

Raises:

| Type                          | Description                               |
| ----------------------------- | ----------------------------------------- |
| `FundingRateUnavailableError` | If rate cannot be retrieved               |
| `ValueError`                  | If no funding rate provider is configured |

Example

#### Get GMX V2 ETH funding rate

rate = snapshot.funding_rate("gmx_v2", "ETH-USD") print(f"8h rate: {rate.rate_percent_8h:.4f}%") print(f"Annualized: {rate.rate_percent_annualized:.2f}%")

#### Check if longs are paying

if rate.is_positive: print("Longs pay shorts (bullish sentiment)")

### funding_rate_spread

```
funding_rate_spread(
    market: str, venue_a: str, venue_b: str
) -> FundingRateSpread
```

Get the funding rate spread between two venues.

Compares funding rates from two venues to identify arbitrage opportunities. A positive spread means venue_a has higher funding than venue_b.

The spread can be used for funding rate arbitrage:

- If spread > 0: Short venue_a, long venue_b
- If spread < 0: Short venue_b, long venue_a

Parameters:

| Name      | Type  | Description                     | Default    |
| --------- | ----- | ------------------------------- | ---------- |
| `market`  | `str` | Market symbol (e.g., "ETH-USD") | *required* |
| `venue_a` | `str` | First venue identifier          | *required* |
| `venue_b` | `str` | Second venue identifier         | *required* |

Returns:

| Type                | Description                                                                                                                                                                                                                                                                                                                                                         |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FundingRateSpread` | FundingRateSpread dataclass with: - market: Market symbol - venue_a: First venue - venue_b: Second venue - rate_a: Funding rate at venue_a - rate_b: Funding rate at venue_b - spread_8h: 8-hour spread (rate_a - rate_b) - spread_annualized: Annualized spread - is_profitable: True if spread exceeds threshold - recommended_direction: Trade direction for arb |

Raises:

| Type                          | Description                               |
| ----------------------------- | ----------------------------------------- |
| `FundingRateUnavailableError` | If either rate cannot be retrieved        |
| `ValueError`                  | If no funding rate provider is configured |

Example

#### Compare GMX V2 vs Hyperliquid for ETH

spread = snapshot.funding_rate_spread( "ETH-USD", "gmx_v2", "hyperliquid" ) print(f"Spread: {spread.spread_percent_8h:.4f}% (8h)")

if spread.is_profitable: print(f"Arbitrage opportunity: {spread.recommended_direction}")

### lending_rate_history

```
lending_rate_history(
    protocol: str,
    token: str,
    chain: str | None = None,
    days: int = 90,
) -> DataEnvelope[list[LendingRateSnapshot]]
```

Get historical lending rate snapshots for backtesting.

Fetches supply/borrow APY history from The Graph or DeFi Llama with graceful fallback between providers. Results are cached in VersionedDataCache for deterministic replay.

Parameters:

| Name       | Type  | Description                                                      | Default                                 |
| ---------- | ----- | ---------------------------------------------------------------- | --------------------------------------- |
| `protocol` | `str` | Lending protocol (e.g. "aave_v3", "morpho_blue", "compound_v3"). | *required*                              |
| `token`    | `str` | Token symbol (e.g. "USDC", "WETH").                              | *required*                              |
| `chain`    | \`str | None\`                                                           | Chain name. Defaults to strategy chain. |
| `days`     | `int` | Number of days of history. Default 90.                           | `90`                                    |

Returns:

| Type                                      | Description                                                                  |
| ----------------------------------------- | ---------------------------------------------------------------------------- |
| `DataEnvelope[list[LendingRateSnapshot]]` | DataEnvelope\[list[LendingRateSnapshot]\] with INFORMATIONAL classification. |
| `DataEnvelope[list[LendingRateSnapshot]]` | Snapshots are sorted ascending by timestamp.                                 |

Raises:

| Type                                 | Description                              |
| ------------------------------------ | ---------------------------------------- |
| `LendingRateHistoryUnavailableError` | If historical data cannot be retrieved.  |
| `ValueError`                         | If no rate history reader is configured. |

Example

envelope = snapshot.lending_rate_history("aave_v3", "USDC", days=90) for snap in envelope.value: print(f"Supply: {snap.supply_apy}%, Borrow: {snap.borrow_apy}%")

### funding_rate_history

```
funding_rate_history(
    venue: str, market_symbol: str, hours: int = 168
) -> DataEnvelope[list[FundingRateSnapshot]]
```

Get historical funding rate snapshots for backtesting.

Fetches funding rate history from Hyperliquid API or DeFi Llama with graceful fallback. Results are cached in VersionedDataCache for deterministic replay.

Parameters:

| Name            | Type  | Description                                       | Default    |
| --------------- | ----- | ------------------------------------------------- | ---------- |
| `venue`         | `str` | Perps venue (e.g. "hyperliquid", "gmx_v2").       | *required* |
| `market_symbol` | `str` | Market symbol (e.g. "ETH-USD", "BTC-USD").        | *required* |
| `hours`         | `int` | Number of hours of history. Default 168 (7 days). | `168`      |

Returns:

| Type                                      | Description                                                                  |
| ----------------------------------------- | ---------------------------------------------------------------------------- |
| `DataEnvelope[list[FundingRateSnapshot]]` | DataEnvelope\[list[FundingRateSnapshot]\] with INFORMATIONAL classification. |
| `DataEnvelope[list[FundingRateSnapshot]]` | Snapshots are sorted ascending by timestamp.                                 |

Raises:

| Type                                 | Description                              |
| ------------------------------------ | ---------------------------------------- |
| `FundingRateHistoryUnavailableError` | If historical data cannot be retrieved.  |
| `ValueError`                         | If no rate history reader is configured. |

Example

envelope = snapshot.funding_rate_history("hyperliquid", "ETH-USD", hours=168) for snap in envelope.value: print(f"Rate: {snap.rate}, Annualized: {snap.annualized_rate}")

### price_across_dexs

```
price_across_dexs(
    token_in: str,
    token_out: str,
    amount: Decimal,
    dexs: list[str] | None = None,
) -> MultiDexPriceResult
```

Get prices from multiple DEXs for comparison.

Fetches quotes from all configured DEXs (Uniswap V3, Curve, Enso) and returns a comparison of prices and execution details. Use this to identify the best execution venue for a swap.

Parameters:

| Name        | Type        | Description                                                   | Default                                         |
| ----------- | ----------- | ------------------------------------------------------------- | ----------------------------------------------- |
| `token_in`  | `str`       | Input token symbol (e.g., "USDC", "WETH")                     | *required*                                      |
| `token_out` | `str`       | Output token symbol (e.g., "WETH", "USDC")                    | *required*                                      |
| `amount`    | `Decimal`   | Input amount (human-readable, e.g., Decimal("10000") for 10k) | *required*                                      |
| `dexs`      | \`list[str] | None\`                                                        | DEXs to query (default: all available on chain) |

Returns:

| Type                  | Description                                                                                                                                                                                                                                                                             |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `MultiDexPriceResult` | MultiDexPriceResult dataclass with: - token_in: Input token symbol - token_out: Output token symbol - amount_in: Input amount - quotes: Dictionary mapping DEX name to DexQuote - best_quote: Quote with highest output amount - price_spread_bps: Spread between best and worst in bps |

Raises:

| Type                       | Description                           |
| -------------------------- | ------------------------------------- |
| `DexQuoteUnavailableError` | If no quotes can be fetched           |
| `ValueError`               | If no multi-DEX service is configured |

Example

#### Compare prices for 10k USDC -> WETH

result = snapshot.price_across_dexs( "USDC", "WETH", Decimal("10000") )

for dex, quote in result.quotes.items(): print(f"{dex}: {quote.amount_out} WETH") print(f" Price impact: {quote.price_impact_bps} bps") print(f" Slippage estimate: {quote.slippage_estimate_bps} bps")

print(f"Best venue: {result.best_quote.dex}") print(f"Price spread: {result.price_spread_bps} bps")

### best_dex_price

```
best_dex_price(
    token_in: str,
    token_out: str,
    amount: Decimal,
    dexs: list[str] | None = None,
) -> BestDexResult
```

Get the best DEX for a trade.

Compares prices from all configured DEXs and returns the one with the highest output amount (best execution). This is useful for routing trades to the optimal venue.

Parameters:

| Name        | Type        | Description                                                   | Default                                           |
| ----------- | ----------- | ------------------------------------------------------------- | ------------------------------------------------- |
| `token_in`  | `str`       | Input token symbol (e.g., "USDC", "WETH")                     | *required*                                        |
| `token_out` | `str`       | Output token symbol (e.g., "WETH", "USDC")                    | *required*                                        |
| `amount`    | `Decimal`   | Input amount (human-readable, e.g., Decimal("10000") for 10k) | *required*                                        |
| `dexs`      | \`list[str] | None\`                                                        | DEXs to compare (default: all available on chain) |

Returns:

| Type            | Description                                                                                                                                                                                                                                                                                                               |
| --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `BestDexResult` | BestDexResult dataclass with: - token_in: Input token symbol - token_out: Output token symbol - amount_in: Input amount - best_dex: Best DEX for the trade (e.g., "uniswap_v3") - best_quote: DexQuote from the best DEX - all_quotes: List of quotes from all DEXs - savings_vs_worst_bps: Savings vs worst venue in bps |

Raises:

| Type         | Description                           |
| ------------ | ------------------------------------- |
| `ValueError` | If no multi-DEX service is configured |

Example

#### Find best venue for USDC -> WETH swap

result = snapshot.best_dex_price( "USDC", "WETH", Decimal("10000") )

if result.best_quote: print(f"Best DEX: {result.best_dex}") print(f"Output: {result.best_quote.amount_out} WETH") print(f"Savings vs worst: {result.savings_vs_worst_bps} bps") else: print("No quotes available")

### il_exposure

```
il_exposure(
    position_id: str, fees_earned: Decimal = Decimal("0")
) -> ILExposure
```

Get the impermanent loss exposure for a tracked LP position.

Calculates the current IL for a tracked LP position using the position's entry prices and current market prices. This method requires an ILCalculator with the position already registered.

The ILCalculator should be configured with the position via add_position() before calling this method.

Parameters:

| Name          | Type      | Description                                             | Default        |
| ------------- | --------- | ------------------------------------------------------- | -------------- |
| `position_id` | `str`     | Unique identifier for the LP position                   | *required*     |
| `fees_earned` | `Decimal` | Optional fees earned by the position (for net PnL calc) | `Decimal('0')` |

Returns:

| Type         | Description                                                                                                                                                                                                                                                                                           |
| ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ILExposure` | ILExposure dataclass with: - position_id: Position identifier - position: LPPosition details - current_il: ILResult with IL metrics - entry_value: Original position value - current_value: Current position value - fees_earned: Fees earned (if provided) - net_pnl: Net profit/loss including fees |

Raises:

| Type                         | Description                       |
| ---------------------------- | --------------------------------- |
| `ILExposureUnavailableError` | If exposure cannot be calculated  |
| `ValueError`                 | If no IL calculator is configured |

Example

#### Get IL exposure for a position

exposure = snapshot.il_exposure("my-lp-position-123") print(f"Current IL: {exposure.current_il.il_percent:.2f}%") print(f"Entry value: ${exposure.entry_value}") print(f"Current value: ${exposure.current_value}")

if exposure.il_offset_by_fees: print("Fees offset the IL - net positive!")

### projected_il

```
projected_il(
    token_a: str,
    token_b: str,
    price_change_pct: Decimal,
    weight_a: Decimal = Decimal("0.5"),
    weight_b: Decimal = Decimal("0.5"),
) -> ProjectedILResult
```

Project impermanent loss for a hypothetical price change.

This method simulates what IL would be if token A's price changed by the specified percentage relative to token B. This is useful for understanding IL risk before entering a position.

Parameters:

| Name               | Type      | Description                                               | Default          |
| ------------------ | --------- | --------------------------------------------------------- | ---------------- |
| `token_a`          | `str`     | Symbol of token A (the volatile token)                    | *required*       |
| `token_b`          | `str`     | Symbol of token B (often a stablecoin)                    | *required*       |
| `price_change_pct` | `Decimal` | Price change percentage (e.g., 50 for +50%, -30 for -30%) | *required*       |
| `weight_a`         | `Decimal` | Weight of token A in the pool (default 0.5)               | `Decimal('0.5')` |
| `weight_b`         | `Decimal` | Weight of token B in the pool (default 0.5)               | `Decimal('0.5')` |

Returns:

| Type                | Description                                                                                                                                                                                                                                                                                                                                                                 |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ProjectedILResult` | ProjectedILResult dataclass with: - price_change_pct: The input price change - il_ratio: Projected IL as decimal (e.g., -0.0057 for 0.57% loss) - il_percent: Projected IL as percentage (e.g., -0.57) - il_bps: Projected IL in basis points (e.g., -57) - pool_type: Type of pool (default: constant_product) - weight_a: Weight of token A - weight_b: Weight of token B |

Raises:

| Type         | Description                                             |
| ------------ | ------------------------------------------------------- |
| `ValueError` | If no IL calculator is configured or invalid parameters |

Example

#### What would IL be if ETH goes up 50%?

proj = snapshot.projected_il("WETH", "USDC", Decimal("50")) print(f"If ETH +50%: IL = {proj.il_percent:.2f}%")

#### What if ETH drops 30%?

proj = snapshot.projected_il("WETH", "USDC", Decimal("-30")) print(f"If ETH -30%: IL = {proj.il_percent:.2f}%")

#### Weighted pool (80/20 ETH/USDC)

proj = snapshot.projected_il( "WETH", "USDC", price_change_pct=Decimal("100"), weight_a=Decimal("0.8"), weight_b=Decimal("0.2"), ) print(f"80/20 pool, ETH +100%: IL = {proj.il_percent:.2f}%")

### prediction

```
prediction(market_id: str) -> PredictionMarket
```

Get prediction market data.

Fetches full market details for a prediction market by ID or slug. Uses lazy loading - only fetches prediction data when accessed.

Parameters:

| Name        | Type  | Description                                                                         | Default    |
| ----------- | ----- | ----------------------------------------------------------------------------------- | ---------- |
| `market_id` | `str` | Prediction market ID or URL slug Examples: "12345", "will-bitcoin-exceed-100k-2025" | *required* |

Returns:

| Type               | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `PredictionMarket` | PredictionMarket with: - market_id: Internal market ID - condition_id: CTF condition ID (0x...) - question: Market question text - slug: URL slug - yes_price: Current YES outcome price (0-1) - no_price: Current NO outcome price (0-1) - spread: Bid-ask spread - volume_24h: 24-hour trading volume in USDC - liquidity: Current liquidity - end_date: Resolution deadline - is_active: Whether market is accepting orders - is_resolved: Whether market has been resolved |

Raises:

| Type                         | Description                             |
| ---------------------------- | --------------------------------------- |
| `PredictionUnavailableError` | If market data cannot be retrieved      |
| `ValueError`                 | If no prediction provider is configured |

Example

#### Get market by ID

market = snapshot.prediction("12345") print(f"YES: {market.yes_price}, NO: {market.no_price}")

#### Get market by slug

market = snapshot.prediction("will-btc-hit-100k") print(f"Question: {market.question}") print(f"24h Volume: ${market.volume_24h:,.2f}")

#### Check implied probability

yes_prob = market.yes_price * 100 print(f"Implied probability: {yes_prob:.1f}%")

### prediction_positions

```
prediction_positions(
    market_id: str | None = None,
) -> list[PredictionPosition]
```

Get all open prediction market positions.

Fetches positions from the prediction market provider. Can optionally filter by market ID.

Parameters:

| Name        | Type  | Description | Default                                 |
| ----------- | ----- | ----------- | --------------------------------------- |
| `market_id` | \`str | None\`      | Optional market ID or slug to filter by |

Returns:

| Type                       | Description                                                                                                                                                                                                                                                                                                                                                                                                  |
| -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `list[PredictionPosition]` | List of PredictionPosition objects with: - market_id: Market ID - condition_id: CTF condition ID - token_id: CLOB token ID - outcome: Position outcome (YES or NO) - size: Number of shares held - avg_price: Average entry price - current_price: Current market price - unrealized_pnl: Unrealized profit/loss - realized_pnl: Realized profit/loss - value: Current position value (size * current_price) |

Raises:

| Type                         | Description                             |
| ---------------------------- | --------------------------------------- |
| `PredictionUnavailableError` | If positions cannot be retrieved        |
| `ValueError`                 | If no prediction provider is configured |

Example

#### Get all positions

positions = snapshot.prediction_positions() total_value = sum(p.value for p in positions) print(f"Total position value: ${total_value:,.2f}")

#### Get positions for a specific market

positions = snapshot.prediction_positions("btc-100k") for pos in positions: print(f"{pos.outcome}: {pos.size} shares @ {pos.avg_price}") print(f" Unrealized PnL: ${pos.unrealized_pnl:,.2f}")

### prediction_orders

```
prediction_orders(
    market_id: str | None = None,
) -> list[PredictionOrder]
```

Get all open prediction market orders.

Fetches open orders from the prediction market provider. Can optionally filter by market ID.

Parameters:

| Name        | Type  | Description | Default                                 |
| ----------- | ----- | ----------- | --------------------------------------- |
| `market_id` | \`str | None\`      | Optional market ID or slug to filter by |

Returns:

| Type                    | Description                                                                                                                                                                                                                                                                                                                        |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `list[PredictionOrder]` | List of PredictionOrder objects with: - order_id: Order ID - market_id: Market ID (token ID) - outcome: Order outcome (YES or NO) - side: Order side (BUY or SELL) - price: Order price - size: Order size in shares - filled_size: Filled amount - remaining_size: Remaining unfilled size - created_at: Order creation timestamp |

Raises:

| Type                         | Description                             |
| ---------------------------- | --------------------------------------- |
| `PredictionUnavailableError` | If orders cannot be retrieved           |
| `ValueError`                 | If no prediction provider is configured |

Example

#### Get all open orders

orders = snapshot.prediction_orders() for order in orders: print(f"{order.side} {order.remaining_size} @ {order.price}")

#### Get orders for a specific market

orders = snapshot.prediction_orders("btc-100k") buy_orders = [o for o in orders if o.side == "BUY"] print(f"Open buy orders: {len(buy_orders)}")

### prediction_price

```
prediction_price(
    market_id: str, outcome: str = "YES"
) -> Decimal
```

Get the current price of a prediction market outcome.

Convenience method that fetches the market and returns the price for the specified outcome. This is equivalent to calling prediction(market_id).yes_price or .no_price.

Parameters:

| Name        | Type  | Description                                               | Default    |
| ----------- | ----- | --------------------------------------------------------- | ---------- |
| `market_id` | `str` | Prediction market ID or URL slug                          | *required* |
| `outcome`   | `str` | Outcome to get price for - "YES" or "NO" (default: "YES") | `'YES'`    |

Returns:

| Type      | Description                                                    |
| --------- | -------------------------------------------------------------- |
| `Decimal` | Price as Decimal (0-1 range, representing implied probability) |

Raises:

| Type                         | Description                                                |
| ---------------------------- | ---------------------------------------------------------- |
| `PredictionUnavailableError` | If market data cannot be retrieved                         |
| `ValueError`                 | If no prediction provider is configured or invalid outcome |

Example

yes_price = snapshot.prediction_price("will-btc-hit-100k") print(f"YES price: {yes_price:.4f}")

no_price = snapshot.prediction_price("will-btc-hit-100k", "NO") print(f"NO price: {no_price:.4f}")

### realized_vol

```
realized_vol(
    token: str,
    window_days: int = 30,
    timeframe: str = "1h",
    estimator: str = "close_to_close",
    *,
    ohlcv_limit: int | None = None,
) -> DataEnvelope[VolatilityResult]
```

Calculate realized volatility for a token.

Fetches OHLCV candles via the configured providers and computes realized volatility using the specified estimator. Requires either an OHLCVRouter or an OHLCVModule for data, and a RealizedVolatilityCalculator for the computation.

Parameters:

| Name          | Type  | Description                                               | Default                                                                                           |
| ------------- | ----- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `token`       | `str` | Token symbol (e.g. "WETH", "ETH").                        | *required*                                                                                        |
| `window_days` | `int` | Lookback window in calendar days. Default 30.             | `30`                                                                                              |
| `timeframe`   | `str` | Candle timeframe (1m, 5m, 15m, 1h, 4h, 1d). Default "1h". | `'1h'`                                                                                            |
| `estimator`   | `str` | "close_to_close" (default) or "parkinson".                | `'close_to_close'`                                                                                |
| `ohlcv_limit` | \`int | None\`                                                    | Override for number of candles to fetch. If None, auto-calculated from window_days and timeframe. |

Returns:

| Type                             | Description                                                       |
| -------------------------------- | ----------------------------------------------------------------- |
| `DataEnvelope[VolatilityResult]` | DataEnvelope[VolatilityResult] with INFORMATIONAL classification. |

Raises:

| Type                         | Description                                |
| ---------------------------- | ------------------------------------------ |
| `VolatilityUnavailableError` | If volatility cannot be calculated.        |
| `ValueError`                 | If no volatility calculator is configured. |

Example

result = snapshot.realized_vol("WETH", window_days=30, timeframe="1h") print(f"Annualized vol: {result.value.annualized_vol:.2%}") print(f"Daily vol: {result.value.daily_vol:.2%}")

### vol_cone

```
vol_cone(
    token: str,
    windows: list[int] | None = None,
    timeframe: str = "1h",
    estimator: str = "close_to_close",
    *,
    ohlcv_limit: int | None = None,
) -> DataEnvelope[VolConeResult]
```

Compute volatility cone: current vol vs historical percentile.

For each window, calculates the current realized vol and compares it to the historical distribution of rolling vols over the full candle history.

Parameters:

| Name          | Type        | Description                      | Default                                                                                   |
| ------------- | ----------- | -------------------------------- | ----------------------------------------------------------------------------------------- |
| `token`       | `str`       | Token symbol (e.g. "WETH").      | *required*                                                                                |
| `windows`     | \`list[int] | None\`                           | Lookback windows in days. Default [7, 14, 30, 90].                                        |
| `timeframe`   | `str`       | Candle timeframe. Default "1h".  | `'1h'`                                                                                    |
| `estimator`   | `str`       | "close_to_close" or "parkinson". | `'close_to_close'`                                                                        |
| `ohlcv_limit` | \`int       | None\`                           | Override for number of candles to fetch. If None, auto-calculated for the largest window. |

Returns:

| Type                          | Description                                                    |
| ----------------------------- | -------------------------------------------------------------- |
| `DataEnvelope[VolConeResult]` | DataEnvelope[VolConeResult] with INFORMATIONAL classification. |

Raises:

| Type                      | Description                                |
| ------------------------- | ------------------------------------------ |
| `VolConeUnavailableError` | If vol cone cannot be calculated.          |
| `ValueError`              | If no volatility calculator is configured. |

Example

cone = snapshot.vol_cone("WETH", windows=[7, 14, 30, 90]) for entry in cone.value.entries: print(f"{entry.window_days}d: {entry.current_vol:.2%} (p{entry.percentile:.0f})")

### portfolio_risk

```
portfolio_risk(
    pnl_series: list[float],
    total_value_usd: Decimal | None = None,
    return_interval: str = "1d",
    risk_free_rate: Decimal = Decimal("0"),
    var_method: str = "parametric",
    timestamps: list[datetime] | None = None,
    benchmark_eth_returns: list[float] | None = None,
    benchmark_btc_returns: list[float] | None = None,
) -> DataEnvelope[PortfolioRisk]
```

Calculate portfolio risk metrics from a PnL return series.

Computes Sharpe ratio, Sortino ratio, VaR, CVaR, and drawdown with explicit conventions for unambiguous results.

Parameters:

| Name                    | Type             | Description                                                  | Default                                                   |
| ----------------------- | ---------------- | ------------------------------------------------------------ | --------------------------------------------------------- |
| `pnl_series`            | `list[float]`    | List of periodic returns as fractions (0.01 = 1% gain).      | *required*                                                |
| `total_value_usd`       | \`Decimal        | None\`                                                       | Current portfolio value in USD. Defaults to Decimal("0"). |
| `return_interval`       | `str`            | Periodicity of returns (1d, 1h, etc.).                       | `'1d'`                                                    |
| `risk_free_rate`        | `Decimal`        | Risk-free rate per period as a decimal.                      | `Decimal('0')`                                            |
| `var_method`            | `str`            | VaR method: "parametric", "historical", or "cornish_fisher". | `'parametric'`                                            |
| `timestamps`            | \`list[datetime] | None\`                                                       | Optional timestamps for each return.                      |
| `benchmark_eth_returns` | \`list[float]    | None\`                                                       | Optional ETH returns for beta calculation.                |
| `benchmark_btc_returns` | \`list[float]    | None\`                                                       | Optional BTC returns for beta calculation.                |

Returns:

| Type                          | Description                                                    |
| ----------------------------- | -------------------------------------------------------------- |
| `DataEnvelope[PortfolioRisk]` | DataEnvelope[PortfolioRisk] with INFORMATIONAL classification. |

Raises:

| Type                            | Description                           |
| ------------------------------- | ------------------------------------- |
| `PortfolioRiskUnavailableError` | If risk metrics cannot be calculated. |
| `ValueError`                    | If no risk calculator is configured.  |

Example

risk = snapshot.portfolio_risk(pnl_series, total_value_usd=Decimal("100000")) print(f"Sharpe: {risk.value.sharpe_ratio:.2f}") print(f"VaR 95%: ${risk.value.var_95}")

### rolling_sharpe

```
rolling_sharpe(
    pnl_series: list[float],
    window_days: int = 30,
    return_interval: str = "1d",
    risk_free_rate: Decimal = Decimal("0"),
    timestamps: list[datetime] | None = None,
) -> DataEnvelope[RollingSharpeResult]
```

Compute rolling Sharpe ratio over a PnL series.

Parameters:

| Name              | Type             | Description                            | Default                                      |
| ----------------- | ---------------- | -------------------------------------- | -------------------------------------------- |
| `pnl_series`      | `list[float]`    | List of periodic returns as fractions. | *required*                                   |
| `window_days`     | `int`            | Rolling window in days. Default 30.    | `30`                                         |
| `return_interval` | `str`            | Periodicity of returns (1d, 1h, etc.). | `'1d'`                                       |
| `risk_free_rate`  | `Decimal`        | Risk-free rate per period.             | `Decimal('0')`                               |
| `timestamps`      | \`list[datetime] | None\`                                 | Optional timestamps aligned with pnl_series. |

Returns:

| Type                                | Description                                                          |
| ----------------------------------- | -------------------------------------------------------------------- |
| `DataEnvelope[RollingSharpeResult]` | DataEnvelope[RollingSharpeResult] with INFORMATIONAL classification. |

Raises:

| Type                            | Description                           |
| ------------------------------- | ------------------------------------- |
| `RollingSharpeUnavailableError` | If rolling Sharpe cannot be computed. |
| `ValueError`                    | If no risk calculator is configured.  |

Example

result = snapshot.rolling_sharpe(pnl_series, window_days=30) for entry in result.value.entries: print(f"{entry.timestamp}: Sharpe={entry.sharpe:.2f}")

### pool_analytics

```
pool_analytics(
    pool_address: str,
    chain: str | None = None,
    protocol: str | None = None,
) -> DataEnvelope[PoolAnalytics]
```

Get real-time analytics for a pool (TVL, volume, fee APR/APY).

Fetches from DeFi Llama (primary) or GeckoTerminal (fallback). Cache TTL: 5 minutes.

Parameters:

| Name           | Type  | Description            | Default                                     |
| -------------- | ----- | ---------------------- | ------------------------------------------- |
| `pool_address` | `str` | Pool contract address. | *required*                                  |
| `chain`        | \`str | None\`                 | Chain name. Defaults to strategy chain.     |
| `protocol`     | \`str | None\`                 | Optional protocol hint (e.g. "uniswap_v3"). |

Returns:

| Type                          | Description                                                    |
| ----------------------------- | -------------------------------------------------------------- |
| `DataEnvelope[PoolAnalytics]` | DataEnvelope[PoolAnalytics] with INFORMATIONAL classification. |

Raises:

| Type                            | Description                                |
| ------------------------------- | ------------------------------------------ |
| `PoolAnalyticsUnavailableError` | If analytics cannot be retrieved.          |
| `ValueError`                    | If no pool analytics reader is configured. |

### best_pool

```
best_pool(
    token_a: str,
    token_b: str,
    chain: str | None = None,
    metric: str = "fee_apr",
    protocols: list[str] | None = None,
) -> DataEnvelope[PoolAnalyticsResult]
```

Find the best pool for a token pair based on a metric.

Searches DeFi Llama for matching pools and ranks by metric.

Parameters:

| Name        | Type        | Description                                                        | Default                                  |
| ----------- | ----------- | ------------------------------------------------------------------ | ---------------------------------------- |
| `token_a`   | `str`       | First token symbol (e.g. "WETH").                                  | *required*                               |
| `token_b`   | `str`       | Second token symbol (e.g. "USDC").                                 | *required*                               |
| `chain`     | \`str       | None\`                                                             | Chain name. Defaults to strategy chain.  |
| `metric`    | `str`       | Sorting metric: "fee_apr", "fee_apy", "tvl_usd", "volume_24h_usd". | `'fee_apr'`                              |
| `protocols` | \`list[str] | None\`                                                             | Optional list of protocols to filter by. |

Returns:

| Type                                | Description                                           |
| ----------------------------------- | ----------------------------------------------------- |
| `DataEnvelope[PoolAnalyticsResult]` | DataEnvelope[PoolAnalyticsResult] with the best pool. |

Raises:

| Type                            | Description                                |
| ------------------------------- | ------------------------------------------ |
| `PoolAnalyticsUnavailableError` | If no pools found or all providers fail.   |
| `ValueError`                    | If no pool analytics reader is configured. |

### yield_opportunities

```
yield_opportunities(
    token: str,
    chains: list[str] | None = None,
    min_tvl: float = 100000,
    sort_by: str = "apy",
) -> DataEnvelope[list[YieldOpportunity]]
```

Find yield opportunities for a token across protocols and chains.

Searches DeFi Llama yields API for matching pools, sorted by the chosen metric. Cache TTL: 15 minutes.

Parameters:

| Name      | Type        | Description                                            | Default                                            |
| --------- | ----------- | ------------------------------------------------------ | -------------------------------------------------- |
| `token`   | `str`       | Token symbol (e.g. "USDC", "WETH").                    | *required*                                         |
| `chains`  | \`list[str] | None\`                                                 | Optional list of chains to filter. None means all. |
| `min_tvl` | `float`     | Minimum TVL in USD. Default $100k.                     | `100000`                                           |
| `sort_by` | `str`       | Sort field: "apy", "tvl", "risk_score". Default "apy". | `'apy'`                                            |

Returns:

| Type                                   | Description                                                     |
| -------------------------------------- | --------------------------------------------------------------- |
| `DataEnvelope[list[YieldOpportunity]]` | DataEnvelope\[list[YieldOpportunity]\] sorted by chosen metric. |

Raises:

| Type                                 | Description                           |
| ------------------------------------ | ------------------------------------- |
| `YieldOpportunitiesUnavailableError` | If data cannot be retrieved.          |
| `ValueError`                         | If no yield aggregator is configured. |

### lst_exchange_rate

```
lst_exchange_rate(symbol: str) -> LSTExchangeRate
```

Get Solana LST exchange rate vs SOL.

Parameters:

| Name     | Type  | Description                                                                                 | Default    |
| -------- | ----- | ------------------------------------------------------------------------------------------- | ---------- |
| `symbol` | `str` | LST symbol (e.g. "jitoSOL", "mSOL", "bSOL", "INF"). Case-insensitive aliases are supported. | *required* |

Returns:

| Type              | Description                                    |
| ----------------- | ---------------------------------------------- |
| `LSTExchangeRate` | LSTExchangeRate with rate vs SOL and APY data. |

Raises:

| Type                      | Description                                            |
| ------------------------- | ------------------------------------------------------ |
| `LSTDataUnavailableError` | If data cannot be retrieved.                           |
| `ValueError`              | If no LST provider is configured or symbol is unknown. |

### lst_all_rates

```
lst_all_rates() -> dict[str, LSTExchangeRate]
```

Get exchange rates for all tracked Solana LSTs.

Returns:

| Type                         | Description                             |
| ---------------------------- | --------------------------------------- |
| `dict[str, LSTExchangeRate]` | Dict mapping symbol -> LSTExchangeRate. |

Raises:

| Type                      | Description                       |
| ------------------------- | --------------------------------- |
| `LSTDataUnavailableError` | If data cannot be retrieved.      |
| `ValueError`              | If no LST provider is configured. |

### position_health

```
position_health(
    protocol: str,
    market_id: str,
    rpc_url: str | None = None,
    collateral_price_usd: Decimal | None = None,
    debt_price_usd: Decimal | None = None,
) -> PositionHealth
```

Get health factor for a lending position.

Reads on-chain position data and computes health factor for Morpho Blue or Aave V3 positions.

Parameters:

| Name                   | Type      | Description                         | Default                                     |
| ---------------------- | --------- | ----------------------------------- | ------------------------------------------- |
| `protocol`             | `str`     | "morpho_blue" or "aave_v3"          | *required*                                  |
| `market_id`            | `str`     | Protocol-specific market identifier | *required*                                  |
| `rpc_url`              | \`str     | None\`                              | RPC endpoint (uses default if not provided) |
| `collateral_price_usd` | \`Decimal | None\`                              | Optional override for collateral price      |
| `debt_price_usd`       | \`Decimal | None\`                              | Optional override for debt token price      |

Returns:

| Type             | Description                                |
| ---------------- | ------------------------------------------ |
| `PositionHealth` | PositionHealth with computed health factor |

Raises:

| Type                  | Description                        |
| --------------------- | ---------------------------------- |
| `MarketSnapshotError` | If health data cannot be retrieved |

### pt_position_health

```
pt_position_health(
    morpho_market_id: str,
    pendle_market_address: str,
    rpc_url: str | None = None,
    collateral_price_usd: Decimal | None = None,
    debt_price_usd: Decimal | None = None,
) -> PTPositionHealth
```

Get extended health data for a PT-collateral position.

Combines Morpho Blue position data with Pendle market metrics (implied APY, maturity risk) for comprehensive risk assessment.

Parameters:

| Name                    | Type      | Description                      | Default                                     |
| ----------------------- | --------- | -------------------------------- | ------------------------------------------- |
| `morpho_market_id`      | `str`     | Morpho Blue market ID            | *required*                                  |
| `pendle_market_address` | `str`     | Pendle market address for the PT | *required*                                  |
| `rpc_url`               | \`str     | None\`                           | RPC endpoint (uses default if not provided) |
| `collateral_price_usd`  | \`Decimal | None\`                           | Override for PT collateral price            |
| `debt_price_usd`        | \`Decimal | None\`                           | Override for debt token price               |

Returns:

| Type               | Description                                        |
| ------------------ | -------------------------------------------------- |
| `PTPositionHealth` | PTPositionHealth with Morpho + Pendle risk metrics |

Raises:

| Type                  | Description                        |
| --------------------- | ---------------------------------- |
| `MarketSnapshotError` | If health data cannot be retrieved |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert snapshot state to dictionary for serialization.

Returns:

| Type             | Description                                         |
| ---------------- | --------------------------------------------------- |
| `dict[str, Any]` | Dictionary with snapshot metadata and cached values |

## Price Data

### PriceOracle

## almanak.framework.data.PriceOracle

Bases: `Protocol`

Protocol for price aggregation and oracle logic.

A PriceOracle wraps one or more BasePriceSource implementations and provides aggregation logic (median, weighted average, etc.) along with outlier detection and graceful degradation.

The oracle is the primary interface for strategies to get price data, abstracting away the complexity of managing multiple sources.

Key responsibilities:

- Aggregate prices from multiple sources
- Detect and filter outliers (>2% deviation from median)
- Handle partial failures (some sources down)
- Track source health metrics for routing decisions

Example implementation

class PriceAggregator: def **init**(self, sources: list[BasePriceSource]): self.\_sources = sources self.\_health_metrics: dict[str, SourceHealthMetrics] = {}

```
async def get_aggregated_price(
    self, token: str, quote: str = "USD"
) -> PriceResult:
    results = []
    errors = {}

    for source in self._sources:
        try:
            result = await source.get_price(token, quote)
            results.append(result)
        except Exception as e:
            errors[source.source_name] = str(e)

    if not results:
        raise AllDataSourcesFailed(errors)

    # Return median price with aggregated confidence
    median_price = self._calculate_median(results)
    confidence = self._calculate_confidence(results)
    return PriceResult(
        price=median_price,
        source="aggregated",
        timestamp=datetime.now(timezone.utc),
        confidence=confidence,
    )
```

### get_aggregated_price

```
get_aggregated_price(
    token: str, quote: str = "USD"
) -> PriceResult
```

Get aggregated price from multiple sources.

Parameters:

| Name    | Type  | Description                    | Default    |
| ------- | ----- | ------------------------------ | ---------- |
| `token` | `str` | Token symbol to get price for  | *required* |
| `quote` | `str` | Quote currency (default "USD") | `'USD'`    |

Returns:

| Type          | Description                                      |
| ------------- | ------------------------------------------------ |
| `PriceResult` | PriceResult with aggregated price and confidence |

Raises:

| Type                   | Description                         |
| ---------------------- | ----------------------------------- |
| `AllDataSourcesFailed` | If all sources fail to provide data |

### get_source_health

```
get_source_health(
    source_name: str,
) -> dict[str, Any] | None
```

Get health metrics for a specific source.

Returns metrics like success rate, average latency, last error time.

Parameters:

| Name          | Type  | Description                 | Default    |
| ------------- | ----- | --------------------------- | ---------- |
| `source_name` | `str` | Name of the source to query | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`dict[str, Any] | None\`      |

### AggregatedPrice

## almanak.framework.data.AggregatedPrice

```
AggregatedPrice(
    price: Decimal,
    sources: list[PoolContribution] = list(),
    block_range: tuple[int, int] = (0, 0),
    method: str = "lwap",
    window_seconds: int = 0,
    pool_count: int = 0,
)
```

Aggregated price from multiple pools or time periods.

Attributes:

| Name             | Type                     | Description                                                    |
| ---------------- | ------------------------ | -------------------------------------------------------------- |
| `price`          | `Decimal`                | The aggregated price value.                                    |
| `sources`        | `list[PoolContribution]` | List of pool contributions with individual prices and weights. |
| `block_range`    | `tuple[int, int]`        | Tuple of (min_block, max_block) covered by the aggregation.    |
| `method`         | `str`                    | Aggregation method used ("twap" or "lwap").                    |
| `window_seconds` | `int`                    | Time window in seconds (for TWAP).                             |
| `pool_count`     | `int`                    | Number of pools used in aggregation.                           |

### PriceAggregator

## almanak.framework.data.PriceAggregator

```
PriceAggregator(
    pool_registry: PoolReaderRegistry,
    rpc_call: RpcCallFn,
    min_liquidity_usd: Decimal = DEFAULT_MIN_LIQUIDITY_USD,
    reference_price_usd: Decimal | None = None,
)
```

Aggregates prices across pools using TWAP and LWAP methods.

TWAP uses Uniswap V3's built-in oracle (observe()) for time-weighted average prices. LWAP reads live prices from multiple pools and weights them by in-range liquidity.

All results are EXECUTION_GRADE: fail-closed with no off-chain fallback.

Parameters:

| Name                  | Type                 | Description                                                | Default                                                                                                                                                      |
| --------------------- | -------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `pool_registry`       | `PoolReaderRegistry` | PoolReaderRegistry for reading pool prices.                | *required*                                                                                                                                                   |
| `rpc_call`            | `RpcCallFn`          | RPC call function for direct contract reads (observe()).   | *required*                                                                                                                                                   |
| `min_liquidity_usd`   | `Decimal`            | Minimum liquidity in USD to include a pool (default $10k). | `DEFAULT_MIN_LIQUIDITY_USD`                                                                                                                                  |
| `reference_price_usd` | \`Decimal            | None\`                                                     | Reference price for converting liquidity to USD. If None, liquidity filtering uses raw liquidity values and the threshold is treated as raw liquidity units. |

### twap

```
twap(
    pool_address: str,
    chain: str,
    window_seconds: int = 300,
    token0_decimals: int = 18,
    token1_decimals: int = 6,
    protocol: str = "uniswap_v3",
) -> DataEnvelope[AggregatedPrice]
```

Calculate TWAP using Uniswap V3 oracle observe().

Calls observe([window_seconds, 0]) on the pool contract to get tick cumulatives, then derives the arithmetic mean tick and converts to a price.

Parameters:

| Name              | Type  | Description                                   | Default        |
| ----------------- | ----- | --------------------------------------------- | -------------- |
| `pool_address`    | `str` | Pool contract address.                        | *required*     |
| `chain`           | `str` | Chain name.                                   | *required*     |
| `window_seconds`  | `int` | Time window in seconds (default 300 = 5 min). | `300`          |
| `token0_decimals` | `int` | Decimals of token0 (default 18).              | `18`           |
| `token1_decimals` | `int` | Decimals of token1 (default 6).               | `6`            |
| `protocol`        | `str` | Protocol name for source attribution.         | `'uniswap_v3'` |

Returns:

| Type                            | Description                                    |
| ------------------------------- | ---------------------------------------------- |
| `DataEnvelope[AggregatedPrice]` | DataEnvelope[AggregatedPrice] with TWAP price. |

Raises:

| Type                   | Description                                      |
| ---------------------- | ------------------------------------------------ |
| `DataUnavailableError` | If observe() call fails or returns invalid data. |

### lwap

```
lwap(
    token_a: str,
    token_b: str,
    chain: str,
    fee_tiers: list[int] | None = None,
    protocols: list[str] | None = None,
) -> DataEnvelope[AggregatedPrice]
```

Calculate liquidity-weighted average price across pools.

Reads live prices from all known pools for the given pair, filters out pools below the minimum liquidity threshold, then computes LWAP = sum(price_i * liquidity_i) / sum(liquidity_i).

Falls back to single-pool price if only one pool is available.

Parameters:

| Name        | Type        | Description                | Default                                                  |
| ----------- | ----------- | -------------------------- | -------------------------------------------------------- |
| `token_a`   | `str`       | Token A symbol or address. | *required*                                               |
| `token_b`   | `str`       | Token B symbol or address. | *required*                                               |
| `chain`     | `str`       | Chain name.                | *required*                                               |
| `fee_tiers` | \`list[int] | None\`                     | Fee tiers to search (default: [100, 500, 3000, 10000]).  |
| `protocols` | \`list[str] | None\`                     | Protocols to search (default: all registered for chain). |

Returns:

| Type                            | Description                                    |
| ------------------------------- | ---------------------------------------------- |
| `DataEnvelope[AggregatedPrice]` | DataEnvelope[AggregatedPrice] with LWAP price. |

Raises:

| Type                   | Description                                   |
| ---------------------- | --------------------------------------------- |
| `DataUnavailableError` | If no pools found for the pair (fail-closed). |

## Balance Data

### BalanceProvider

## almanak.framework.data.BalanceProvider

Bases: `Protocol`

Protocol for on-chain balance queries.

A BalanceProvider abstracts the complexity of querying ERC-20 and native token balances from the blockchain, handling decimal conversion, caching, and RPC error recovery.

Key responsibilities:

- Query ERC-20 balances via balanceOf
- Query native ETH balance via eth_getBalance
- Handle token decimal conversion correctly
- Cache balances to reduce RPC load
- Invalidate cache after transaction execution

Example implementation

class Web3BalanceProvider: def **init**(self, web3: Web3, wallet_address: str): self.\_web3 = web3 self.\_wallet = wallet_address self.\_cache: dict[str, BalanceResult] = {} self.\_cache_ttl = 5 # 5 second cache

```
async def get_balance(self, token: str) -> BalanceResult:
    if token == "ETH":
        return await self._get_native_balance()

    token_info = self._get_token_info(token)
    contract = self._web3.eth.contract(
        address=token_info.address,
        abi=ERC20_ABI,
    )
    raw_balance = contract.functions.balanceOf(self._wallet).call()
    balance = Decimal(raw_balance) / Decimal(10 ** token_info.decimals)

    return BalanceResult(
        balance=balance,
        token=token,
        address=token_info.address,
        decimals=token_info.decimals,
        raw_balance=raw_balance,
    )

def invalidate_cache(self, token: Optional[str] = None) -> None:
    if token:
        self._cache.pop(token, None)
    else:
        self._cache.clear()
```

### get_balance

```
get_balance(token: str) -> BalanceResult
```

Get the balance of a token for the configured wallet.

Parameters:

| Name    | Type  | Description                                             | Default    |
| ------- | ----- | ------------------------------------------------------- | ---------- |
| `token` | `str` | Token symbol (e.g., "WETH", "USDC") or "ETH" for native | *required* |

Returns:

| Type            | Description                                        |
| --------------- | -------------------------------------------------- |
| `BalanceResult` | BalanceResult with balance in human-readable units |

Raises:

| Type              | Description                  |
| ----------------- | ---------------------------- |
| `DataSourceError` | If balance cannot be fetched |

### get_native_balance

```
get_native_balance() -> BalanceResult
```

Get the native token balance (ETH, MATIC, etc.).

Convenience method for getting the chain's native token balance.

Returns:

| Type            | Description                    |
| --------------- | ------------------------------ |
| `BalanceResult` | BalanceResult for native token |

### invalidate_cache

```
invalidate_cache(token: str | None = None) -> None
```

Invalidate cached balances.

Should be called after transaction execution to ensure fresh data.

Parameters:

| Name    | Type  | Description | Default                                            |
| ------- | ----- | ----------- | -------------------------------------------------- |
| `token` | \`str | None\`      | Specific token to invalidate, or None to clear all |

## OHLCV Data

### OHLCVProvider

## almanak.framework.data.OHLCVProvider

Bases: `Protocol`

Protocol for OHLCV (candlestick) data providers.

Used by indicators like RSI that need historical price data.

Implementations must:

- Support multiple timeframes (1m, 5m, 15m, 1h, 4h, 1d)
- Return properly typed OHLCVCandle objects
- Handle caching to avoid repeated API calls
- Gracefully degrade if full history unavailable

The supported_timeframes property should return the subset of VALID_TIMEFRAMES that this provider can supply data for.

### supported_timeframes

```
supported_timeframes: list[str]
```

Return the list of timeframes this provider supports.

Returns:

| Type        | Description                                                    |
| ----------- | -------------------------------------------------------------- |
| `list[str]` | List of supported timeframe strings (e.g., ["1h", "4h", "1d"]) |

### get_ohlcv

```
get_ohlcv(
    token: str,
    quote: str = "USD",
    timeframe: str = "1h",
    limit: int = 100,
) -> list[OHLCVCandle]
```

Get OHLCV data for a token.

Parameters:

| Name        | Type  | Description                                        | Default    |
| ----------- | ----- | -------------------------------------------------- | ---------- |
| `token`     | `str` | Token symbol                                       | *required* |
| `quote`     | `str` | Quote currency                                     | `'USD'`    |
| `timeframe` | `str` | Candle timeframe (must be in supported_timeframes) | `'1h'`     |
| `limit`     | `int` | Number of candles to fetch                         | `100`      |

Returns:

| Type                | Description                                               |
| ------------------- | --------------------------------------------------------- |
| `list[OHLCVCandle]` | List of OHLCVCandle objects sorted by timestamp ascending |

Raises:

| Type                    | Description                               |
| ----------------------- | ----------------------------------------- |
| `DataSourceError`       | If data cannot be fetched                 |
| `InsufficientDataError` | If requested limit exceeds available data |
| `ValueError`            | If timeframe is not supported             |

### OHLCVData

## almanak.framework.data.OHLCVData

```
OHLCVData(
    timestamp: datetime,
    open: Decimal,
    high: Decimal,
    low: Decimal,
    close: Decimal,
    volume: Decimal | None = None,
)
```

OHLCV candlestick data point.

Attributes:

| Name        | Type       | Description      |
| ----------- | ---------- | ---------------- |
| `timestamp` | `datetime` | Candle open time |
| `open`      | `Decimal`  | Opening price    |
| `high`      | `Decimal`  | Highest price    |
| `low`       | `Decimal`  | Lowest price     |
| `close`     | `Decimal`  | Closing price    |
| `volume`    | \`Decimal  | None\`           |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

## Pool Analytics

### PoolAnalytics

## almanak.framework.data.PoolAnalytics

```
PoolAnalytics(
    pool_address: str,
    chain: str,
    protocol: str,
    tvl_usd: Decimal,
    volume_24h_usd: Decimal,
    volume_7d_usd: Decimal,
    fee_apr: float,
    fee_apy: float,
    utilization_rate: float | None = None,
    token0_weight: float = 0.5,
    token1_weight: float = 0.5,
)
```

Analytics for a single pool.

Attributes:

| Name               | Type      | Description                                                |
| ------------------ | --------- | ---------------------------------------------------------- |
| `pool_address`     | `str`     | Pool contract address.                                     |
| `chain`            | `str`     | Chain name.                                                |
| `protocol`         | `str`     | Protocol name (e.g. "uniswap_v3").                         |
| `tvl_usd`          | `Decimal` | Total value locked in USD.                                 |
| `volume_24h_usd`   | `Decimal` | 24-hour trading volume in USD.                             |
| `volume_7d_usd`    | `Decimal` | 7-day trading volume in USD.                               |
| `fee_apr`          | `float`   | Annualized fee return as a percentage (e.g. 12.5 = 12.5%). |
| `fee_apy`          | `float`   | Compounded annual fee return as a percentage.              |
| `utilization_rate` | \`float   | None\`                                                     |
| `token0_weight`    | `float`   | Fraction of TVL in token0 (0.0-1.0).                       |
| `token1_weight`    | `float`   | Fraction of TVL in token1 (0.0-1.0).                       |

### LiquidityDepth

## almanak.framework.data.LiquidityDepth

```
LiquidityDepth(
    ticks: list[TickData],
    total_liquidity: int,
    current_tick: int,
    current_price: Decimal,
    pool_address: str = "",
    token0_decimals: int = 18,
    token1_decimals: int = 6,
    tick_spacing: int = 60,
)
```

Tick-level liquidity distribution for a pool.

Attributes:

| Name              | Type             | Description                                         |
| ----------------- | ---------------- | --------------------------------------------------- |
| `ticks`           | `list[TickData]` | Initialized ticks sorted by tick_index (ascending). |
| `total_liquidity` | `int`            | Current in-range liquidity (L) from the pool.       |
| `current_tick`    | `int`            | Current active tick from slot0.                     |
| `current_price`   | `Decimal`        | Current price from slot0.                           |
| `pool_address`    | `str`            | Pool contract address.                              |
| `token0_decimals` | `int`            | Decimals of token0.                                 |
| `token1_decimals` | `int`            | Decimals of token1.                                 |
| `tick_spacing`    | `int`            | Tick spacing for this pool.                         |

## Volatility and Risk

### RealizedVolatilityCalculator

## almanak.framework.data.RealizedVolatilityCalculator

Computes realized volatility from OHLCV candle data.

Supports two estimators:

- **close_to_close**: Standard deviation of log returns. Simple and widely used, but only uses closing prices.
- **parkinson**: High-low range estimator. More efficient (lower variance) for the same sample size because it uses intra-period range information.

All volatilities are annualized using sqrt(periods_per_year).

### realized_vol

```
realized_vol(
    candles: list[OHLCVCandle],
    window_days: int = 30,
    timeframe: str = "1h",
    estimator: str = "close_to_close",
) -> VolatilityResult
```

Calculate realized volatility over a lookback window.

Parameters:

| Name          | Type                | Description                                                                                                    | Default            |
| ------------- | ------------------- | -------------------------------------------------------------------------------------------------------------- | ------------------ |
| `candles`     | `list[OHLCVCandle]` | OHLCV candles sorted ascending by timestamp. Should cover at least window_days of data at the given timeframe. | *required*         |
| `window_days` | `int`               | Lookback window in calendar days.                                                                              | `30`               |
| `timeframe`   | `str`               | Candle timeframe (1m, 5m, 15m, 1h, 4h, 1d).                                                                    | `'1h'`             |
| `estimator`   | `str`               | "close_to_close" (default) or "parkinson".                                                                     | `'close_to_close'` |

Returns:

| Type               | Description                                              |
| ------------------ | -------------------------------------------------------- |
| `VolatilityResult` | VolatilityResult with annualized, daily, and hourly vol. |

Raises:

| Type                    | Description                                       |
| ----------------------- | ------------------------------------------------- |
| `InsufficientDataError` | If fewer than 30 observations in the window.      |
| `ValueError`            | If timeframe is unsupported or estimator unknown. |

### vol_cone

```
vol_cone(
    candles: list[OHLCVCandle],
    windows: list[int] | None = None,
    timeframe: str = "1h",
    estimator: str = "close_to_close",
    token: str = "",
) -> VolConeResult
```

Compute volatility cone: current vol vs historical percentile.

For each window length, calculates the current realized vol and compares it to the distribution of rolling volatilities computed over the full candle history.

Parameters:

| Name        | Type                | Description                                                                                                                       | Default                                            |
| ----------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- |
| `candles`   | `list[OHLCVCandle]` | Full OHLCV history sorted ascending. Should be significantly longer than the largest window for meaningful percentile estimation. | *required*                                         |
| `windows`   | \`list[int]         | None\`                                                                                                                            | Lookback windows in days. Default [7, 14, 30, 90]. |
| `timeframe` | `str`               | Candle timeframe.                                                                                                                 | `'1h'`                                             |
| `estimator` | `str`               | "close_to_close" or "parkinson".                                                                                                  | `'close_to_close'`                                 |
| `token`     | `str`               | Token symbol for labeling.                                                                                                        | `''`                                               |

Returns:

| Type            | Description                                     |
| --------------- | ----------------------------------------------- |
| `VolConeResult` | VolConeResult with one VolConeEntry per window. |

Raises:

| Type                    | Description                                 |
| ----------------------- | ------------------------------------------- |
| `InsufficientDataError` | If not enough data for the smallest window. |
| `ValueError`            | If timeframe is unsupported.                |

### PortfolioRiskCalculator

## almanak.framework.data.PortfolioRiskCalculator

Computes portfolio risk metrics from PnL or return series.

All calculations use explicit conventions (return interval, risk-free rate, annualization) to ensure results are unambiguous and comparable.

Supports three VaR methods:

- **parametric**: Assumes normal distribution. VaR = -z * sigma * value.
- **historical**: Percentile-based from empirical return distribution.
- **cornish_fisher**: Adjusts z-score for skewness and kurtosis.

### portfolio_risk

```
portfolio_risk(
    pnl_series: list[float],
    total_value_usd: Decimal,
    return_interval: str = "1d",
    risk_free_rate: Decimal = Decimal("0"),
    var_method: VaRMethod = VaRMethod.PARAMETRIC,
    timestamps: list[datetime] | None = None,
    benchmark_eth_returns: list[float] | None = None,
    benchmark_btc_returns: list[float] | None = None,
) -> PortfolioRisk
```

Calculate portfolio risk metrics from a PnL series.

The pnl_series should contain periodic returns as fractions (e.g. 0.01 = 1% gain, -0.02 = 2% loss).

Parameters:

| Name                    | Type             | Description                                            | Default                                                |
| ----------------------- | ---------------- | ------------------------------------------------------ | ------------------------------------------------------ |
| `pnl_series`            | `list[float]`    | List of periodic returns (fractions, not percentages). | *required*                                             |
| `total_value_usd`       | `Decimal`        | Current portfolio value in USD.                        | *required*                                             |
| `return_interval`       | `str`            | Periodicity of the returns (1d, 1h, etc.).             | `'1d'`                                                 |
| `risk_free_rate`        | `Decimal`        | Risk-free rate per period as a decimal.                | `Decimal('0')`                                         |
| `var_method`            | `VaRMethod`      | VaR calculation method.                                | `PARAMETRIC`                                           |
| `timestamps`            | \`list[datetime] | None\`                                                 | Optional timestamps for each return (for conventions). |
| `benchmark_eth_returns` | \`list[float]    | None\`                                                 | Optional ETH returns for beta calculation.             |
| `benchmark_btc_returns` | \`list[float]    | None\`                                                 | Optional BTC returns for beta calculation.             |

Returns:

| Type            | Description                                              |
| --------------- | -------------------------------------------------------- |
| `PortfolioRisk` | PortfolioRisk with all metrics and explicit conventions. |

Raises:

| Type                    | Description                        |
| ----------------------- | ---------------------------------- |
| `InsufficientDataError` | If fewer than 30 observations.     |
| `ValueError`            | If return_interval is unsupported. |

### rolling_sharpe

```
rolling_sharpe(
    pnl_series: list[float],
    window_days: int = 30,
    return_interval: str = "1d",
    risk_free_rate: Decimal = Decimal("0"),
    timestamps: list[datetime] | None = None,
) -> RollingSharpeResult
```

Compute rolling Sharpe ratio over the PnL series.

Parameters:

| Name              | Type             | Description                 | Default                                      |
| ----------------- | ---------------- | --------------------------- | -------------------------------------------- |
| `pnl_series`      | `list[float]`    | List of periodic returns.   | *required*                                   |
| `window_days`     | `int`            | Rolling window in days.     | `30`                                         |
| `return_interval` | `str`            | Periodicity of the returns. | `'1d'`                                       |
| `risk_free_rate`  | `Decimal`        | Risk-free rate per period.  | `Decimal('0')`                               |
| `timestamps`      | \`list[datetime] | None\`                      | Optional timestamps aligned with pnl_series. |

Returns:

| Type                  | Description                                            |
| --------------------- | ------------------------------------------------------ |
| `RollingSharpeResult` | RollingSharpeResult with time series of Sharpe ratios. |

Raises:

| Type                    | Description                          |
| ----------------------- | ------------------------------------ |
| `InsufficientDataError` | If fewer than 30 observations total. |
| `ValueError`            | If return_interval is unsupported.   |

## Yield and Rates

### YieldAggregator

## almanak.framework.data.YieldAggregator

```
YieldAggregator(
    cache_ttl: int = 900, request_timeout: float = 15.0
)
```

Cross-protocol yield comparison using DeFi Llama.

Fetches yield data from DeFi Llama yields API, filtering by token, chain, TVL, and protocol. Results are cached for 15 minutes.

Parameters:

| Name              | Type    | Description                                               | Default |
| ----------------- | ------- | --------------------------------------------------------- | ------- |
| `cache_ttl`       | `int`   | In-memory cache TTL in seconds. Default 900 (15 minutes). | `900`   |
| `request_timeout` | `float` | HTTP request timeout in seconds. Default 15.              | `15.0`  |

### get_yield_opportunities

```
get_yield_opportunities(
    token: str,
    chains: list[str] | None = None,
    min_tvl: float = 100000,
    sort_by: str = "apy",
) -> DataEnvelope[list[YieldOpportunity]]
```

Find yield opportunities for a token across protocols and chains.

Parameters:

| Name      | Type        | Description                                            | Default                                                                                         |
| --------- | ----------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| `token`   | `str`       | Token symbol (e.g. "USDC", "WETH").                    | *required*                                                                                      |
| `chains`  | \`list[str] | None\`                                                 | Optional list of chains to filter (e.g. ["arbitrum", "base"]). None means all supported chains. |
| `min_tvl` | `float`     | Minimum TVL in USD. Default $100k.                     | `100000`                                                                                        |
| `sort_by` | `str`       | Sort field: "apy", "tvl", "risk_score". Default "apy". | `'apy'`                                                                                         |

Returns:

| Type                                   | Description                                                         |
| -------------------------------------- | ------------------------------------------------------------------- |
| `DataEnvelope[list[YieldOpportunity]]` | DataEnvelope\[list[YieldOpportunity]\] sorted by the chosen metric. |

Raises:

| Type                    | Description                       |
| ----------------------- | --------------------------------- |
| `DataSourceUnavailable` | If DeFi Llama API is unavailable. |

### health

```
health() -> dict[str, int]
```

Return health metrics.

### RateMonitor

## almanak.framework.data.RateMonitor

```
RateMonitor(
    chain: str = "ethereum",
    cache_ttl_seconds: float = DEFAULT_CACHE_TTL_SECONDS,
    protocols: list[str] | None = None,
    rpc_url: str | None = None,
)
```

Unified lending rate monitor for multiple DeFi protocols.

This class provides a single interface for fetching lending rates from Aave V3, Morpho Blue, and Compound V3. It handles caching, error recovery, and cross-protocol rate comparison.

Attributes:

| Name                | Type        | Description                           |
| ------------------- | ----------- | ------------------------------------- |
| `chain`             | `str`       | Blockchain network                    |
| `cache_ttl_seconds` | `str`       | How long to cache rates (default 12s) |
| `protocols`         | `list[str]` | List of protocols to monitor          |

Example

monitor = RateMonitor(chain="ethereum")

### Get specific rate

rate = await monitor.get_lending_rate("aave_v3", "USDC", RateSide.SUPPLY)

### Get best rate

best = await monitor.get_best_lending_rate("USDC", RateSide.SUPPLY)

### Get all rates for a protocol

rates = await monitor.get_protocol_rates("aave_v3")

Initialize the RateMonitor.

Parameters:

| Name                | Type        | Description                                   | Default                                                |
| ------------------- | ----------- | --------------------------------------------- | ------------------------------------------------------ |
| `chain`             | `str`       | Blockchain network (ethereum, arbitrum, etc.) | `'ethereum'`                                           |
| `cache_ttl_seconds` | `float`     | Cache TTL in seconds (default 12s = ~1 block) | `DEFAULT_CACHE_TTL_SECONDS`                            |
| `protocols`         | \`list[str] | None\`                                        | Protocols to monitor (default: all available on chain) |
| `rpc_url`           | \`str       | None\`                                        | RPC URL for on-chain queries (optional)                |

### chain

```
chain: str
```

Get the chain.

### protocols

```
protocols: list[str]
```

Get monitored protocols.

### set_mock_rate

```
set_mock_rate(
    protocol: str,
    token: str,
    side: str,
    apy_percent: Decimal,
) -> None
```

Set a mock rate for testing.

Parameters:

| Name          | Type      | Description                          | Default    |
| ------------- | --------- | ------------------------------------ | ---------- |
| `protocol`    | `str`     | Protocol identifier                  | *required* |
| `token`       | `str`     | Token symbol                         | *required* |
| `side`        | `str`     | supply or borrow                     | *required* |
| `apy_percent` | `Decimal` | APY as percentage (e.g., 5.0 for 5%) | *required* |

### clear_mock_rates

```
clear_mock_rates() -> None
```

Clear all mock rates.

### get_lending_rate

```
get_lending_rate(
    protocol: str, token: str, side: RateSide
) -> LendingRate
```

Get lending rate for a specific protocol/token/side.

Parameters:

| Name       | Type       | Description                                             | Default    |
| ---------- | ---------- | ------------------------------------------------------- | ---------- |
| `protocol` | `str`      | Protocol identifier (aave_v3, morpho_blue, compound_v3) | *required* |
| `token`    | `str`      | Token symbol (USDC, WETH, etc.)                         | *required* |
| `side`     | `RateSide` | Rate side (SUPPLY or BORROW)                            | *required* |

Returns:

| Type          | Description               |
| ------------- | ------------------------- |
| `LendingRate` | LendingRate with APY data |

Raises:

| Type                        | Description                        |
| --------------------------- | ---------------------------------- |
| `ProtocolNotSupportedError` | If protocol not available on chain |
| `TokenNotSupportedError`    | If token not supported             |
| `RateUnavailableError`      | If rate cannot be fetched          |

### get_best_lending_rate

```
get_best_lending_rate(
    token: str,
    side: RateSide,
    protocols: list[str] | None = None,
) -> BestRateResult
```

Get the best lending rate across protocols for a token.

For supply rates, returns the highest rate. For borrow rates, returns the lowest rate.

Parameters:

| Name        | Type        | Description                  | Default                                       |
| ----------- | ----------- | ---------------------------- | --------------------------------------------- |
| `token`     | `str`       | Token symbol                 | *required*                                    |
| `side`      | `RateSide`  | Rate side (SUPPLY or BORROW) | *required*                                    |
| `protocols` | \`list[str] | None\`                       | Protocols to compare (default: all available) |

Returns:

| Type             | Description                                 |
| ---------------- | ------------------------------------------- |
| `BestRateResult` | BestRateResult with best rate and all rates |

### get_protocol_rates

```
get_protocol_rates(
    protocol: str, tokens: list[str] | None = None
) -> ProtocolRates
```

Get all rates for a protocol.

Parameters:

| Name       | Type        | Description         | Default                                            |
| ---------- | ----------- | ------------------- | -------------------------------------------------- |
| `protocol` | `str`       | Protocol identifier | *required*                                         |
| `tokens`   | \`list[str] | None\`              | Tokens to fetch (default: common tokens for chain) |

Returns:

| Type            | Description                        |
| --------------- | ---------------------------------- |
| `ProtocolRates` | ProtocolRates with all token rates |

### clear_cache

```
clear_cache() -> None
```

Clear all cached rates.

### get_cache_stats

```
get_cache_stats() -> dict[str, Any]
```

Get cache statistics.

Returns:

| Type             | Description                 |
| ---------------- | --------------------------- |
| `dict[str, Any]` | Dictionary with cache stats |

### FundingRateProvider

## almanak.framework.data.FundingRateProvider

```
FundingRateProvider(
    venues: list[str] | None = None,
    cache_ttl_seconds: float = DEFAULT_CACHE_TTL_SECONDS,
    rpc_url: str | None = None,
    chain: str = "arbitrum",
)
```

Unified funding rate provider for multiple perpetual venues.

This class provides a single interface for fetching funding rates from GMX V2 and Hyperliquid. It handles caching, error recovery, and cross-venue rate comparison.

Attributes:

| Name                | Type        | Description                           |
| ------------------- | ----------- | ------------------------------------- |
| `venues`            | `list[str]` | List of venues to monitor             |
| `cache_ttl_seconds` | `list[str]` | How long to cache rates (default 60s) |

Example

provider = FundingRateProvider()

### Get specific rate

rate = await provider.get_funding_rate(Venue.GMX_V2, "ETH-USD")

### Get spread between venues

spread = await provider.get_funding_rate_spread("ETH-USD", Venue.GMX_V2, Venue.HYPERLIQUID)

### Get historical rates

history = await provider.get_historical_funding_rates(Venue.GMX_V2, "ETH-USD", hours=24)

Initialize the FundingRateProvider.

Parameters:

| Name                | Type        | Description                                  | Default                                                                                     |
| ------------------- | ----------- | -------------------------------------------- | ------------------------------------------------------------------------------------------- |
| `venues`            | \`list[str] | None\`                                       | Venues to monitor (default: all supported)                                                  |
| `cache_ttl_seconds` | `float`     | Cache TTL in seconds (default 60s)           | `DEFAULT_CACHE_TTL_SECONDS`                                                                 |
| `rpc_url`           | \`str       | None\`                                       | RPC URL for on-chain queries (GMX V2). If not provided, will use default rates as fallback. |
| `chain`             | `str`       | Chain for GMX V2 queries (default: arbitrum) | `'arbitrum'`                                                                                |

### venues

```
venues: list[str]
```

Get monitored venues.

### close

```
close() -> None
```

Close HTTP session and release resources.

### set_mock_rate

```
set_mock_rate(
    venue: str, market: str, rate_hourly: Decimal
) -> None
```

Set a mock funding rate for testing.

Parameters:

| Name          | Type      | Description         | Default    |
| ------------- | --------- | ------------------- | ---------- |
| `venue`       | `str`     | Venue identifier    | *required* |
| `market`      | `str`     | Market symbol       | *required* |
| `rate_hourly` | `Decimal` | Hourly funding rate | *required* |

### clear_mock_rates

```
clear_mock_rates() -> None
```

Clear all mock rates.

### get_funding_rate

```
get_funding_rate(venue: Venue, market: str) -> FundingRate
```

Get the current funding rate for a venue/market.

Parameters:

| Name     | Type    | Description                            | Default    |
| -------- | ------- | -------------------------------------- | ---------- |
| `venue`  | `Venue` | Venue identifier (gmx_v2, hyperliquid) | *required* |
| `market` | `str`   | Market symbol (e.g., ETH-USD, BTC-USD) | *required* |

Returns:

| Type          | Description                        |
| ------------- | ---------------------------------- |
| `FundingRate` | FundingRate with current rate data |

Raises:

| Type                          | Description               |
| ----------------------------- | ------------------------- |
| `VenueNotSupportedError`      | If venue not supported    |
| `MarketNotSupportedError`     | If market not supported   |
| `FundingRateUnavailableError` | If rate cannot be fetched |

### get_funding_rate_spread

```
get_funding_rate_spread(
    market: str, venue_a: Venue, venue_b: Venue
) -> FundingRateSpread
```

Get the funding rate spread between two venues.

The spread represents the difference in funding rates, which creates arbitrage opportunities. A positive spread means venue_a has higher funding than venue_b.

Parameters:

| Name      | Type    | Description                   | Default    |
| --------- | ------- | ----------------------------- | ---------- |
| `market`  | `str`   | Market symbol (e.g., ETH-USD) | *required* |
| `venue_a` | `Venue` | First venue                   | *required* |
| `venue_b` | `Venue` | Second venue                  | *required* |

Returns:

| Type                | Description                            |
| ------------------- | -------------------------------------- |
| `FundingRateSpread` | FundingRateSpread with comparison data |

Raises:

| Type                          | Description                      |
| ----------------------------- | -------------------------------- |
| `FundingRateUnavailableError` | If either rate cannot be fetched |

### get_historical_funding_rates

```
get_historical_funding_rates(
    venue: Venue, market: str, hours: int = 24
) -> HistoricalFundingData
```

Get historical funding rates for a venue/market.

Returns historical funding rate data for analysis and trend detection.

Parameters:

| Name     | Type    | Description                                               | Default    |
| -------- | ------- | --------------------------------------------------------- | ---------- |
| `venue`  | `Venue` | Venue identifier                                          | *required* |
| `market` | `str`   | Market symbol                                             | *required* |
| `hours`  | `int`   | Number of hours of history to fetch (default 24, max 168) | `24`       |

Returns:

| Type                    | Description                             |
| ----------------------- | --------------------------------------- |
| `HistoricalFundingData` | HistoricalFundingData with rate history |

Raises:

| Type                          | Description               |
| ----------------------------- | ------------------------- |
| `VenueNotSupportedError`      | If venue not supported    |
| `MarketNotSupportedError`     | If market not supported   |
| `FundingRateUnavailableError` | If data cannot be fetched |

### get_all_funding_rates

```
get_all_funding_rates(
    venue: Venue,
) -> dict[str, FundingRate]
```

Get funding rates for all supported markets on a venue.

Parameters:

| Name    | Type    | Description      | Default    |
| ------- | ------- | ---------------- | ---------- |
| `venue` | `Venue` | Venue identifier | *required* |

Returns:

| Type                     | Description                                     |
| ------------------------ | ----------------------------------------------- |
| `dict[str, FundingRate]` | Dictionary mapping market symbol to FundingRate |

### clear_cache

```
clear_cache() -> None
```

Clear all cached rates.

### get_cache_stats

```
get_cache_stats() -> dict[str, Any]
```

Get cache statistics.

Returns:

| Type             | Description                 |
| ---------------- | --------------------------- |
| `dict[str, Any]` | Dictionary with cache stats |

## Impermanent Loss

### ILCalculator

## almanak.framework.data.ILCalculator

```
ILCalculator(
    positions: dict[str, LPPosition] | None = None,
    mock_prices: dict[str, Decimal] | None = None,
)
```

Calculator for impermanent loss across various AMM pool types.

This class provides methods to calculate impermanent loss for liquidity positions, including:

- Standard constant product AMM pools (Uniswap V2, SushiSwap)
- Concentrated liquidity pools (Uniswap V3)
- Weighted pools (Balancer)
- Stable pools (Curve)

The calculator also supports projecting IL for simulated price changes and tracking IL exposure for active positions.

Example

calc = ILCalculator()

### Calculate IL for a 50/50 pool

result = calc.calculate_il( entry_price_a=Decimal("2000"), entry_price_b=Decimal("1"), current_price_a=Decimal("2500"), current_price_b=Decimal("1"), ) print(f"IL: {result.il_percent:.4f}%")

### Project IL for various price changes

for pct in \[10, 25, 50, 100\]: proj = calc.project_il(price_change_pct=Decimal(pct)) print(f"Price +{pct}%: IL = {proj.il_percent:.4f}%")

Initialize the IL calculator.

Parameters:

| Name          | Type                    | Description | Default                                           |
| ------------- | ----------------------- | ----------- | ------------------------------------------------- |
| `positions`   | \`dict[str, LPPosition] | None\`      | Optional dictionary of tracked LP positions       |
| `mock_prices` | \`dict[str, Decimal]    | None\`      | Optional mock prices for testing (token -> price) |

### calculate_il

```
calculate_il(
    entry_price_a: Decimal,
    entry_price_b: Decimal,
    current_price_a: Decimal,
    current_price_b: Decimal,
    weight_a: Decimal = Decimal("0.5"),
    weight_b: Decimal = Decimal("0.5"),
    pool_type: PoolType = PoolType.CONSTANT_PRODUCT,
    entry_value: Decimal | None = None,
) -> ILResult
```

Calculate impermanent loss given entry and current prices.

For constant product AMMs (Uniswap V2 style), IL is calculated as: IL = 2 * sqrt(price_ratio) / (1 + price_ratio) - 1

For weighted pools (Balancer style), IL is calculated using: IL = (price_ratio ^ weight_a) / ((weight_a * price_ratio) + weight_b) - 1

Parameters:

| Name              | Type       | Description                                           | Default                                                  |
| ----------------- | ---------- | ----------------------------------------------------- | -------------------------------------------------------- |
| `entry_price_a`   | `Decimal`  | Entry price of token A (in quote currency, e.g., USD) | *required*                                               |
| `entry_price_b`   | `Decimal`  | Entry price of token B (in quote currency)            | *required*                                               |
| `current_price_a` | `Decimal`  | Current price of token A                              | *required*                                               |
| `current_price_b` | `Decimal`  | Current price of token B                              | *required*                                               |
| `weight_a`        | `Decimal`  | Weight of token A (default 0.5 for equal weight)      | `Decimal('0.5')`                                         |
| `weight_b`        | `Decimal`  | Weight of token B (default 0.5 for equal weight)      | `Decimal('0.5')`                                         |
| `pool_type`       | `PoolType` | Type of AMM pool                                      | `CONSTANT_PRODUCT`                                       |
| `entry_value`     | \`Decimal  | None\`                                                | Optional initial position value (for absolute loss calc) |

Returns:

| Type       | Description              |
| ---------- | ------------------------ |
| `ILResult` | ILResult with IL metrics |

Raises:

| Type                 | Description                      |
| -------------------- | -------------------------------- |
| `InvalidPriceError`  | If any price is zero or negative |
| `InvalidWeightError` | If weights don't sum to 1.0      |

### calculate_il_concentrated

```
calculate_il_concentrated(
    entry_price_a: Decimal,
    entry_price_b: Decimal,
    current_price_a: Decimal,
    current_price_b: Decimal,
    tick_lower: int,
    tick_upper: int,
    entry_value: Decimal | None = None,
) -> ILResult
```

Calculate IL for concentrated liquidity positions (Uniswap V3).

Concentrated liquidity amplifies both gains and IL compared to full-range positions. The IL is higher when price moves outside the range.

Parameters:

| Name              | Type      | Description                      | Default                         |
| ----------------- | --------- | -------------------------------- | ------------------------------- |
| `entry_price_a`   | `Decimal` | Entry price of token A           | *required*                      |
| `entry_price_b`   | `Decimal` | Entry price of token B           | *required*                      |
| `current_price_a` | `Decimal` | Current price of token A         | *required*                      |
| `current_price_b` | `Decimal` | Current price of token B         | *required*                      |
| `tick_lower`      | `int`     | Lower tick bound of the position | *required*                      |
| `tick_upper`      | `int`     | Upper tick bound of the position | *required*                      |
| `entry_value`     | \`Decimal | None\`                           | Optional initial position value |

Returns:

| Type       | Description                                        |
| ---------- | -------------------------------------------------- |
| `ILResult` | ILResult with IL metrics for concentrated position |

Raises:

| Type                | Description                      |
| ------------------- | -------------------------------- |
| `InvalidPriceError` | If any price is zero or negative |

### project_il

```
project_il(
    price_change_pct: Decimal,
    weight_a: Decimal = Decimal("0.5"),
    weight_b: Decimal = Decimal("0.5"),
    pool_type: PoolType = PoolType.CONSTANT_PRODUCT,
) -> ProjectedILResult
```

Project impermanent loss for a given price change.

This method simulates what IL would be if token A's price changed by the specified percentage while token B remains constant (typical for ETH/stablecoin pairs).

Parameters:

| Name               | Type       | Description                                               | Default            |
| ------------------ | ---------- | --------------------------------------------------------- | ------------------ |
| `price_change_pct` | `Decimal`  | Price change percentage (e.g., 50 for +50%, -30 for -30%) | *required*         |
| `weight_a`         | `Decimal`  | Weight of token A (default 0.5)                           | `Decimal('0.5')`   |
| `weight_b`         | `Decimal`  | Weight of token B (default 0.5)                           | `Decimal('0.5')`   |
| `pool_type`        | `PoolType` | Type of AMM pool                                          | `CONSTANT_PRODUCT` |

Returns:

| Type                | Description                                 |
| ------------------- | ------------------------------------------- |
| `ProjectedILResult` | ProjectedILResult with projected IL metrics |

Raises:

| Type                 | Description                                    |
| -------------------- | ---------------------------------------------- |
| `InvalidWeightError` | If weights don't sum to 1.0                    |
| `InvalidPriceError`  | If price change would result in negative price |

### add_position

```
add_position(position: LPPosition) -> None
```

Add an LP position for tracking.

Parameters:

| Name       | Type         | Description              | Default    |
| ---------- | ------------ | ------------------------ | ---------- |
| `position` | `LPPosition` | The LP position to track | *required* |

### remove_position

```
remove_position(position_id: str) -> None
```

Remove an LP position from tracking.

Parameters:

| Name          | Type  | Description                  | Default    |
| ------------- | ----- | ---------------------------- | ---------- |
| `position_id` | `str` | ID of the position to remove | *required* |

Raises:

| Type                    | Description               |
| ----------------------- | ------------------------- |
| `PositionNotFoundError` | If position doesn't exist |

### get_position

```
get_position(position_id: str) -> LPPosition
```

Get a tracked LP position.

Parameters:

| Name          | Type  | Description        | Default    |
| ------------- | ----- | ------------------ | ---------- |
| `position_id` | `str` | ID of the position | *required* |

Returns:

| Type         | Description     |
| ------------ | --------------- |
| `LPPosition` | The LP position |

Raises:

| Type                    | Description               |
| ----------------------- | ------------------------- |
| `PositionNotFoundError` | If position doesn't exist |

### get_all_positions

```
get_all_positions() -> list[LPPosition]
```

Get all tracked LP positions.

Returns:

| Type               | Description                   |
| ------------------ | ----------------------------- |
| `list[LPPosition]` | List of all tracked positions |

### calculate_il_exposure

```
calculate_il_exposure(
    position_id: str,
    current_price_a: Decimal | None = None,
    current_price_b: Decimal | None = None,
    fees_earned: Decimal = Decimal("0"),
) -> ILExposure
```

Calculate IL exposure for a tracked position.

Parameters:

| Name              | Type      | Description                            | Default                                              |
| ----------------- | --------- | -------------------------------------- | ---------------------------------------------------- |
| `position_id`     | `str`     | ID of the tracked position             | *required*                                           |
| `current_price_a` | \`Decimal | None\`                                 | Current price of token A (uses mock if not provided) |
| `current_price_b` | \`Decimal | None\`                                 | Current price of token B (uses mock if not provided) |
| `fees_earned`     | `Decimal` | Fees earned by the position (optional) | `Decimal('0')`                                       |

Returns:

| Type         | Description                           |
| ------------ | ------------------------------------- |
| `ILExposure` | ILExposure with full exposure details |

Raises:

| Type                         | Description                    |
| ---------------------------- | ------------------------------ |
| `PositionNotFoundError`      | If position doesn't exist      |
| `ILExposureUnavailableError` | If prices cannot be determined |

### set_mock_prices

```
set_mock_prices(prices: dict[str, Decimal]) -> None
```

Set mock prices for testing.

Parameters:

| Name     | Type                 | Description                                | Default    |
| -------- | -------------------- | ------------------------------------------ | ---------- |
| `prices` | `dict[str, Decimal]` | Dictionary mapping token symbols to prices | *required* |

### clear_mock_prices

```
clear_mock_prices() -> None
```

Clear mock prices.

## Data Routing

### DataRouter

## almanak.framework.data.DataRouter

```
DataRouter(config: DataRoutingConfig = DataRoutingConfig())
```

Routes data requests to providers with fallback and circuit-breaking.

For EXECUTION_GRADE data types, the router fails closed after the primary provider fails -- no fallback to degraded sources is attempted.

For INFORMATIONAL data types, the router tries the fallback chain in order, each with its own timeout, and returns the best available result with degraded confidence.

Attributes:

| Name     | Type                | Description                                            |
| -------- | ------------------- | ------------------------------------------------------ |
| `config` | `DataRoutingConfig` | Routing configuration specifying provider assignments. |

### register_provider

```
register_provider(provider: DataProvider) -> None
```

Register a data provider for routing.

Parameters:

| Name       | Type           | Description                                | Default    |
| ---------- | -------------- | ------------------------------------------ | ---------- |
| `provider` | `DataProvider` | A DataProvider implementation to register. | *required* |

### route

```
route(
    data_type: str,
    *,
    instrument: str = "",
    strategy_config: dict | None = None,
    **fetch_kwargs: object,
) -> DataEnvelope
```

Select a provider and fetch data with fallback logic.

Provider selection order

1. Strategy-level override (if strategy_config provided)
1. Global routing config
1. Built-in defaults

Parameters:

| Name              | Type     | Description                                           | Default                                            |
| ----------------- | -------- | ----------------------------------------------------- | -------------------------------------------------- |
| `data_type`       | `str`    | Data type key (e.g. "ohlcv", "pool_price").           | *required*                                         |
| `instrument`      | `str`    | Instrument identifier for logging/error context.      | `''`                                               |
| `strategy_config` | \`dict   | None\`                                                | Optional strategy config dict with data_overrides. |
| `**fetch_kwargs`  | `object` | Additional kwargs passed through to provider.fetch(). | `{}`                                               |

Returns:

| Type           | Description                              |
| -------------- | ---------------------------------------- |
| `DataEnvelope` | DataEnvelope from the selected provider. |

Raises:

| Type                   | Description              |
| ---------------------- | ------------------------ |
| `DataUnavailableError` | When all providers fail. |

### get_metrics

```
get_metrics(
    provider_name: str | None = None,
) -> dict[str, object]
```

Return metrics for a specific provider or all providers.

Parameters:

| Name            | Type  | Description | Default                                             |
| --------------- | ----- | ----------- | --------------------------------------------------- |
| `provider_name` | \`str | None\`      | If provided, return metrics for this provider only. |

Returns:

| Type                | Description          |
| ------------------- | -------------------- |
| `dict[str, object]` | Dict of metric data. |

### health

```
health() -> dict[str, object]
```

Return health status for all registered providers.

Returns:

| Type                | Description                                                |
| ------------------- | ---------------------------------------------------------- |
| `dict[str, object]` | Dict mapping provider name -> circuit breaker health dict. |

### CircuitBreaker

## almanak.framework.data.CircuitBreaker

```
CircuitBreaker(
    name: str,
    failure_threshold: int = 5,
    cooldown_seconds: float = 60.0,
)
```

Thread-safe circuit breaker for a single data provider.

Opens after `failure_threshold` consecutive failures, then enters a cooldown period. After cooldown, allows one test request (HALF_OPEN). If the test succeeds the circuit closes; if it fails the circuit reopens.

Attributes:

| Name                | Type | Description                            |
| ------------------- | ---- | -------------------------------------- |
| `name`              |      | Provider name this breaker guards.     |
| `failure_threshold` |      | Consecutive failures before opening.   |
| `cooldown_seconds`  |      | Seconds to wait before half-open test. |

### state

```
state: CircuitState
```

Current circuit state, accounting for cooldown expiry.

### failure_count

```
failure_count: int
```

Number of consecutive failures.

### last_failure_time

```
last_failure_time: float | None
```

Monotonic timestamp of the last recorded failure, or None.

### allow_request

```
allow_request() -> bool
```

Check whether a request should be allowed through.

Returns:

| Type   | Description                                                  |
| ------ | ------------------------------------------------------------ |
| `bool` | True if the circuit is CLOSED or transitioning to HALF_OPEN. |
| `bool` | False if the circuit is OPEN (still within cooldown).        |

### record_success

```
record_success() -> None
```

Record a successful request, resetting the breaker to CLOSED.

### record_failure

```
record_failure() -> None
```

Record a failed request, potentially opening the circuit.

### reset

```
reset() -> None
```

Manually reset the breaker to CLOSED state.

### health

```
health() -> dict[str, object]
```

Return health metrics for monitoring.

Returns:

| Type                | Description                                                    |
| ------------------- | -------------------------------------------------------------- |
| `dict[str, object]` | Dict with state, failure_count, last_failure_time, and config. |

## Exceptions

## almanak.framework.data.DataUnavailableError

```
DataUnavailableError(
    data_type: str, instrument: str, reason: str
)
```

Bases: `DataSourceError`

Raised when required data cannot be obtained from any provider.

Used by the DataRouter when all providers (primary + fallbacks) have failed or been circuit-broken for a given request.

Attributes:

| Name         | Type | Description                                                   |
| ------------ | ---- | ------------------------------------------------------------- |
| `data_type`  |      | What kind of data was requested (e.g. 'pool_price', 'ohlcv'). |
| `instrument` |      | Instrument identifier (symbol or address).                    |
| `reason`     |      | Human-readable explanation.                                   |

## almanak.framework.data.MarketSnapshotError

Bases: `Exception`

Base exception for MarketSnapshot errors.

# Deployment

Canary deployment support for safe strategy rollouts.

## CanaryDeployment

## almanak.framework.deployment.CanaryDeployment

```
CanaryDeployment(
    strategy_id: str,
    stable_version_id: str,
    canary_version_id: str,
    config: CanaryConfig | None = None,
    on_promote: CanaryCallback | None = None,
    on_rollback: CanaryCallback | None = None,
    on_state_change: CanaryCallback | None = None,
    metrics_provider: MetricsProvider | None = None,
    chain: str = "unknown",
)
```

Manages canary deployments for safe strategy version testing.

This class handles the full lifecycle of a canary deployment:

1. Deploy canary with limited capital allocation
1. Run both versions in parallel
1. Monitor and compare performance
1. Auto-promote or auto-rollback based on criteria

Attributes:

| Name                | Type | Description                       |
| ------------------- | ---- | --------------------------------- |
| `strategy_id`       |      | ID of the strategy being deployed |
| `stable_version_id` |      | ID of the current stable version  |
| `canary_version_id` |      | ID of the new canary version      |
| `state`             |      | Current deployment state          |
| `config`            |      | Deployment configuration          |

Initialize the canary deployment manager.

Parameters:

| Name                | Type              | Description                           | Default                                                  |
| ------------------- | ----------------- | ------------------------------------- | -------------------------------------------------------- |
| `strategy_id`       | `str`             | ID of the strategy being deployed     | *required*                                               |
| `stable_version_id` | `str`             | ID of the current stable version      | *required*                                               |
| `canary_version_id` | `str`             | ID of the new canary version          | *required*                                               |
| `config`            | \`CanaryConfig    | None\`                                | Deployment configuration (uses defaults if not provided) |
| `on_promote`        | \`CanaryCallback  | None\`                                | Callback when canary is promoted                         |
| `on_rollback`       | \`CanaryCallback  | None\`                                | Callback when canary is rolled back                      |
| `on_state_change`   | \`CanaryCallback  | None\`                                | Callback on any state change                             |
| `metrics_provider`  | \`MetricsProvider | None\`                                | Function to fetch metrics for a version                  |
| `chain`             | `str`             | Blockchain network for event emission | `'unknown'`                                              |

### deploy_canary

```
deploy_canary(capital_usd: Decimal) -> DeployCanaryResult
```

Start the canary deployment.

Allocates capital between canary and stable versions and begins the observation period.

Parameters:

| Name          | Type      | Description                                    | Default    |
| ------------- | --------- | ---------------------------------------------- | ---------- |
| `capital_usd` | `Decimal` | Total capital to allocate across both versions | *required* |

Returns:

| Type                 | Description                                |
| -------------------- | ------------------------------------------ |
| `DeployCanaryResult` | DeployCanaryResult with deployment details |

### compare_performance

```
compare_performance() -> CanaryComparison
```

Compare canary performance against stable version.

Evaluates performance metrics against promotion criteria and returns a decision recommendation.

Returns:

| Type               | Description                                |
| ------------------ | ------------------------------------------ |
| `CanaryComparison` | CanaryComparison with metrics and decision |

### promote_canary

```
promote_canary() -> CanaryResult
```

Promote the canary version to stable.

Makes the canary version the new stable version and reallocates all capital to it.

Returns:

| Type           | Description                                |
| -------------- | ------------------------------------------ |
| `CanaryResult` | CanaryResult indicating success or failure |

### rollback_canary

```
rollback_canary() -> CanaryResult
```

Rollback the canary deployment.

Stops the canary version and reallocates all capital back to the stable version.

Returns:

| Type           | Description                                |
| -------------- | ------------------------------------------ |
| `CanaryResult` | CanaryResult indicating success or failure |

### cancel_deployment

```
cancel_deployment() -> CanaryResult
```

Cancel the canary deployment without promoting or rolling back.

Returns:

| Type           | Description                                |
| -------------- | ------------------------------------------ |
| `CanaryResult` | CanaryResult indicating success or failure |

### update_canary_metrics

```
update_canary_metrics(
    pnl_usd: Decimal | None = None,
    trades: int | None = None,
    errors: int | None = None,
    drawdown: Decimal | None = None,
) -> None
```

Manually update canary metrics.

Used when metrics_provider is not available.

Parameters:

| Name       | Type      | Description | Default          |
| ---------- | --------- | ----------- | ---------------- |
| `pnl_usd`  | \`Decimal | None\`      | Net PnL in USD   |
| `trades`   | \`int     | None\`      | Number of trades |
| `errors`   | \`int     | None\`      | Number of errors |
| `drawdown` | \`Decimal | None\`      | Max drawdown     |

### update_stable_metrics

```
update_stable_metrics(
    pnl_usd: Decimal | None = None,
    trades: int | None = None,
    errors: int | None = None,
    drawdown: Decimal | None = None,
) -> None
```

Manually update stable metrics.

Used when metrics_provider is not available.

Parameters:

| Name       | Type      | Description | Default          |
| ---------- | --------- | ----------- | ---------------- |
| `pnl_usd`  | \`Decimal | None\`      | Net PnL in USD   |
| `trades`   | \`int     | None\`      | Number of trades |
| `errors`   | \`int     | None\`      | Number of errors |
| `drawdown` | \`Decimal | None\`      | Max drawdown     |

### get_status

```
get_status() -> dict[str, Any]
```

Get the current status summary.

Returns:

| Type             | Description                       |
| ---------------- | --------------------------------- |
| `dict[str, Any]` | Dictionary with deployment status |

### to_dict

```
to_dict() -> dict[str, Any]
```

Export the deployment state for persistence.

Returns:

| Type             | Description                                 |
| ---------------- | ------------------------------------------- |
| `dict[str, Any]` | Dictionary containing full deployment state |

### from_dict

```
from_dict(
    data: dict[str, Any],
    on_promote: CanaryCallback | None = None,
    on_rollback: CanaryCallback | None = None,
    on_state_change: CanaryCallback | None = None,
    metrics_provider: MetricsProvider | None = None,
) -> CanaryDeployment
```

Restore a deployment from persisted state.

Parameters:

| Name               | Type              | Description                     | Default                            |
| ------------------ | ----------------- | ------------------------------- | ---------------------------------- |
| `data`             | `dict[str, Any]`  | Dictionary with deployment data | *required*                         |
| `on_promote`       | \`CanaryCallback  | None\`                          | Optional promotion callback        |
| `on_rollback`      | \`CanaryCallback  | None\`                          | Optional rollback callback         |
| `on_state_change`  | \`CanaryCallback  | None\`                          | Optional state change callback     |
| `metrics_provider` | \`MetricsProvider | None\`                          | Optional metrics provider function |

Returns:

| Type               | Description                                   |
| ------------------ | --------------------------------------------- |
| `CanaryDeployment` | CanaryDeployment instance with restored state |

## CanaryConfig

## almanak.framework.deployment.CanaryConfig

```
CanaryConfig(
    canary_percent: int = 10,
    observation_period_minutes: int = 60,
    auto_promote: bool = True,
    auto_rollback: bool = True,
    promotion_criteria: PromotionCriteria = PromotionCriteria(),
    check_interval_seconds: int = 60,
    emit_events: bool = True,
)
```

Configuration for a canary deployment.

Attributes:

| Name                         | Type                | Description                                            |
| ---------------------------- | ------------------- | ------------------------------------------------------ |
| `canary_percent`             | `int`               | Percentage of total capital allocated to canary (1-50) |
| `observation_period_minutes` | `int`               | How long to observe before deciding (min 5)            |
| `auto_promote`               | `bool`              | Automatically promote if criteria met                  |
| `auto_rollback`              | `bool`              | Automatically rollback if criteria not met             |
| `promotion_criteria`         | `PromotionCriteria` | Criteria for promotion decisions                       |
| `check_interval_seconds`     | `int`               | How often to check metrics                             |
| `emit_events`                | `bool`              | Whether to emit timeline events                        |

### __post_init__

```
__post_init__() -> None
```

Validate configuration after initialization.

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

### from_dict

```
from_dict(data: dict[str, Any]) -> CanaryConfig
```

Create from dictionary.

## CanaryState

## almanak.framework.deployment.CanaryState

```
CanaryState(
    deployment_id: str,
    strategy_id: str,
    stable_version_id: str,
    canary_version_id: str,
    status: CanaryStatus = CanaryStatus.PENDING,
    config: CanaryConfig = CanaryConfig(),
    started_at: datetime | None = None,
    ended_at: datetime | None = None,
    canary_metrics: CanaryMetrics | None = None,
    stable_metrics: CanaryMetrics | None = None,
    total_capital_usd: Decimal = Decimal("0"),
    decision_history: list[dict[str, Any]] = list(),
)
```

Current state of a canary deployment.

Tracks the full state of an ongoing or completed canary deployment.

Attributes:

| Name                | Type                   | Description                           |
| ------------------- | ---------------------- | ------------------------------------- |
| `deployment_id`     | `str`                  | Unique identifier for this deployment |
| `strategy_id`       | `str`                  | Strategy being deployed               |
| `stable_version_id` | `str`                  | ID of the stable version              |
| `canary_version_id` | `str`                  | ID of the canary version              |
| `status`            | `CanaryStatus`         | Current deployment status             |
| `config`            | `CanaryConfig`         | Deployment configuration              |
| `started_at`        | \`datetime             | None\`                                |
| `ended_at`          | \`datetime             | None\`                                |
| `canary_metrics`    | \`CanaryMetrics        | None\`                                |
| `stable_metrics`    | \`CanaryMetrics        | None\`                                |
| `total_capital_usd` | `Decimal`              | Total capital across both versions    |
| `decision_history`  | `list[dict[str, Any]]` | History of decisions made             |

### canary_capital_usd

```
canary_capital_usd: Decimal
```

Calculate capital allocated to canary.

### stable_capital_usd

```
stable_capital_usd: Decimal
```

Calculate capital allocated to stable.

### observation_deadline

```
observation_deadline: datetime | None
```

Calculate when observation period ends.

### observation_remaining_seconds

```
observation_remaining_seconds: int
```

Calculate seconds remaining in observation period.

### observation_complete

```
observation_complete: bool
```

Check if observation period is complete.

### __post_init__

```
__post_init__() -> None
```

Generate deployment ID if not provided.

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

### from_dict

```
from_dict(data: dict[str, Any]) -> CanaryState
```

Create from dictionary.

## CanaryStatus

## almanak.framework.deployment.CanaryStatus

Bases: `StrEnum`

Status of a canary deployment.

## CanaryMetrics

## almanak.framework.deployment.CanaryMetrics

```
CanaryMetrics(
    version_id: str,
    capital_allocated_usd: Decimal,
    metrics: PerformanceMetrics,
    error_count: int = 0,
    trade_count: int = 0,
    is_canary: bool = False,
    measurement_start: datetime | None = None,
)
```

Performance metrics tracked during canary deployment.

Extends PerformanceMetrics with canary-specific tracking.

Attributes:

| Name                    | Type                 | Description                         |
| ----------------------- | -------------------- | ----------------------------------- |
| `version_id`            | `str`                | The version these metrics belong to |
| `capital_allocated_usd` | `Decimal`            | Capital allocated to this version   |
| `metrics`               | `PerformanceMetrics` | The underlying performance metrics  |
| `error_count`           | `int`                | Number of errors encountered        |
| `trade_count`           | `int`                | Number of trades executed           |
| `is_canary`             | `bool`               | Whether this is the canary version  |
| `measurement_start`     | \`datetime           | None\`                              |

### error_rate

```
error_rate: Decimal
```

Calculate the error rate.

### duration_seconds

```
duration_seconds: int
```

Get the duration of metrics collection.

### __post_init__

```
__post_init__() -> None
```

Set default values after initialization.

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

### from_dict

```
from_dict(data: dict[str, Any]) -> CanaryMetrics
```

Create from dictionary.

## CanaryResult

## almanak.framework.deployment.CanaryResult

```
CanaryResult(
    success: bool,
    decision: CanaryDecision = CanaryDecision.CONTINUE,
    comparison: CanaryComparison | None = None,
    error: str | None = None,
    message: str = "",
)
```

Result of a canary decision or action.

Attributes:

| Name         | Type               | Description                   |
| ------------ | ------------------ | ----------------------------- |
| `success`    | `bool`             | Whether the action succeeded  |
| `decision`   | `CanaryDecision`   | The decision made             |
| `comparison` | \`CanaryComparison | None\`                        |
| `error`      | \`str              | None\`                        |
| `message`    | `str`              | Human-readable status message |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

## CanaryDecision

## almanak.framework.deployment.CanaryDecision

Bases: `StrEnum`

Decision to make about a canary deployment.

# Enums

Core enumeration types used throughout the SDK.

## Chain

## almanak.core.enums.Chain

Bases: `Enum`

## Network

## almanak.core.enums.Network

Bases: `Enum`

## Protocol

## almanak.core.enums.Protocol

Bases: `Enum`

## ActionType

## almanak.core.enums.ActionType

Bases: `Enum`

## ExecutionStatus

## almanak.core.enums.ExecutionStatus

Bases: `Enum`

## SwapSide

## almanak.core.enums.SwapSide

Bases: `Enum`

## TransactionType

## almanak.core.enums.TransactionType

Bases: `Enum`

# Execution

The execution pipeline compiles intents, signs transactions, simulates, and submits them on-chain.

## GatewayExecutionOrchestrator

The primary orchestrator used when running with the gateway sidecar.

## almanak.framework.execution.GatewayExecutionOrchestrator

```
GatewayExecutionOrchestrator(
    client: GatewayClient,
    chain: str = "arbitrum",
    wallet_address: str | None = None,
    timeout: float | None = None,
    execute_timeout: float | None = None,
    max_gas_price_gwei: int = 0,
)
```

ExecutionOrchestrator that executes through the gateway.

This implementation routes all execution requests to the gateway sidecar, which has access to private keys and can sign/submit transactions.

The interface is intentionally simpler than the full ExecutionOrchestrator since the complex signing and submission logic lives in the gateway.

Example

from almanak.framework.gateway_client import GatewayClient from almanak.framework.execution.gateway_orchestrator import GatewayExecutionOrchestrator

with GatewayClient() as client: orchestrator = GatewayExecutionOrchestrator( client=client, chain="arbitrum", wallet_address="0x1234...", ) result = await orchestrator.execute(action_bundle) print(f"Execution success: {result.success}")

Initialize gateway-backed execution orchestrator.

Parameters:

| Name                 | Type            | Description                                                                                                           | Default                                                                                                                                                                                                                                                                                                                                                                |
| -------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client`             | `GatewayClient` | Connected GatewayClient instance                                                                                      | *required*                                                                                                                                                                                                                                                                                                                                                             |
| `chain`              | `str`           | Chain name for execution                                                                                              | `'arbitrum'`                                                                                                                                                                                                                                                                                                                                                           |
| `wallet_address`     | \`str           | None\`                                                                                                                | Wallet address for signing                                                                                                                                                                                                                                                                                                                                             |
| `timeout`            | \`float         | None\`                                                                                                                | gRPC timeout for CompileIntent/GetTransactionStatus calls in seconds. If None, uses chain-specific TX confirmation timeout (300s for Ethereum L1, 120s for L2s).                                                                                                                                                                                                       |
| `execute_timeout`    | \`float         | None\`                                                                                                                | gRPC timeout for the Execute call in seconds. Must be larger than timeout to account for gas estimation overhead before TX submission. If None, uses chain-specific execute timeout (600s for Ethereum, 300s for L2s). Complex intents like LP_CLOSE can take 100s+ to estimate gas on Anvil forks; this timeout must cover gas estimation + TX confirmation combined. |
| `max_gas_price_gwei` | `int`           | Gas price cap in gwei (0 = use gateway default). Passed to the gateway so the ExecutionOrchestrator enforces the cap. | `0`                                                                                                                                                                                                                                                                                                                                                                    |

### chain

```
chain: str
```

Get the chain name.

### wallet_address

```
wallet_address: str | None
```

Get the wallet address.

### compile_intent

```
compile_intent(
    intent: Any,
    wallet_address: str | None = None,
    price_map: dict[str, str] | None = None,
) -> dict[str, Any]
```

Compile an intent into an action bundle through gateway.

Parameters:

| Name             | Type             | Description              | Default                                                                                                                                                                          |
| ---------------- | ---------------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `intent`         | `Any`            | Intent object to compile | *required*                                                                                                                                                                       |
| `wallet_address` | \`str            | None\`                   | Override wallet address                                                                                                                                                          |
| `price_map`      | \`dict[str, str] | None\`                   | Token symbol -> USD price string for real pricing. If provided, the gateway compiler uses these instead of placeholder prices. Values are strings to preserve Decimal precision. |

Returns:

| Type             | Description                 |
| ---------------- | --------------------------- |
| `dict[str, Any]` | Action bundle as dictionary |

Raises:

| Type               | Description          |
| ------------------ | -------------------- |
| `CompilationError` | If compilation fails |

### execute

```
execute(
    action_bundle: Any,
    context: Any | None = None,
    strategy_id: str = "",
    intent_id: str = "",
    dry_run: bool = False,
    simulation_enabled: bool = True,
    wallet_address: str | None = None,
) -> GatewayExecutionResult
```

Execute an action bundle through gateway.

Parameters:

| Name                 | Type   | Description                               | Default                                                                                                                                                                                  |
| -------------------- | ------ | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `action_bundle`      | `Any`  | Action bundle to execute (object or dict) | *required*                                                                                                                                                                               |
| `context`            | \`Any  | None\`                                    | Optional ExecutionContext for interface compatibility with ExecutionOrchestrator. If provided, extracts strategy_id, intent_id, dry_run, simulation_enabled, and wallet_address from it. |
| `strategy_id`        | `str`  | Strategy identifier for tracking          | `''`                                                                                                                                                                                     |
| `intent_id`          | `str`  | Intent identifier for tracking            | `''`                                                                                                                                                                                     |
| `dry_run`            | `bool` | If True, simulate only without submitting | `False`                                                                                                                                                                                  |
| `simulation_enabled` | `bool` | If True, run simulation before execution  | `True`                                                                                                                                                                                   |
| `wallet_address`     | \`str  | None\`                                    | Override wallet address                                                                                                                                                                  |

Returns:

| Type                     | Description                                        |
| ------------------------ | -------------------------------------------------- |
| `GatewayExecutionResult` | GatewayExecutionResult with tx hashes and receipts |

Raises:

| Type             | Description        |
| ---------------- | ------------------ |
| `ExecutionError` | If execution fails |

### get_transaction_status

```
get_transaction_status(
    tx_hash: str, chain: str | None = None
) -> dict[str, Any]
```

Get transaction status from gateway.

Parameters:

| Name      | Type  | Description               | Default                                         |
| --------- | ----- | ------------------------- | ----------------------------------------------- |
| `tx_hash` | `str` | Transaction hash to check | *required*                                      |
| `chain`   | \`str | None\`                    | Chain to query (defaults to orchestrator chain) |

Returns:

| Type             | Description                                                |
| ---------------- | ---------------------------------------------------------- |
| `dict[str, Any]` | Status dictionary with status, confirmations, block_number |

## ExecutionOrchestrator

## almanak.framework.execution.ExecutionOrchestrator

```
ExecutionOrchestrator(
    signer: Signer,
    submitter: Submitter,
    simulator: Simulator,
    chain: str = "arbitrum",
    rpc_url: str | None = None,
    risk_guard: RiskGuard | None = None,
    event_callback: EventCallback | None = None,
    gas_buffer_multiplier: float | None = None,
    tx_timeout_seconds: float | None = None,
    session_store: ExecutionSessionStore | None = None,
    tx_risk_config: TransactionRiskConfig | None = None,
)
```

Orchestrates the full transaction execution flow.

The ExecutionOrchestrator coordinates:

- RiskGuard validation
- Transaction simulation (optional)
- Nonce assignment
- Transaction signing
- Transaction submission
- Receipt polling and parsing

Events are emitted at each step for observability.

Example

orchestrator = ExecutionOrchestrator( signer=signer, submitter=submitter, simulator=simulator, chain="arbitrum", )

result = await orchestrator.execute(action_bundle) if result.success: print(f"All transactions confirmed: {result.transaction_results}") else: print(f"Execution failed at {result.error_phase}: {result.error}")

Initialize the orchestrator.

Parameters:

| Name                    | Type                    | Description                                           | Default                                                                                                          |
| ----------------------- | ----------------------- | ----------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `signer`                | `Signer`                | Signer implementation for transaction signing         | *required*                                                                                                       |
| `submitter`             | `Submitter`             | Submitter implementation for transaction submission   | *required*                                                                                                       |
| `simulator`             | `Simulator`             | Simulator implementation for pre-execution simulation | *required*                                                                                                       |
| `chain`                 | `str`                   | Target blockchain network                             | `'arbitrum'`                                                                                                     |
| `rpc_url`               | \`str                   | None\`                                                | RPC URL for nonce queries (optional if submitter provides)                                                       |
| `risk_guard`            | \`RiskGuard             | None\`                                                | RiskGuard for validation (uses default if not provided)                                                          |
| `event_callback`        | \`EventCallback         | None\`                                                | Optional callback for execution events                                                                           |
| `gas_buffer_multiplier` | \`float                 | None\`                                                | Gas buffer multiplier (uses chain default if not provided)                                                       |
| `tx_timeout_seconds`    | \`float                 | None\`                                                | Timeout for transaction confirmation. If None, uses chain-specific default (300s for Ethereum L1, 120s for L2s). |
| `session_store`         | \`ExecutionSessionStore | None\`                                                | Optional ExecutionSessionStore for crash recovery checkpoints                                                    |
| `tx_risk_config`        | \`TransactionRiskConfig | None\`                                                | Transaction risk configuration (uses default if not provided)                                                    |

### reset_nonce_cache

```
reset_nonce_cache(
    wallet_address: str | None = None,
) -> None
```

Reset the local nonce cache, forcing fresh on-chain query on next execution.

Call this after a nonce-related error to recover from nonce drift.

Parameters:

| Name             | Type  | Description | Default                                                       |
| ---------------- | ----- | ----------- | ------------------------------------------------------------- |
| `wallet_address` | \`str | None\`      | Specific address to reset. If None, clears all cached nonces. |

### execute

```
execute(
    action_bundle: ActionBundle,
    context: ExecutionContext | None = None,
) -> ExecutionResult
```

Execute an ActionBundle through the full execution pipeline.

This method:

1. Validates transactions via RiskGuard
1. Simulates transactions (if enabled)
1. Assigns sequential nonces
1. Signs all transactions
1. Submits transactions
1. Polls for and parses receipts

Parameters:

| Name            | Type               | Description                 | Default                    |
| --------------- | ------------------ | --------------------------- | -------------------------- |
| `action_bundle` | `ActionBundle`     | The ActionBundle to execute | *required*                 |
| `context`       | \`ExecutionContext | None\`                      | Optional execution context |

Returns:

| Type              | Description                                     |
| ----------------- | ----------------------------------------------- |
| `ExecutionResult` | ExecutionResult with complete execution details |

### get_current_nonce

```
get_current_nonce(address: str | None = None) -> int
```

Get the current nonce for an address.

Parameters:

| Name      | Type  | Description | Default                                       |
| --------- | ----- | ----------- | --------------------------------------------- |
| `address` | \`str | None\`      | Address to query (defaults to signer address) |

Returns:

| Type  | Description                   |
| ----- | ----------------------------- |
| `int` | Current nonce for the address |

### get_gas_price

```
get_gas_price() -> dict[str, int]
```

Get current gas prices from the network.

Returns:

| Type             | Description                                            |
| ---------------- | ------------------------------------------------------ |
| `dict[str, int]` | Dict with max_fee_per_gas and max_priority_fee_per_gas |

### set_event_callback

```
set_event_callback(callback: EventCallback | None) -> None
```

Set the event callback.

Parameters:

| Name       | Type            | Description | Default                                |
| ---------- | --------------- | ----------- | -------------------------------------- |
| `callback` | \`EventCallback | None\`      | Callback function for execution events |

## ExecutionResult

## almanak.framework.execution.ExecutionResult

```
ExecutionResult(
    success: bool,
    phase: ExecutionPhase,
    transaction_results: list[TransactionResult] = list(),
    simulation_result: SimulationResult | None = None,
    total_gas_used: int = 0,
    total_gas_cost_wei: int = 0,
    error: str | None = None,
    error_phase: ExecutionPhase | None = None,
    started_at: datetime = (lambda: datetime.now(UTC))(),
    completed_at: datetime | None = None,
    correlation_id: str = "",
    gas_warnings: list[str] = list(),
    position_id: int | str | None = None,
    swap_amounts: SwapAmounts | None = None,
    lp_close_data: LPCloseData | None = None,
    bin_ids: list[int] | None = None,
    extracted_data: dict[str, Any] = dict(),
    extraction_warnings: list[str] = list(),
)
```

Complete result of an execution attempt.

Attributes:

| Name                  | Type                      | Description                                        |
| --------------------- | ------------------------- | -------------------------------------------------- |
| `success`             | `bool`                    | Whether all transactions succeeded                 |
| `phase`               | `ExecutionPhase`          | Phase where execution completed or failed          |
| `transaction_results` | `list[TransactionResult]` | Results for each transaction                       |
| `simulation_result`   | \`SimulationResult        | None\`                                             |
| `total_gas_used`      | `int`                     | Sum of gas used across all transactions            |
| `total_gas_cost_wei`  | `int`                     | Sum of gas costs across all transactions           |
| `error`               | \`str                     | None\`                                             |
| `error_phase`         | \`ExecutionPhase          | None\`                                             |
| `started_at`          | `datetime`                | When execution started                             |
| `completed_at`        | \`datetime                | None\`                                             |
| `correlation_id`      | `str`                     | Unique identifier for this execution               |
| `position_id`         | \`int                     | str                                                |
| `swap_amounts`        | \`SwapAmounts             | None\`                                             |
| `lp_close_data`       | \`LPCloseData             | None\`                                             |
| `bin_ids`             | \`list[int]               | None\`                                             |
| `extracted_data`      | `dict[str, Any]`          | Flexible dict for protocol-specific extracted data |
| `extraction_warnings` | `list[str]`               | Non-fatal warnings from extraction process         |

### effective_price

```
effective_price: Decimal | None
```

Convenience accessor for swap effective price.

### slippage_bps

```
slippage_bps: int | None
```

Convenience accessor for swap slippage in basis points.

### __post_init__

```
__post_init__() -> None
```

Generate correlation_id if not provided.

### get_extracted

```
get_extracted(
    key: str,
    expected_type: type | None = None,
    default: Any = None,
) -> Any
```

Get extracted data with optional type checking.

Provides safe access to protocol-specific extracted data with optional type validation.

Parameters:

| Name            | Type   | Description                                  | Default                           |
| --------------- | ------ | -------------------------------------------- | --------------------------------- |
| `key`           | `str`  | Data key to retrieve from extracted_data     | *required*                        |
| `expected_type` | \`type | None\`                                       | Optional type to validate against |
| `default`       | `Any`  | Default value if key not found or wrong type | `None`                            |

Returns:

| Type  | Description                |
| ----- | -------------------------- |
| `Any` | Extracted value or default |

Example

tick_lower = result.get_extracted("tick_lower", int, 0) liquidity = result.get_extracted("liquidity")

### to_outcome

```
to_outcome() -> ExecutionOutcome
```

Convert to chain-agnostic ExecutionOutcome.

Returns:

| Type               | Description                                                       |
| ------------------ | ----------------------------------------------------------------- |
| `ExecutionOutcome` | ExecutionOutcome with EVM-specific fields mapped to common shape. |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

## ExecutionContext

## almanak.framework.execution.ExecutionContext

```
ExecutionContext(
    strategy_id: str = "unknown",
    intent_id: str = "",
    chain: str = "arbitrum",
    wallet_address: str = "",
    correlation_id: str = "",
    session_id: str = "",
    simulation_enabled: bool = False,
    intent_description: str = "",
    dry_run: bool = False,
    protocol: str | None = None,
)
```

Context for the current execution.

Attributes:

| Name                 | Type   | Description                                       |
| -------------------- | ------ | ------------------------------------------------- |
| `strategy_id`        | `str`  | Strategy identifier                               |
| `intent_id`          | `str`  | Intent identifier (for session tracking)          |
| `chain`              | `str`  | Blockchain network                                |
| `wallet_address`     | `str`  | Address executing transactions                    |
| `correlation_id`     | `str`  | Unique identifier for this execution              |
| `session_id`         | `str`  | Execution session identifier (for crash recovery) |
| `simulation_enabled` | `bool` | Whether to simulate before execution              |
| `dry_run`            | `bool` | If True, don't actually submit transactions       |

### __post_init__

```
__post_init__() -> None
```

Generate correlation_id if not provided.

## Result Enrichment

After successful execution, `ResultEnricher` automatically extracts data from transaction receipts (position IDs, swap amounts, etc.) and attaches it to the result.

### ResultEnricher

## almanak.framework.execution.ResultEnricher

```
ResultEnricher(
    parser_registry: ReceiptParserRegistry | None = None,
)
```

Enriches ExecutionResult with intent-specific extracted data.

This component implements the "Framework Orchestrates, Protocols Execute" pattern. It determines WHAT to extract based on intent type, and delegates HOW to extract to protocol-specific parsers.

Key Design Principles:

1. Fail-Safe: Extraction errors are logged but never crash execution
1. Type-Safe: Core fields are strongly typed
1. Extensible: New protocols can be added without framework changes
1. Zero Cognitive Load: Data "just appears" on result

Example

enricher = ResultEnricher()

### In StrategyRunner after execution:

result = await orchestrator.execute(bundle) if result.success: result = enricher.enrich(result, intent, context)

### Strategy callback receives enriched result:

strategy.on_intent_executed(intent, success=True, result=result)

### Strategy can use result.position_id directly!

Initialize the ResultEnricher.

Parameters:

| Name              | Type                    | Description | Default                                                                           |
| ----------------- | ----------------------- | ----------- | --------------------------------------------------------------------------------- |
| `parser_registry` | \`ReceiptParserRegistry | None\`      | Registry for protocol parsers. If not provided, uses the default global registry. |

### enrich

```
enrich(
    result: ExecutionResult,
    intent: Any,
    context: ExecutionContext,
) -> ExecutionResult
```

Enrich execution result with intent-specific extracted data.

This method extracts relevant data from transaction receipts based on the intent type and attaches it to the ExecutionResult.

IMPORTANT: This method NEVER raises exceptions. All errors are logged as warnings and added to result.extraction_warnings.

Parameters:

| Name      | Type               | Description                            | Default    |
| --------- | ------------------ | -------------------------------------- | ---------- |
| `result`  | `ExecutionResult`  | Raw execution result from orchestrator | *required* |
| `intent`  | `Any`              | The intent that was executed           | *required* |
| `context` | `ExecutionContext` | Execution context with chain info      | *required* |

Returns:

| Type              | Description                                       |
| ----------------- | ------------------------------------------------- |
| `ExecutionResult` | Enriched ExecutionResult (same instance, mutated) |

Example

result = enricher.enrich(result, intent, context)

#### result.position_id is now populated (if LP_OPEN)

#### result.swap_amounts is now populated (if SWAP)

### SwapAmounts

## almanak.framework.execution.SwapAmounts

```
SwapAmounts(
    amount_in: int,
    amount_out: int,
    amount_in_decimal: Decimal,
    amount_out_decimal: Decimal,
    effective_price: Decimal | None = None,
    slippage_bps: int | None = None,
    token_in: str | None = None,
    token_out: str | None = None,
)
```

Extracted swap execution data.

Represents the token amounts exchanged in a swap transaction. All fields are immutable (frozen=True) for safety.

Attributes:

| Name                 | Type      | Description                                  |
| -------------------- | --------- | -------------------------------------------- |
| `amount_in`          | `int`     | Raw input amount (in token's smallest unit)  |
| `amount_out`         | `int`     | Raw output amount (in token's smallest unit) |
| `amount_in_decimal`  | `Decimal` | Human-readable input amount                  |
| `amount_out_decimal` | `Decimal` | Human-readable output amount                 |
| `effective_price`    | \`Decimal | None\`                                       |
| `slippage_bps`       | \`int     | None\`                                       |
| `token_in`           | \`str     | None\`                                       |
| `token_out`          | \`str     | None\`                                       |

Example

if result.swap_amounts: price = result.swap_amounts.effective_price slippage = result.swap_amounts.slippage_bps

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

### LPCloseData

## almanak.framework.execution.LPCloseData

```
LPCloseData(
    amount0_collected: int,
    amount1_collected: int,
    fees0: int = 0,
    fees1: int = 0,
    liquidity_removed: int | None = None,
    additional_amounts: dict[int, int] | None = None,
    additional_fees: dict[int, int] | None = None,
)
```

Extracted LP close execution data.

Represents the amounts collected when closing an LP position, including principal and fees.

Attributes:

| Name                 | Type             | Description                                         |
| -------------------- | ---------------- | --------------------------------------------------- |
| `amount0_collected`  | `int`            | Total amount of token0 collected (principal + fees) |
| `amount1_collected`  | `int`            | Total amount of token1 collected (principal + fees) |
| `fees0`              | `int`            | Fees earned in token0 (if separately tracked)       |
| `fees1`              | `int`            | Fees earned in token1 (if separately tracked)       |
| `liquidity_removed`  | \`int            | None\`                                              |
| `additional_amounts` | \`dict[int, int] | None\`                                              |
| `additional_fees`    | \`dict[int, int] | None\`                                              |

Example

if result.lp_close_data: total_0 = result.lp_close_data.amount0_collected fees_0 = result.lp_close_data.fees0

# For 4-coin pools (e.g., Curve NG):

all_amounts = result.lp_close_data.all_amounts # [amt0, amt1, amt2, amt3]

### all_amounts

```
all_amounts: list[int]
```

Return all coin amounts as a list, including additional coins.

### all_fees

```
all_fees: list[int]
```

Return all fee amounts as a list, including additional coins.

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

## Signers

### LocalKeySigner

## almanak.framework.execution.LocalKeySigner

```
LocalKeySigner(private_key: str)
```

Bases: `Signer`

Signs transactions using a local private key.

This signer uses the eth-account library to sign transactions locally. The private key is stored in memory and derived to an Ethereum account at initialization time.

SECURITY CONTRACT

- Private keys are NEVER logged, printed, or included in error messages
- Private keys are NEVER exposed through any method or property
- Transaction fields are validated before signing
- The wallet address is derived from the key at initialization

Supported transaction types

- EIP-1559 (Type 2): Modern fee market transactions
- Legacy (Type 0): Pre-EIP-1559 transactions

Attributes:

| Name      | Type  | Description                                                   |
| --------- | ----- | ------------------------------------------------------------- |
| `address` | `str` | The checksummed Ethereum address derived from the private key |

Example

### Initialize with private key

signer = LocalKeySigner(private_key="0x...")

### Create unsigned transaction

tx = UnsignedTransaction( to="0x1234...", value=0, data="0xa9059cbb...", chain_id=42161, gas_limit=100000, nonce=5, max_fee_per_gas=100_000_000, max_priority_fee_per_gas=1_000_000, )

### Sign transaction

signed = await signer.sign(tx, chain="arbitrum") print(f"Signed tx hash: {signed.tx_hash}")

Initialize the signer with a private key.

Parameters:

| Name          | Type  | Description                                         | Default    |
| ------------- | ----- | --------------------------------------------------- | ---------- |
| `private_key` | `str` | Hex-encoded private key (with or without 0x prefix) | *required* |

Raises:

| Type           | Description                   |
| -------------- | ----------------------------- |
| `SigningError` | If the private key is invalid |

Example

signer = LocalKeySigner(private_key="0xabc123...")

### address

```
address: str
```

Return the wallet address associated with this signer.

The address is derived from the private key at initialization and is cached for efficiency.

Returns:

| Type  | Description                                               |
| ----- | --------------------------------------------------------- |
| `str` | Checksummed Ethereum address (0x-prefixed, 42 characters) |

Example

signer = LocalKeySigner(private_key="0x...") print(signer.address) # 0x71C7656EC7ab88b098defB751B7401B5f6d8976F

### sign

```
sign(
    tx: UnsignedTransaction, chain: str
) -> SignedTransaction
```

Sign a transaction with the local private key.

This method validates the transaction fields, builds the appropriate transaction dictionary based on transaction type (EIP-1559 or legacy), and signs it using the eth-account library.

Parameters:

| Name    | Type                  | Description                                                      | Default    |
| ------- | --------------------- | ---------------------------------------------------------------- | ---------- |
| `tx`    | `UnsignedTransaction` | Unsigned transaction to sign                                     | *required* |
| `chain` | `str`                 | Chain name (e.g., "arbitrum", "ethereum") for logging/validation | *required* |

Returns:

| Type                | Description                                                            |
| ------------------- | ---------------------------------------------------------------------- |
| `SignedTransaction` | SignedTransaction containing the raw signed bytes and transaction hash |

Raises:

| Type           | Description                                          |
| -------------- | ---------------------------------------------------- |
| `SigningError` | If signing fails due to invalid fields or key issues |
| `ValueError`   | If transaction fields are malformed                  |

Example

tx = UnsignedTransaction( to="0x1234...", value=1_000_000_000_000_000_000, # 1 ETH data="0x", chain_id=1, gas_limit=21000, nonce=0, max_fee_per_gas=30_000_000_000, max_priority_fee_per_gas=1_000_000_000, ) signed = await signer.sign(tx, chain="ethereum")

### __repr__

```
__repr__() -> str
```

Return string representation without exposing private key.

### __str__

```
__str__() -> str
```

Return string representation without exposing private key.

## Simulators

### DirectSimulator

## almanak.framework.execution.DirectSimulator

```
DirectSimulator(name: str = 'direct')
```

Bases: `Simulator`

Pass-through simulator that skips actual simulation.

This simulator returns a successful SimulationResult without performing any actual simulation. It is designed for environments where simulation is not needed, such as local fork testing or trusted execution contexts.

The key differentiator from other simulators:

- Returns `simulated=False` to indicate no simulation was performed
- Always returns `success=True` (assumes transactions are valid)
- Logs that simulation was skipped for observability

This is NOT a stub or shortcut - it is a legitimate implementation for production use cases where pre-execution simulation adds no value or where the latency cost is unacceptable.

Future Alternatives:

- TenderlySimulator: Full simulation via Tenderly API
- LocalSimulator: Simulation via local node eth_call
- FlashbotsSimulator: Simulation via Flashbots bundle simulation

Attributes:

| Name   | Type  | Description                                             |
| ------ | ----- | ------------------------------------------------------- |
| `name` | `str` | Identifier for this simulator (for logging and metrics) |

Example

simulator = DirectSimulator()

### Simulate a single transaction

result = await simulator.simulate([tx], chain="arbitrum")

if result.success:

# Proceed to signing and submission

... else:

# Handle simulation failure (won't happen with DirectSimulator)

...

Initialize the DirectSimulator.

Parameters:

| Name   | Type  | Description                                                | Default    |
| ------ | ----- | ---------------------------------------------------------- | ---------- |
| `name` | `str` | Identifier for this simulator instance (default: "direct") | `'direct'` |

### name

```
name: str
```

Return the simulator name.

### simulate

```
simulate(
    txs: list[UnsignedTransaction],
    chain: str,
    state_overrides: dict[str, Any] | None = None,
) -> SimulationResult
```

Return a pass-through simulation result without actual simulation.

This method logs that simulation was skipped and returns a successful SimulationResult with `simulated=False` to indicate no actual simulation was performed.

Parameters:

| Name              | Type                        | Description                                     | Default                                              |
| ----------------- | --------------------------- | ----------------------------------------------- | ---------------------------------------------------- |
| `txs`             | `list[UnsignedTransaction]` | List of unsigned transactions to "simulate"     | *required*                                           |
| `chain`           | `str`                       | Chain name (logged but not used for simulation) | *required*                                           |
| `state_overrides` | \`dict[str, Any]            | None\`                                          | Ignored - DirectSimulator doesn't perform simulation |

Returns:

| Type               | Description                                             |
| ------------------ | ------------------------------------------------------- |
| `SimulationResult` | SimulationResult with:                                  |
| `SimulationResult` | success=True (assumes transactions are valid)           |
| `SimulationResult` | simulated=False (indicates no simulation was performed) |
| `SimulationResult` | Empty gas_estimates, warnings, state_changes, logs      |

Note

This method never raises exceptions for valid input. Infrastructure failures are not possible since no external calls are made.

### TenderlySimulator

## almanak.framework.execution.TenderlySimulator

```
TenderlySimulator(
    account_slug: str,
    project_slug: str,
    access_key: str,
    timeout_seconds: float = 10.0,
    name: str = "tenderly",
)
```

Bases: `Simulator`

Transaction simulation via Tenderly REST API.

This simulator uses Tenderly's simulate-bundle endpoint to simulate transactions before submission. It provides accurate gas estimates, pre-flight validation, and supports SAFE wallet simulations via state overrides.

Key Advantages over Alchemy

- No transaction bundle limit (Alchemy limited to 3)
- State override support for SAFE wallet ETH balance
- Support for more chains (9+ vs 4 for Alchemy)
- Detailed simulation dashboard URLs

Attributes:

| Name              | Type | Description                 |
| ----------------- | ---- | --------------------------- |
| `account_slug`    |      | Tenderly account identifier |
| `project_slug`    |      | Tenderly project identifier |
| `timeout_seconds` |      | Request timeout             |

Example

simulator = TenderlySimulator( account_slug="my-account", project_slug="my-project", access_key="xxx", )

### Basic simulation

result = await simulator.simulate([tx], chain="arbitrum")

### SAFE wallet simulation with ETH balance override

result = await simulator.simulate( [tx], chain="arbitrum", state_overrides={"0xSafeAddress": {"balance": hex(10 * 10\*\*18)}}, )

Initialize the TenderlySimulator.

Parameters:

| Name              | Type    | Description                                     | Default      |
| ----------------- | ------- | ----------------------------------------------- | ------------ |
| `account_slug`    | `str`   | Tenderly account slug                           | *required*   |
| `project_slug`    | `str`   | Tenderly project slug                           | *required*   |
| `access_key`      | `str`   | Tenderly API access key                         | *required*   |
| `timeout_seconds` | `float` | Request timeout (default 10s)                   | `10.0`       |
| `name`            | `str`   | Simulator name for logging (default "tenderly") | `'tenderly'` |

Raises:

| Type         | Description                          |
| ------------ | ------------------------------------ |
| `ValueError` | If any required parameter is missing |

### name

```
name: str
```

Return the simulator name.

### supports_chain

```
supports_chain(chain: str) -> bool
```

Check if this simulator supports a given chain.

Parameters:

| Name    | Type  | Description            | Default    |
| ------- | ----- | ---------------------- | ---------- |
| `chain` | `str` | Chain name (lowercase) | *required* |

Returns:

| Type   | Description                          |
| ------ | ------------------------------------ |
| `bool` | True if Tenderly supports this chain |

### simulate

```
simulate(
    txs: list[UnsignedTransaction],
    chain: str,
    state_overrides: dict[str, Any] | None = None,
) -> SimulationResult
```

Simulate transactions via Tenderly API.

This method simulates the execution of one or more transactions using Tenderly's bundle simulation endpoint. It returns gas estimates and validates that transactions will succeed.

Parameters:

| Name              | Type                        | Description                               | Default                                                                                  |
| ----------------- | --------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------- |
| `txs`             | `list[UnsignedTransaction]` | List of unsigned transactions to simulate | *required*                                                                               |
| `chain`           | `str`                       | Chain name (e.g., "arbitrum")             | *required*                                                                               |
| `state_overrides` | \`dict[str, Any]            | None\`                                    | Optional state overrides for SAFE wallets Format: {"0xAddress": {"balance": "0xHexWei"}} |

Returns:

| Type               | Description                                            |
| ------------------ | ------------------------------------------------------ |
| `SimulationResult` | SimulationResult with gas estimates and success status |

Raises:

| Type              | Description                                   |
| ----------------- | --------------------------------------------- |
| `SimulationError` | For infrastructure failures (not tx failures) |

## Receipt Parsing

### ReceiptParserRegistry

## almanak.framework.execution.ReceiptParserRegistry

```
ReceiptParserRegistry()
```

Registry for protocol receipt parsers.

The registry provides lazy loading of parser classes and supports both built-in parsers and custom parser registration.

Built-in parsers are loaded from the connectors package when first requested. Custom parsers can be registered at any time.

Example

registry = ReceiptParserRegistry()

### Get a built-in parser

spark_parser = registry.get("spark")

### Register a custom parser

registry.register("my_protocol", MyProtocolReceiptParser)

### Check available protocols

protocols = registry.list_protocols()

Initialize the registry.

### get

```
get(protocol: str, **kwargs: Any) -> ReceiptParser
```

Get a receipt parser for a protocol.

Parameters:

| Name       | Type  | Description                                                       | Default    |
| ---------- | ----- | ----------------------------------------------------------------- | ---------- |
| `protocol` | `str` | Protocol name (e.g., "spark", "lido", "ethena", "pancakeswap_v3") | *required* |
| `**kwargs` | `Any` | Additional arguments to pass to parser constructor                | `{}`       |

Returns:

| Type            | Description             |
| --------------- | ----------------------- |
| `ReceiptParser` | Receipt parser instance |

Raises:

| Type         | Description                   |
| ------------ | ----------------------------- |
| `ValueError` | If protocol is not registered |

### register

```
register(
    protocol: str, parser_class: type[ReceiptParser]
) -> None
```

Register a custom receipt parser.

Parameters:

| Name           | Type                  | Description                 | Default    |
| -------------- | --------------------- | --------------------------- | ---------- |
| `protocol`     | `str`                 | Protocol name               | *required* |
| `parser_class` | `type[ReceiptParser]` | Parser class (not instance) | *required* |

Raises:

| Type        | Description                    |
| ----------- | ------------------------------ |
| `TypeError` | If parser_class is not a class |

### unregister

```
unregister(protocol: str) -> bool
```

Unregister a custom receipt parser.

Parameters:

| Name       | Type  | Description   | Default    |
| ---------- | ----- | ------------- | ---------- |
| `protocol` | `str` | Protocol name | *required* |

Returns:

| Type   | Description                                         |
| ------ | --------------------------------------------------- |
| `bool` | True if parser was unregistered, False if not found |

### list_protocols

```
list_protocols() -> list[str]
```

List all available protocol names.

Returns:

| Type        | Description                       |
| ----------- | --------------------------------- |
| `list[str]` | List of registered protocol names |

### is_registered

```
is_registered(protocol: str) -> bool
```

Check if a protocol is registered.

Parameters:

| Name       | Type  | Description   | Default    |
| ---------- | ----- | ------------- | ---------- |
| `protocol` | `str` | Protocol name | *required* |

Returns:

| Type   | Description                    |
| ------ | ------------------------------ |
| `bool` | True if protocol is registered |

### clear_cache

```
clear_cache() -> None
```

Clear the parser instance cache.

Useful for testing or when parser configuration changes.

## Exceptions

## almanak.framework.execution.ExecutionError

Bases: `Exception`

Base exception for execution layer errors.

All execution-related exceptions inherit from this class to allow broad exception handling when needed.

## almanak.framework.execution.SimulationError

```
SimulationError(reason: str, recoverable: bool = True)
```

Bases: `ExecutionError`

Raised when transaction simulation fails.

This is distinct from a transaction that simulates successfully but would revert - that returns SimulationResult with success=False.

This exception is for infrastructure failures:

- Simulation service unavailable
- Network timeout
- Invalid simulation parameters

Attributes:

| Name          | Type | Description                                       |
| ------------- | ---- | ------------------------------------------------- |
| `reason`      |      | Human-readable explanation of the failure         |
| `recoverable` |      | Whether the error is transient and can be retried |

## almanak.framework.execution.SigningError

```
SigningError(reason: str, tx_hash: str | None = None)
```

Bases: `ExecutionError`

Raised when transaction signing fails.

This exception should be raised when:

- Private key is invalid or missing
- Transaction fields are malformed
- Signing algorithm encounters an error

Note: Never include sensitive key material in error messages.

Attributes:

| Name      | Type | Description                                                 |
| --------- | ---- | ----------------------------------------------------------- |
| `reason`  |      | Human-readable explanation of the failure                   |
| `tx_hash` |      | Optional hash of the transaction that failed (if available) |

# Technical Indicators

Technical analysis indicator calculators available through the data layer.

## RSICalculator

## almanak.framework.data.indicators.RSICalculator

```
RSICalculator(
    ohlcv_provider: OHLCVProvider, default_period: int = 14
)
```

RSI (Relative Strength Index) Calculator using Wilder's Smoothing Method.

Implements the standard RSI formula with Wilder's exponential moving average for smoothing, which is the industry-standard approach.

RSI Formula

RSI = 100 - (100 / (1 + RS)) RS = Average Gain / Average Loss

Wilder's Smoothing:

- First average: Simple average of first N periods
- Subsequent: ((Previous Avg * (N-1)) + Current Value) / N

Attributes:

| Name             | Type | Description                                                 |
| ---------------- | ---- | ----------------------------------------------------------- |
| `ohlcv_provider` |      | Provider for OHLCV data (implements OHLCVProvider protocol) |
| `default_period` |      | Default RSI period (default 14)                             |

Example

provider = CoinGeckoOHLCVProvider() calculator = RSICalculator(ohlcv_provider=provider)

### Calculate 14-period RSI for WETH

rsi = await calculator.calculate_rsi("WETH", period=14) print(f"RSI: {rsi:.2f}")

### Interpret the signal

if rsi < 30: print("Oversold - potential buy signal") elif rsi > 70: print("Overbought - potential sell signal")

Initialize the RSI Calculator.

Parameters:

| Name             | Type            | Description                                  | Default    |
| ---------------- | --------------- | -------------------------------------------- | ---------- |
| `ohlcv_provider` | `OHLCVProvider` | Provider implementing OHLCVProvider protocol | *required* |
| `default_period` | `int`           | Default RSI calculation period (default 14)  | `14`       |

### calculate_rsi_from_prices

```
calculate_rsi_from_prices(
    close_prices: list[Decimal], period: int = 14
) -> float
```

Calculate RSI from a list of close prices using Wilder's smoothing.

This is a pure calculation function that can be used independently of the data provider for testing or alternative data sources.

Parameters:

| Name           | Type            | Description                           | Default    |
| -------------- | --------------- | ------------------------------------- | ---------- |
| `close_prices` | `list[Decimal]` | List of closing prices (oldest first) | *required* |
| `period`       | `int`           | RSI calculation period (default 14)   | `14`       |

Returns:

| Type    | Description             |
| ------- | ----------------------- |
| `float` | RSI value from 0 to 100 |

Raises:

| Type                    | Description                                |
| ----------------------- | ------------------------------------------ |
| `InsufficientDataError` | If not enough price data (need period + 1) |

### calculate_rsi

```
calculate_rsi(
    token: str, period: int = 14, timeframe: str = "4h"
) -> float
```

Calculate RSI for a token.

Fetches OHLCV data from the configured provider and calculates RSI using Wilder's smoothing method.

Parameters:

| Name        | Type  | Description                                                                                                                                           | Default    |
| ----------- | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `token`     | `str` | Token symbol (e.g., "WETH", "ETH")                                                                                                                    | *required* |
| `period`    | `int` | RSI calculation period (default 14)                                                                                                                   | `14`       |
| `timeframe` | `str` | OHLCV candle timeframe (default "4h") Supported: "1m", "5m", "15m", "1h", "4h", "1d" Note: 1m/5m/15m may return 30-min candles (CoinGecko limitation) | `'4h'`     |

Returns:

| Type    | Description             |
| ------- | ----------------------- |
| `float` | RSI value from 0 to 100 |

Raises:

| Type                    | Description                   |
| ----------------------- | ----------------------------- |
| `InsufficientDataError` | If not enough historical data |
| `DataSourceError`       | If data cannot be fetched     |

Example

#### Default 4-hour candles

rsi = await calculator.calculate_rsi("WETH", period=14)

#### 1-hour candles for shorter-term analysis

rsi_1h = await calculator.calculate_rsi("WETH", period=14, timeframe="1h")

#### Daily candles for longer-term analysis

rsi_1d = await calculator.calculate_rsi("WETH", period=14, timeframe="1d")

### get_ohlcv_provider_health

```
get_ohlcv_provider_health() -> dict[str, Any]
```

Get health metrics from the OHLCV provider if available.

## BollingerBandsCalculator

## almanak.framework.data.indicators.BollingerBandsCalculator

```
BollingerBandsCalculator(ohlcv_provider: OHLCVProvider)
```

Bollinger Bands Calculator.

Calculates Bollinger Bands with configurable period and standard deviation multiplier.

Default Parameters

- Period: 20 (industry standard)
- Standard Deviation Multiplier: 2.0 (captures ~95% of price movement)

Attributes:

| Name             | Type | Description                                                 |
| ---------------- | ---- | ----------------------------------------------------------- |
| `ohlcv_provider` |      | Provider for OHLCV data (implements OHLCVProvider protocol) |

Example

provider = CoinGeckoOHLCVProvider() bb_calc = BollingerBandsCalculator(ohlcv_provider=provider)

bb = await bb_calc.calculate_bollinger_bands("WETH", period=20, std_dev=2.0) print(f"Upper: {bb.upper_band}, Middle: {bb.middle_band}, Lower: {bb.lower_band}")

### Check price position

if bb.percent_b < 0.2: print("Near lower band - potential buy")

Initialize the Bollinger Bands Calculator.

Parameters:

| Name             | Type            | Description                                  | Default    |
| ---------------- | --------------- | -------------------------------------------- | ---------- |
| `ohlcv_provider` | `OHLCVProvider` | Provider implementing OHLCVProvider protocol | *required* |

### name

```
name: str
```

Return indicator name.

### min_data_points

```
min_data_points: int
```

Return minimum data points (default period of 20).

### calculate_bollinger_from_prices

```
calculate_bollinger_from_prices(
    close_prices: list[Decimal],
    period: int = 20,
    std_dev_multiplier: float = 2.0,
) -> BollingerBandsResult
```

Calculate Bollinger Bands from a list of close prices.

Parameters:

| Name                 | Type            | Description                                 | Default    |
| -------------------- | --------------- | ------------------------------------------- | ---------- |
| `close_prices`       | `list[Decimal]` | List of closing prices (oldest first)       | *required* |
| `period`             | `int`           | SMA period (default 20)                     | `20`       |
| `std_dev_multiplier` | `float`         | Standard deviation multiplier (default 2.0) | `2.0`      |

Returns:

| Type                   | Description                               |
| ---------------------- | ----------------------------------------- |
| `BollingerBandsResult` | BollingerBandsResult with all band values |

Raises:

| Type                    | Description              |
| ----------------------- | ------------------------ |
| `InsufficientDataError` | If not enough price data |

### calculate_bollinger_bands

```
calculate_bollinger_bands(
    token: str,
    period: int = 20,
    std_dev: float = 2.0,
    timeframe: str = "1h",
) -> BollingerBandsResult
```

Calculate Bollinger Bands for a token.

Parameters:

| Name        | Type    | Description                                 | Default    |
| ----------- | ------- | ------------------------------------------- | ---------- |
| `token`     | `str`   | Token symbol (e.g., "WETH", "ETH")          | *required* |
| `period`    | `int`   | SMA period (default 20)                     | `20`       |
| `std_dev`   | `float` | Standard deviation multiplier (default 2.0) | `2.0`      |
| `timeframe` | `str`   | OHLCV candle timeframe (default "1h")       | `'1h'`     |

Returns:

| Type                   | Description                                                    |
| ---------------------- | -------------------------------------------------------------- |
| `BollingerBandsResult` | BollingerBandsResult with upper_band, middle_band, lower_band, |
| `BollingerBandsResult` | bandwidth, and percent_b                                       |

Raises:

| Type                    | Description                   |
| ----------------------- | ----------------------------- |
| `InsufficientDataError` | If not enough historical data |
| `DataSourceError`       | If data cannot be fetched     |

Example

bb = await bb_calc.calculate_bollinger_bands("WETH", period=20, std_dev=2.0)

#### Trading logic

if bb.percent_b < 0:

# Price below lower band - potential buy

return SwapIntent(token_in="USDC", token_out="WETH", ...) elif bb.percent_b > 1:

# Price above upper band - potential sell

return SwapIntent(token_in="WETH", token_out="USDC", ...)

#### Volatility check

if bb.bandwidth < 0.05: print("Low volatility - squeeze detected")

### calculate

```
calculate(
    token: str, timeframe: str = "1h", **params: Any
) -> dict[str, float]
```

Calculate Bollinger Bands (BaseIndicator protocol implementation).

Parameters:

| Name        | Type  | Description                                | Default    |
| ----------- | ----- | ------------------------------------------ | ---------- |
| `token`     | `str` | Token symbol                               | *required* |
| `timeframe` | `str` | OHLCV candle timeframe                     | `'1h'`     |
| `**params`  | `Any` | period (default 20), std_dev (default 2.0) | `{}`       |

Returns:

| Type               | Description                                |
| ------------------ | ------------------------------------------ |
| `dict[str, float]` | Dictionary with all Bollinger Bands values |

## MACDCalculator

## almanak.framework.data.indicators.MACDCalculator

```
MACDCalculator(ohlcv_provider: OHLCVProvider)
```

MACD (Moving Average Convergence Divergence) Calculator.

Calculates MACD with configurable fast, slow, and signal periods.

Default Parameters (industry standard):

- Fast EMA: 12 periods
- Slow EMA: 26 periods
- Signal EMA: 9 periods

Attributes:

| Name             | Type | Description                                                 |
| ---------------- | ---- | ----------------------------------------------------------- |
| `ohlcv_provider` |      | Provider for OHLCV data (implements OHLCVProvider protocol) |

Example

provider = CoinGeckoOHLCVProvider() macd_calc = MACDCalculator(ohlcv_provider=provider)

macd = await macd_calc.calculate_macd("WETH", fast=12, slow=26, signal=9) print(f"MACD: {macd.macd_line}, Signal: {macd.signal_line}")

### Check for bullish crossover

if macd.histogram > 0: print("MACD above signal - bullish")

Initialize the MACD Calculator.

Parameters:

| Name             | Type            | Description                                  | Default    |
| ---------------- | --------------- | -------------------------------------------- | ---------- |
| `ohlcv_provider` | `OHLCVProvider` | Provider implementing OHLCVProvider protocol | *required* |

### name

```
name: str
```

Return indicator name.

### min_data_points

```
min_data_points: int
```

Return minimum data points (slow period + signal + buffer).

### calculate_macd_from_prices

```
calculate_macd_from_prices(
    close_prices: list[Decimal],
    fast_period: int = 12,
    slow_period: int = 26,
    signal_period: int = 9,
) -> MACDResult
```

Calculate MACD from a list of close prices.

Parameters:

| Name            | Type            | Description                           | Default    |
| --------------- | --------------- | ------------------------------------- | ---------- |
| `close_prices`  | `list[Decimal]` | List of closing prices (oldest first) | *required* |
| `fast_period`   | `int`           | Fast EMA period (default 12)          | `12`       |
| `slow_period`   | `int`           | Slow EMA period (default 26)          | `26`       |
| `signal_period` | `int`           | Signal line EMA period (default 9)    | `9`        |

Returns:

| Type         | Description                                           |
| ------------ | ----------------------------------------------------- |
| `MACDResult` | MACDResult with macd_line, signal_line, and histogram |

Raises:

| Type                    | Description              |
| ----------------------- | ------------------------ |
| `InsufficientDataError` | If not enough price data |

### calculate_macd

```
calculate_macd(
    token: str,
    fast_period: int = 12,
    slow_period: int = 26,
    signal_period: int = 9,
    timeframe: str = "1h",
) -> MACDResult
```

Calculate MACD for a token.

Parameters:

| Name            | Type  | Description                           | Default    |
| --------------- | ----- | ------------------------------------- | ---------- |
| `token`         | `str` | Token symbol (e.g., "WETH", "ETH")    | *required* |
| `fast_period`   | `int` | Fast EMA period (default 12)          | `12`       |
| `slow_period`   | `int` | Slow EMA period (default 26)          | `26`       |
| `signal_period` | `int` | Signal line EMA period (default 9)    | `9`        |
| `timeframe`     | `str` | OHLCV candle timeframe (default "1h") | `'1h'`     |

Returns:

| Type         | Description                                           |
| ------------ | ----------------------------------------------------- |
| `MACDResult` | MACDResult with macd_line, signal_line, and histogram |

Raises:

| Type                    | Description                   |
| ----------------------- | ----------------------------- |
| `InsufficientDataError` | If not enough historical data |
| `DataSourceError`       | If data cannot be fetched     |

Example

macd = await macd_calc.calculate_macd("WETH", fast=12, slow=26, signal=9)

#### Trading logic

if macd.histogram > 0:

# MACD above signal - bullish momentum

print("Bullish crossover") elif macd.histogram < 0:

# MACD below signal - bearish momentum

print("Bearish crossover")

### calculate

```
calculate(
    token: str, timeframe: str = "1h", **params: Any
) -> dict[str, float]
```

Calculate MACD (BaseIndicator protocol implementation).

Parameters:

| Name        | Type  | Description                                           | Default    |
| ----------- | ----- | ----------------------------------------------------- | ---------- |
| `token`     | `str` | Token symbol                                          | *required* |
| `timeframe` | `str` | OHLCV candle timeframe                                | `'1h'`     |
| `**params`  | `Any` | fast_period (12), slow_period (26), signal_period (9) | `{}`       |

Returns:

| Type               | Description                     |
| ------------------ | ------------------------------- |
| `dict[str, float]` | Dictionary with all MACD values |

## StochasticCalculator

## almanak.framework.data.indicators.StochasticCalculator

```
StochasticCalculator(ohlcv_provider: OHLCVProvider)
```

Stochastic Oscillator Calculator.

Calculates the Stochastic Oscillator with configurable %K and %D periods.

Default Parameters

- %K Period: 14 (industry standard)
- %D Period: 3 (smoothing period)

Attributes:

| Name             | Type | Description                                                 |
| ---------------- | ---- | ----------------------------------------------------------- |
| `ohlcv_provider` |      | Provider for OHLCV data (implements OHLCVProvider protocol) |

Example

provider = CoinGeckoOHLCVProvider() stoch_calc = StochasticCalculator(ohlcv_provider=provider)

stoch = await stoch_calc.calculate_stochastic("WETH", k_period=14, d_period=3) print(f"%K: {stoch.k_value:.2f}, %D: {stoch.d_value:.2f}")

if stoch.k_value < 20: print("Oversold territory")

Initialize the Stochastic Calculator.

Parameters:

| Name             | Type            | Description                                  | Default    |
| ---------------- | --------------- | -------------------------------------------- | ---------- |
| `ohlcv_provider` | `OHLCVProvider` | Provider implementing OHLCVProvider protocol | *required* |

### name

```
name: str
```

Return indicator name.

### min_data_points

```
min_data_points: int
```

Return minimum data points (k_period + d_period).

### calculate_stochastic_from_candles

```
calculate_stochastic_from_candles(
    candles: list[OHLCVCandle],
    k_period: int = 14,
    d_period: int = 3,
) -> StochasticResult
```

Calculate Stochastic Oscillator from OHLCV candles.

Formula: %K = 100 * (Close - Lowest Low) / (Highest High - Lowest Low) %D = SMA(%K, d_period)

Parameters:

| Name       | Type                | Description                                | Default    |
| ---------- | ------------------- | ------------------------------------------ | ---------- |
| `candles`  | `list[OHLCVCandle]` | List of OHLCVCandle objects (oldest first) | *required* |
| `k_period` | `int`               | Lookback period for %K (default 14)        | `14`       |
| `d_period` | `int`               | SMA period for %D (default 3)              | `3`        |

Returns:

| Type               | Description                               |
| ------------------ | ----------------------------------------- |
| `StochasticResult` | StochasticResult with k_value and d_value |

Raises:

| Type                    | Description               |
| ----------------------- | ------------------------- |
| `InsufficientDataError` | If not enough candle data |

### calculate_stochastic

```
calculate_stochastic(
    token: str,
    k_period: int = 14,
    d_period: int = 3,
    timeframe: str = "1h",
) -> StochasticResult
```

Calculate Stochastic Oscillator for a token.

Parameters:

| Name        | Type  | Description                           | Default    |
| ----------- | ----- | ------------------------------------- | ---------- |
| `token`     | `str` | Token symbol (e.g., "WETH", "ETH")    | *required* |
| `k_period`  | `int` | Lookback period for %K (default 14)   | `14`       |
| `d_period`  | `int` | SMA period for %D (default 3)         | `3`        |
| `timeframe` | `str` | OHLCV candle timeframe (default "1h") | `'1h'`     |

Returns:

| Type               | Description                                                  |
| ------------------ | ------------------------------------------------------------ |
| `StochasticResult` | StochasticResult with k_value and d_value (both 0-100 scale) |

Raises:

| Type                    | Description                   |
| ----------------------- | ----------------------------- |
| `InsufficientDataError` | If not enough historical data |
| `DataSourceError`       | If data cannot be fetched     |

Example

stoch = await stoch_calc.calculate_stochastic("WETH", k_period=14, d_period=3)

#### Trading logic

if stoch.k_value < 20: print("Oversold - potential buy signal") elif stoch.k_value > 80: print("Overbought - potential sell signal")

#### Crossover signals

if stoch.k_value > stoch.d_value: print("Bullish - %K above %D")

### calculate

```
calculate(
    token: str, timeframe: str = "1h", **params: Any
) -> dict[str, float]
```

Calculate Stochastic (BaseIndicator protocol implementation).

Parameters:

| Name        | Type  | Description                                 | Default    |
| ----------- | ----- | ------------------------------------------- | ---------- |
| `token`     | `str` | Token symbol                                | *required* |
| `timeframe` | `str` | OHLCV candle timeframe                      | `'1h'`     |
| `**params`  | `Any` | k_period (default 14), d_period (default 3) | `{}`       |

Returns:

| Type               | Description                         |
| ------------------ | ----------------------------------- |
| `dict[str, float]` | Dictionary with k_value and d_value |

## ATRCalculator

## almanak.framework.data.indicators.ATRCalculator

```
ATRCalculator(ohlcv_provider: OHLCVProvider)
```

ATR (Average True Range) Calculator.

Calculates ATR with configurable period using Wilder's smoothing method.

Default Parameters

- Period: 14 (industry standard)

Attributes:

| Name             | Type | Description                                                 |
| ---------------- | ---- | ----------------------------------------------------------- |
| `ohlcv_provider` |      | Provider for OHLCV data (implements OHLCVProvider protocol) |

Example

provider = CoinGeckoOHLCVProvider() atr_calc = ATRCalculator(ohlcv_provider=provider)

atr = await atr_calc.calculate_atr("WETH", period=14, timeframe="4h") print(f"ATR: ${atr:.2f}")

### Position sizing

if atr > 100: print("High volatility - reduce position size")

Initialize the ATR Calculator.

Parameters:

| Name             | Type            | Description                                  | Default    |
| ---------------- | --------------- | -------------------------------------------- | ---------- |
| `ohlcv_provider` | `OHLCVProvider` | Provider implementing OHLCVProvider protocol | *required* |

### name

```
name: str
```

Return indicator name.

### min_data_points

```
min_data_points: int
```

Return minimum data points (period + 1).

### calculate_atr_from_candles

```
calculate_atr_from_candles(
    candles: list[OHLCVCandle], period: int = 14
) -> float
```

Calculate ATR from OHLCV candles using Wilder's smoothing.

Wilder's ATR uses a modified EMA:

- First ATR = Simple average of first N True Ranges
- Subsequent ATR = ((Prior ATR * (N-1)) + Current TR) / N

Parameters:

| Name      | Type                | Description                                | Default    |
| --------- | ------------------- | ------------------------------------------ | ---------- |
| `candles` | `list[OHLCVCandle]` | List of OHLCVCandle objects (oldest first) | *required* |
| `period`  | `int`               | ATR period (default 14)                    | `14`       |

Returns:

| Type    | Description |
| ------- | ----------- |
| `float` | ATR value   |

Raises:

| Type                    | Description               |
| ----------------------- | ------------------------- |
| `InsufficientDataError` | If not enough candle data |

### calculate_atr_from_prices

```
calculate_atr_from_prices(
    prices: list, period: int = 14
) -> float
```

Calculate ATR from close prices only using Wilder's smoothing.

When only close prices are available (e.g., synthetic Monte Carlo paths), True Range is approximated as |close[i] - close[i-1]|. This is mathematically equivalent to ATR computed from OHLCV data where open = high = low = close for each candle.

Parameters:

| Name     | Type   | Description                                              | Default    |
| -------- | ------ | -------------------------------------------------------- | ---------- |
| `prices` | `list` | List of close prices (oldest first), as Decimal or float | *required* |
| `period` | `int`  | ATR period (default 14)                                  | `14`       |

Returns:

| Type    | Description       |
| ------- | ----------------- |
| `float` | ATR value (float) |

Raises:

| Type                    | Description              |
| ----------------------- | ------------------------ |
| `InsufficientDataError` | If not enough price data |
| `ValueError`            | If period < 1            |

### calculate_atr

```
calculate_atr(
    token: str, period: int = 14, timeframe: str = "1h"
) -> float
```

Calculate ATR for a token.

Parameters:

| Name        | Type  | Description                           | Default    |
| ----------- | ----- | ------------------------------------- | ---------- |
| `token`     | `str` | Token symbol (e.g., "WETH", "ETH")    | *required* |
| `period`    | `int` | ATR period (default 14)               | `14`       |
| `timeframe` | `str` | OHLCV candle timeframe (default "1h") | `'1h'`     |

Returns:

| Type    | Description                                      |
| ------- | ------------------------------------------------ |
| `float` | ATR value (in the same units as the token price) |

Raises:

| Type                    | Description                   |
| ----------------------- | ----------------------------- |
| `InsufficientDataError` | If not enough historical data |
| `DataSourceError`       | If data cannot be fetched     |

Example

atr = await atr_calc.calculate_atr("WETH", period=14, timeframe="4h")

#### Stop-loss calculation

current_price = 2500 stop_loss = current_price - (2 * atr) print(f"Stop loss at ${stop_loss:.2f} (2 ATR below)")

#### Position sizing with 1% risk

risk_per_trade = 10000 * 0.01 # $100 position_size = risk_per_trade / atr print(f"Position size: {position_size:.2f} units")

### calculate

```
calculate(
    token: str, timeframe: str = "1h", **params: Any
) -> dict[str, float]
```

Calculate ATR (BaseIndicator protocol implementation).

Parameters:

| Name        | Type  | Description            | Default    |
| ----------- | ----- | ---------------------- | ---------- |
| `token`     | `str` | Token symbol           | *required* |
| `timeframe` | `str` | OHLCV candle timeframe | `'1h'`     |
| `**params`  | `Any` | period (default 14)    | `{}`       |

Returns:

| Type               | Description               |
| ------------------ | ------------------------- |
| `dict[str, float]` | Dictionary with atr value |

## MovingAverageCalculator

## almanak.framework.data.indicators.MovingAverageCalculator

```
MovingAverageCalculator(ohlcv_provider: OHLCVProvider)
```

Calculator for Simple, Exponential, and Weighted Moving Averages.

Provides three types of moving averages commonly used in technical analysis:

- SMA: Simple average of last N closing prices
- EMA: Exponential moving average with configurable smoothing
- WMA: Weighted moving average with linear weighting

Attributes:

| Name             | Type | Description                                                 |
| ---------------- | ---- | ----------------------------------------------------------- |
| `ohlcv_provider` |      | Provider for OHLCV data (implements OHLCVProvider protocol) |

Example

provider = CoinGeckoOHLCVProvider() ma_calc = MovingAverageCalculator(ohlcv_provider=provider)

sma = await ma_calc.sma("WETH", period=20, timeframe="4h") ema = await ma_calc.ema("WETH", period=12, timeframe="1h")

Initialize the Moving Average Calculator.

Parameters:

| Name             | Type            | Description                                  | Default    |
| ---------------- | --------------- | -------------------------------------------- | ---------- |
| `ohlcv_provider` | `OHLCVProvider` | Provider implementing OHLCVProvider protocol | *required* |

### name

```
name: str
```

Return indicator name.

### min_data_points

```
min_data_points: int
```

Return minimum data points (depends on period, default 20).

### calculate_sma_from_prices

```
calculate_sma_from_prices(
    close_prices: list[Decimal], period: int
) -> float
```

Calculate SMA from a list of close prices.

Parameters:

| Name           | Type            | Description                           | Default    |
| -------------- | --------------- | ------------------------------------- | ---------- |
| `close_prices` | `list[Decimal]` | List of closing prices (oldest first) | *required* |
| `period`       | `int`           | Number of periods for the average     | *required* |

Returns:

| Type    | Description                 |
| ------- | --------------------------- |
| `float` | Simple Moving Average value |

Raises:

| Type                    | Description              |
| ----------------------- | ------------------------ |
| `InsufficientDataError` | If not enough price data |

### calculate_ema_from_prices

```
calculate_ema_from_prices(
    close_prices: list[Decimal],
    period: int,
    smoothing: float = 2.0,
) -> float
```

Calculate EMA from a list of close prices.

Uses the formula: EMA = Price(t) * k + EMA(y) * (1 - k) where k = smoothing / (period + 1)

Parameters:

| Name           | Type            | Description                                     | Default    |
| -------------- | --------------- | ----------------------------------------------- | ---------- |
| `close_prices` | `list[Decimal]` | List of closing prices (oldest first)           | *required* |
| `period`       | `int`           | Number of periods for the average               | *required* |
| `smoothing`    | `float`         | Smoothing factor (default 2.0 for standard EMA) | `2.0`      |

Returns:

| Type    | Description                      |
| ------- | -------------------------------- |
| `float` | Exponential Moving Average value |

Raises:

| Type                    | Description              |
| ----------------------- | ------------------------ |
| `InsufficientDataError` | If not enough price data |

### calculate_wma_from_prices

```
calculate_wma_from_prices(
    close_prices: list[Decimal], period: int
) -> float
```

Calculate WMA from a list of close prices.

Weighted Moving Average: More recent prices have higher weights. Weight for position i (1-indexed from oldest): i / sum(1..period)

Parameters:

| Name           | Type            | Description                           | Default    |
| -------------- | --------------- | ------------------------------------- | ---------- |
| `close_prices` | `list[Decimal]` | List of closing prices (oldest first) | *required* |
| `period`       | `int`           | Number of periods for the average     | *required* |

Returns:

| Type    | Description                   |
| ------- | ----------------------------- |
| `float` | Weighted Moving Average value |

Raises:

| Type                    | Description              |
| ----------------------- | ------------------------ |
| `InsufficientDataError` | If not enough price data |

### sma

```
sma(
    token: str, period: int = 20, timeframe: str = "1h"
) -> float
```

Calculate Simple Moving Average for a token.

SMA is the unweighted mean of the last N closing prices. Commonly used periods: 10, 20, 50, 100, 200

Parameters:

| Name        | Type  | Description                           | Default    |
| ----------- | ----- | ------------------------------------- | ---------- |
| `token`     | `str` | Token symbol (e.g., "WETH", "ETH")    | *required* |
| `period`    | `int` | Number of periods (default 20)        | `20`       |
| `timeframe` | `str` | OHLCV candle timeframe (default "1h") | `'1h'`     |

Returns:

| Type    | Description        |
| ------- | ------------------ |
| `float` | SMA value as float |

Raises:

| Type                    | Description                   |
| ----------------------- | ----------------------------- |
| `InsufficientDataError` | If not enough historical data |
| `DataSourceError`       | If data cannot be fetched     |

Example

sma_20 = await ma_calc.sma("WETH", period=20, timeframe="1h") sma_200 = await ma_calc.sma("WETH", period=200, timeframe="1d")

### ema

```
ema(
    token: str,
    period: int = 12,
    timeframe: str = "1h",
    smoothing: float = 2.0,
) -> float
```

Calculate Exponential Moving Average for a token.

EMA gives more weight to recent prices using exponential decay. Commonly used periods: 12, 26 (for MACD), 9, 21

Parameters:

| Name        | Type    | Description                           | Default    |
| ----------- | ------- | ------------------------------------- | ---------- |
| `token`     | `str`   | Token symbol (e.g., "WETH", "ETH")    | *required* |
| `period`    | `int`   | Number of periods (default 12)        | `12`       |
| `timeframe` | `str`   | OHLCV candle timeframe (default "1h") | `'1h'`     |
| `smoothing` | `float` | Smoothing factor (default 2.0)        | `2.0`      |

Returns:

| Type    | Description        |
| ------- | ------------------ |
| `float` | EMA value as float |

Raises:

| Type                    | Description                   |
| ----------------------- | ----------------------------- |
| `InsufficientDataError` | If not enough historical data |
| `DataSourceError`       | If data cannot be fetched     |

Example

ema_12 = await ma_calc.ema("WETH", period=12, timeframe="1h") ema_26 = await ma_calc.ema("WETH", period=26, timeframe="1h")

### wma

```
wma(
    token: str, period: int = 20, timeframe: str = "1h"
) -> float
```

Calculate Weighted Moving Average for a token.

WMA assigns higher weights to more recent data points linearly. Most recent price has weight N, oldest has weight 1.

Parameters:

| Name        | Type  | Description                           | Default    |
| ----------- | ----- | ------------------------------------- | ---------- |
| `token`     | `str` | Token symbol (e.g., "WETH", "ETH")    | *required* |
| `period`    | `int` | Number of periods (default 20)        | `20`       |
| `timeframe` | `str` | OHLCV candle timeframe (default "1h") | `'1h'`     |

Returns:

| Type    | Description        |
| ------- | ------------------ |
| `float` | WMA value as float |

Raises:

| Type                    | Description                   |
| ----------------------- | ----------------------------- |
| `InsufficientDataError` | If not enough historical data |
| `DataSourceError`       | If data cannot be fetched     |

Example

wma_20 = await ma_calc.wma("WETH", period=20, timeframe="1h")

### calculate

```
calculate(
    token: str, timeframe: str = "1h", **params: Any
) -> dict[str, float]
```

Calculate moving average (BaseIndicator protocol implementation).

Parameters:

| Name        | Type  | Description                                    | Default    |
| ----------- | ----- | ---------------------------------------------- | ---------- |
| `token`     | `str` | Token symbol                                   | *required* |
| `timeframe` | `str` | OHLCV candle timeframe                         | `'1h'`     |
| `**params`  | `Any` | Must include 'type' (sma/ema/wma) and 'period' | `{}`       |

Returns:

| Type               | Description                      |
| ------------------ | -------------------------------- |
| `dict[str, float]` | Dictionary with calculated value |

Example

result = await ma_calc.calculate("WETH", type="sma", period=20)

#### {"sma": 2500.0}

## Result Types

### BollingerBandsResult

## almanak.framework.data.indicators.BollingerBandsResult

```
BollingerBandsResult(
    upper_band: float,
    middle_band: float,
    lower_band: float,
    bandwidth: float,
    percent_b: float,
)
```

Result from Bollinger Bands calculation.

Attributes:

| Name          | Type    | Description                                                                                                                                                                                                                                                                                                                                                                                                         |
| ------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `upper_band`  | `float` | Upper band (SMA + std_dev * multiplier)                                                                                                                                                                                                                                                                                                                                                                             |
| `middle_band` | `float` | Middle band (Simple Moving Average)                                                                                                                                                                                                                                                                                                                                                                                 |
| `lower_band`  | `float` | Lower band (SMA - std_dev * multiplier)                                                                                                                                                                                                                                                                                                                                                                             |
| `bandwidth`   | `float` | Band width as ratio ((upper - lower) / middle). Typical ranges for crypto assets (1h candles, 20-period, 2 std dev): - Squeeze: < 0.02 (low volatility, breakout likely) - Normal: 0.02 - 0.06 (typical trading range) - Expansion: > 0.06 (high volatility, trend in progress) These are starting guidelines; actual thresholds vary by asset and timeframe. Shorter timeframes and lower-cap assets trend higher. |
| `percent_b`   | `float` | Price position relative to bands (0 = lower, 1 = upper). Values above 1.0 indicate price above the upper band; values below 0.0 indicate price below the lower band.                                                                                                                                                                                                                                                |

### to_dict

```
to_dict() -> dict[str, float]
```

Convert to dictionary format.

### MACDResult

## almanak.framework.data.indicators.MACDResult

```
MACDResult(
    macd_line: float, signal_line: float, histogram: float
)
```

Result from MACD calculation.

Attributes:

| Name          | Type    | Description                              |
| ------------- | ------- | ---------------------------------------- |
| `macd_line`   | `float` | MACD line (fast EMA - slow EMA)          |
| `signal_line` | `float` | Signal line (EMA of MACD line)           |
| `histogram`   | `float` | MACD histogram (MACD line - signal line) |

### to_dict

```
to_dict() -> dict[str, float]
```

Convert to dictionary format.

### StochasticResult

## almanak.framework.data.indicators.StochasticResult

```
StochasticResult(k_value: float, d_value: float)
```

Result from Stochastic Oscillator calculation.

Attributes:

| Name      | Type    | Description                                            |
| --------- | ------- | ------------------------------------------------------ |
| `k_value` | `float` | %K (fast stochastic) - current position in price range |
| `d_value` | `float` | %D (slow stochastic) - SMA of %K                       |

### to_dict

```
to_dict() -> dict[str, float]
```

Convert to dictionary format.

## OHLCV Data

### OHLCVData

## almanak.framework.data.indicators.OHLCVData

```
OHLCVData(
    timestamp: datetime,
    open: Decimal,
    high: Decimal,
    low: Decimal,
    close: Decimal,
    volume: Decimal | None = None,
)
```

OHLCV candlestick data point.

Attributes:

| Name        | Type       | Description      |
| ----------- | ---------- | ---------------- |
| `timestamp` | `datetime` | Candle open time |
| `open`      | `Decimal`  | Opening price    |
| `high`      | `Decimal`  | Highest price    |
| `low`       | `Decimal`  | Lowest price     |
| `close`     | `Decimal`  | Closing price    |
| `volume`    | \`Decimal  | None\`           |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

### CoinGeckoOHLCVProvider

## almanak.framework.data.indicators.CoinGeckoOHLCVProvider

```
CoinGeckoOHLCVProvider(
    api_key: str = "",
    cache_ttl: int = 300,
    request_timeout: float = 30.0,
)
```

CoinGecko OHLCV data provider implementing the OHLCVProvider protocol.

Fetches historical OHLCV data from CoinGecko API for technical analysis. Implements the OHLCVProvider protocol from src/data/interfaces.py.

Note on timeframe support

CoinGecko's OHLC API has granularity limitations based on the day range:

- 1-2 days: Returns 30-minute candles (supports 1m, 5m, 15m approximation)
- 3-30 days: Returns 4-hour candles (supports 1h, 4h)
- 31+ days: Returns daily candles (supports 1d)

For finer timeframes (1m, 5m, 15m), we request 1-2 day ranges and return the 30-minute candles. Strategies requiring exact minute-level candles should use a more granular data source.

Attributes:

| Name              | Type | Description                                             |
| ----------------- | ---- | ------------------------------------------------------- |
| `api_key`         |      | Optional CoinGecko API key (uses pro API if provided)   |
| `cache_ttl`       |      | Cache time-to-live in seconds (default 300 = 5 minutes) |
| `request_timeout` |      | HTTP request timeout in seconds (default 30)            |

Example

provider = CoinGeckoOHLCVProvider(api_key="optional-key") candles = await provider.get_ohlcv("WETH", timeframe="1h", limit=100) print(f"Got {len(candles)} candles") for candle in candles\[-3:\]: print(f" {candle.timestamp}: close={candle.close}")

Initialize the CoinGecko OHLCV provider.

Parameters:

| Name              | Type    | Description                                             | Default |
| ----------------- | ------- | ------------------------------------------------------- | ------- |
| `api_key`         | `str`   | Optional CoinGecko API key. If provided, uses pro API.  | `''`    |
| `cache_ttl`       | `int`   | Cache time-to-live in seconds. Default 300 (5 minutes). | `300`   |
| `request_timeout` | `float` | HTTP request timeout in seconds. Default 30.            | `30.0`  |

### supported_timeframes

```
supported_timeframes: list[str]
```

Return the list of timeframes this provider supports.

Returns:

| Name   | Type        | Description                                                           |
| ------ | ----------- | --------------------------------------------------------------------- |
|        | `list[str]` | List of supported timeframe strings.                                  |
| `Note` | `list[str]` | 1m, 5m, 15m return 30-minute candles (CoinGecko's finest granularity) |

### close

```
close() -> None
```

Close the HTTP session.

### get_ohlcv

```
get_ohlcv(
    token: str,
    quote: str = "USD",
    timeframe: str = "1h",
    limit: int = 100,
) -> list[OHLCVCandle]
```

Get OHLCV data for a token.

Uses the CoinGecko OHLC endpoint for historical candlestick data. Implements the OHLCVProvider protocol.

Parameters:

| Name        | Type  | Description                                                                                                                                  | Default    |
| ----------- | ----- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `token`     | `str` | Token symbol (e.g., "WETH", "ETH")                                                                                                           | *required* |
| `quote`     | `str` | Quote currency (default "USD")                                                                                                               | `'USD'`    |
| `timeframe` | `str` | Candle timeframe. Supported: "1m", "5m", "15m", "1h", "4h", "1d" Note: 1m, 5m, 15m return 30-minute candles (CoinGecko's finest granularity) | `'1h'`     |
| `limit`     | `int` | Number of candles to fetch                                                                                                                   | `100`      |

Returns:

| Type                | Description                                                               |
| ------------------- | ------------------------------------------------------------------------- |
| `list[OHLCVCandle]` | List of OHLCVCandle objects sorted by timestamp ascending.                |
| `list[OHLCVCandle]` | Volume is always None as CoinGecko OHLC API does not provide volume data. |

Raises:

| Type                    | Description                   |
| ----------------------- | ----------------------------- |
| `DataSourceUnavailable` | If data cannot be fetched     |
| `ValueError`            | If timeframe is not supported |

### get_health_metrics

```
get_health_metrics() -> dict[str, Any]
```

Get health metrics for observability.

### clear_cache

```
clear_cache() -> None
```

Clear the OHLCV cache.

### __aenter__

```
__aenter__() -> CoinGeckoOHLCVProvider
```

Async context manager entry.

### __aexit__

```
__aexit__(exc_type: Any, exc_val: Any, exc_tb: Any) -> None
```

Async context manager exit.

## Registry

### IndicatorRegistry

## almanak.framework.data.indicators.IndicatorRegistry

Registry for technical indicator discovery and instantiation.

This class provides a centralized registry for indicator classes, enabling dynamic lookup and factory-style creation.

The registry uses class methods so it can be used without instantiation, acting as a singleton-like pattern for global indicator registration.

Example

### Register an indicator

IndicatorRegistry.register("rsi", RSICalculator) IndicatorRegistry.register("bollinger", BollingerBandsCalculator)

### List all registered indicators

indicators = IndicatorRegistry.list_all()

### ['rsi', 'bollinger']

### Get an indicator class by name

RSIClass = IndicatorRegistry.get("rsi") if RSIClass: calculator = RSIClass(ohlcv_provider=provider)

### Check if an indicator is registered

if IndicatorRegistry.has("macd"): MACDClass = IndicatorRegistry.get("macd")

### register

```
register(
    name: str,
    indicator_class: type,
    metadata: dict[str, Any] | None = None,
) -> None
```

Register an indicator class.

Parameters:

| Name              | Type             | Description                                      | Default                                                            |
| ----------------- | ---------------- | ------------------------------------------------ | ------------------------------------------------------------------ |
| `name`            | `str`            | Unique name for the indicator (case-insensitive) | *required*                                                         |
| `indicator_class` | `type`           | The indicator class to register                  | *required*                                                         |
| `metadata`        | \`dict[str, Any] | None\`                                           | Optional metadata about the indicator (description, version, etc.) |

Raises:

| Type         | Description                   |
| ------------ | ----------------------------- |
| `ValueError` | If name is already registered |

Example

IndicatorRegistry.register("rsi", RSICalculator, metadata={ "description": "Relative Strength Index", "version": "1.0.0", "category": "momentum", })

### get

```
get(name: str) -> type | None
```

Get an indicator class by name.

Parameters:

| Name   | Type  | Description                       | Default    |
| ------ | ----- | --------------------------------- | ---------- |
| `name` | `str` | Indicator name (case-insensitive) | *required* |

Returns:

| Type   | Description |
| ------ | ----------- |
| \`type | None\`      |

Example

RSIClass = IndicatorRegistry.get("rsi") if RSIClass: calculator = RSIClass(ohlcv_provider=provider)

### has

```
has(name: str) -> bool
```

Check if an indicator is registered.

Parameters:

| Name   | Type  | Description                       | Default    |
| ------ | ----- | --------------------------------- | ---------- |
| `name` | `str` | Indicator name (case-insensitive) | *required* |

Returns:

| Type   | Description                         |
| ------ | ----------------------------------- |
| `bool` | True if registered, False otherwise |

### list_all

```
list_all() -> list[str]
```

List all registered indicator names.

Returns:

| Type        | Description                               |
| ----------- | ----------------------------------------- |
| `list[str]` | Sorted list of registered indicator names |

Example

indicators = IndicatorRegistry.list_all()

#### ['atr', 'bollinger', 'macd', 'rsi', 'stochastic']

### get_metadata

```
get_metadata(name: str) -> dict[str, Any]
```

Get metadata for an indicator.

Parameters:

| Name   | Type  | Description                       | Default    |
| ------ | ----- | --------------------------------- | ---------- |
| `name` | `str` | Indicator name (case-insensitive) | *required* |

Returns:

| Type             | Description                                     |
| ---------------- | ----------------------------------------------- |
| `dict[str, Any]` | Metadata dictionary, or empty dict if not found |

### unregister

```
unregister(name: str) -> bool
```

Unregister an indicator.

Parameters:

| Name   | Type  | Description                       | Default    |
| ------ | ----- | --------------------------------- | ---------- |
| `name` | `str` | Indicator name (case-insensitive) | *required* |

Returns:

| Type   | Description                              |
| ------ | ---------------------------------------- |
| `bool` | True if unregistered, False if not found |

### clear

```
clear() -> None
```

Clear all registered indicators.

Primarily used for testing.

### create

```
create(
    name: str, *args: Any, **kwargs: Any
) -> BaseIndicator | None
```

Factory method to create an indicator instance.

Parameters:

| Name       | Type  | Description                                    | Default    |
| ---------- | ----- | ---------------------------------------------- | ---------- |
| `name`     | `str` | Indicator name (case-insensitive)              | *required* |
| `*args`    | `Any` | Positional arguments for indicator constructor | `()`       |
| `**kwargs` | `Any` | Keyword arguments for indicator constructor    | `{}`       |

Returns:

| Type            | Description |
| --------------- | ----------- |
| \`BaseIndicator | None\`      |

Example

calculator = IndicatorRegistry.create("rsi", ohlcv_provider=provider) if calculator: rsi = await calculator.calculate("WETH", timeframe="1h", period=14)

# Intents

The intent vocabulary - high-level descriptions of what a strategy wants to do. The framework compiles these into executable transactions.

## Intent

Factory class for creating intents.

## almanak.framework.intents.Intent

Factory class for creating intents with a fluent API.

This class provides static factory methods for creating intents, making strategy code more readable and ergonomic.

Example

### Instead of:

intent = SwapIntent(from_token="USDC", to_token="ETH", amount_usd=Decimal("1000"))

### You can write:

intent = Intent.swap(from_token="USDC", to_token="ETH", amount_usd=Decimal("1000"))

### swap

```
swap(
    from_token: str,
    to_token: str,
    amount_usd: Decimal | None = None,
    amount: ChainedAmount | None = None,
    max_slippage: Decimal = Decimal("0.005"),
    max_price_impact: Decimal | None = None,
    protocol: str | None = None,
    chain: str | None = None,
    destination_chain: str | None = None,
) -> SwapIntent
```

Create a swap intent.

Parameters:

| Name                | Type            | Description                                 | Default                                                                                                                                                                                |
| ------------------- | --------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `from_token`        | `str`           | Symbol or address of the token to swap from | *required*                                                                                                                                                                             |
| `to_token`          | `str`           | Symbol or address of the token to swap to   | *required*                                                                                                                                                                             |
| `amount_usd`        | \`Decimal       | None\`                                      | Amount to swap in USD terms                                                                                                                                                            |
| `amount`            | \`ChainedAmount | None\`                                      | Amount to swap in token terms, or "all" to use previous step output                                                                                                                    |
| `max_slippage`      | `Decimal`       | Maximum acceptable slippage (default 0.5%)  | `Decimal('0.005')`                                                                                                                                                                     |
| `max_price_impact`  | \`Decimal       | None\`                                      | Maximum acceptable price impact vs oracle price (e.g., 0.50 = 50%). Compilation fails if quoter/oracle deviation exceeds this. Defaults to None (uses compiler config default of 30%). |
| `protocol`          | \`str           | None\`                                      | Preferred protocol for the swap                                                                                                                                                        |
| `chain`             | \`str           | None\`                                      | Source chain for execution (defaults to strategy's primary chain)                                                                                                                      |
| `destination_chain` | \`str           | None\`                                      | Destination chain for cross-chain swaps (None for same-chain)                                                                                                                          |

Returns:

| Name         | Type         | Description             |
| ------------ | ------------ | ----------------------- |
| `SwapIntent` | `SwapIntent` | The created swap intent |

Example

#### Swap $1000 worth of USDC to ETH

intent = Intent.swap("USDC", "ETH", amount_usd=Decimal("1000"))

#### Swap 0.5 ETH to USDC on Base

intent = Intent.swap("ETH", "USDC", amount=Decimal("0.5"), chain="base")

#### Swap all ETH from previous step output

intent = Intent.swap("ETH", "USDC", amount="all", chain="base")

#### Cross-chain swap: Base USDC -> Arbitrum WETH via Enso

intent = Intent.swap("USDC", "WETH", amount_usd=Decimal("1000"), chain="base", destination_chain="arbitrum", protocol="enso")

### lp_open

```
lp_open(
    pool: str,
    amount0: Decimal,
    amount1: Decimal,
    range_lower: Decimal,
    range_upper: Decimal,
    protocol: str = "uniswap_v3",
    chain: str | None = None,
    protocol_params: dict[str, Any] | None = None,
) -> LPOpenIntent
```

Create an LP open intent.

Parameters:

| Name              | Type             | Description                                  | Default                                                                          |
| ----------------- | ---------------- | -------------------------------------------- | -------------------------------------------------------------------------------- |
| `pool`            | `str`            | Pool address or identifier                   | *required*                                                                       |
| `amount0`         | `Decimal`        | Amount of token0 to provide                  | *required*                                                                       |
| `amount1`         | `Decimal`        | Amount of token1 to provide                  | *required*                                                                       |
| `range_lower`     | `Decimal`        | Lower price bound for concentrated liquidity | *required*                                                                       |
| `range_upper`     | `Decimal`        | Upper price bound for concentrated liquidity | *required*                                                                       |
| `protocol`        | `str`            | LP protocol (default "uniswap_v3")           | `'uniswap_v3'`                                                                   |
| `chain`           | \`str            | None\`                                       | Target chain for execution (defaults to strategy's primary chain)                |
| `protocol_params` | \`dict[str, Any] | None\`                                       | Optional protocol-specific parameters (e.g., {"bin_range": 10} for TraderJoe V2) |

Returns:

| Name           | Type           | Description                |
| -------------- | -------------- | -------------------------- |
| `LPOpenIntent` | `LPOpenIntent` | The created LP open intent |

Example

#### Open an ETH/USDC LP position around the current price

intent = Intent.lp_open( pool="0x8ad...", amount0=Decimal("1"), # 1 ETH amount1=Decimal("2000"), # 2000 USDC range_lower=Decimal("1800"), range_upper=Decimal("2200"), )

### lp_close

```
lp_close(
    position_id: str,
    pool: str | None = None,
    collect_fees: bool = True,
    protocol: str = "uniswap_v3",
    chain: str | None = None,
    protocol_params: dict[str, Any] | None = None,
) -> LPCloseIntent
```

Create an LP close intent.

Parameters:

| Name              | Type             | Description                                        | Default                                                                                                                   |
| ----------------- | ---------------- | -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| `position_id`     | `str`            | Identifier of the position to close                | *required*                                                                                                                |
| `pool`            | \`str            | None\`                                             | Pool address (optional, for validation)                                                                                   |
| `collect_fees`    | `bool`           | Whether to collect accumulated fees (default True) | `True`                                                                                                                    |
| `protocol`        | `str`            | LP protocol (default "uniswap_v3")                 | `'uniswap_v3'`                                                                                                            |
| `chain`           | \`str            | None\`                                             | Target chain for execution (defaults to strategy's primary chain)                                                         |
| `protocol_params` | \`dict[str, Any] | None\`                                             | Optional protocol-specific parameters (e.g., V4 requires liquidity, currency0, currency1 from an on-chain position query) |

Returns:

| Name            | Type            | Description                 |
| --------------- | --------------- | --------------------------- |
| `LPCloseIntent` | `LPCloseIntent` | The created LP close intent |

Example

#### Close an LP position and collect fees

intent = Intent.lp_close(position_id="12345")

#### Close without collecting fees

intent = Intent.lp_close(position_id="12345", collect_fees=False)

### collect_fees

```
collect_fees(
    pool: str,
    protocol: str = "traderjoe_v2",
    chain: str | None = None,
    protocol_params: dict[str, Any] | None = None,
) -> CollectFeesIntent
```

Create a collect fees intent to harvest LP fees without closing the position.

Parameters:

| Name              | Type             | Description                                              | Default                                                                                  |
| ----------------- | ---------------- | -------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `pool`            | `str`            | Pool identifier (e.g., "WAVAX/USDC/20" for TraderJoe V2) | *required*                                                                               |
| `protocol`        | `str`            | LP protocol (default "traderjoe_v2")                     | `'traderjoe_v2'`                                                                         |
| `chain`           | \`str            | None\`                                                   | Target chain for execution (defaults to strategy's primary chain)                        |
| `protocol_params` | \`dict[str, Any] | None\`                                                   | Optional protocol-specific parameters. For Uniswap V4: position_id, currency0, currency1 |

Returns:

| Name                | Type                | Description                     |
| ------------------- | ------------------- | ------------------------------- |
| `CollectFeesIntent` | `CollectFeesIntent` | The created collect fees intent |

Example

#### Collect fees from a TraderJoe V2 WAVAX/USDC LP position

intent = Intent.collect_fees(pool="WAVAX/USDC/20", protocol="traderjoe_v2")

### borrow

```
borrow(
    protocol: str,
    collateral_token: str,
    collateral_amount: ChainedAmount,
    borrow_token: str,
    borrow_amount: Decimal,
    interest_rate_mode: InterestRateMode | None = None,
    market_id: str | None = None,
    chain: str | None = None,
) -> BorrowIntent
```

Create a borrow intent.

Parameters:

| Name                 | Type               | Description                                                       | Default                                                                                                                                                                                |
| -------------------- | ------------------ | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocol`           | `str`              | Lending protocol (e.g., "aave_v3", "morpho_blue")                 | *required*                                                                                                                                                                             |
| `collateral_token`   | `str`              | Token to use as collateral                                        | *required*                                                                                                                                                                             |
| `collateral_amount`  | `ChainedAmount`    | Amount of collateral to supply, or "all" for previous step output | *required*                                                                                                                                                                             |
| `borrow_token`       | `str`              | Token to borrow                                                   | *required*                                                                                                                                                                             |
| `borrow_amount`      | `Decimal`          | Amount to borrow                                                  | *required*                                                                                                                                                                             |
| `interest_rate_mode` | \`InterestRateMode | None\`                                                            | Interest rate mode for Aave ('variable' only, stable is deprecated). Only applies to protocols that support rate mode selection. For Aave V3, defaults to 'variable' if not specified. |
| `market_id`          | \`str              | None\`                                                            | Market identifier for isolated lending protocols (e.g., Morpho Blue). Required for morpho/morpho_blue, ignored for aave_v3.                                                            |
| `chain`              | \`str              | None\`                                                            | Target chain for execution (defaults to strategy's primary chain)                                                                                                                      |

Returns:

| Name           | Type           | Description               |
| -------------- | -------------- | ------------------------- |
| `BorrowIntent` | `BorrowIntent` | The created borrow intent |

Example

#### Supply ETH as collateral and borrow USDC on Arbitrum with variable rate

intent = Intent.borrow( protocol="aave_v3", collateral_token="ETH", collateral_amount=Decimal("1"), borrow_token="USDC", borrow_amount=Decimal("1500"), interest_rate_mode="variable", chain="arbitrum", )

#### Borrow on Morpho Blue (requires market_id)

intent = Intent.borrow( protocol="morpho_blue", collateral_token="wstETH", collateral_amount=Decimal("0"), # Already supplied borrow_token="USDC", borrow_amount=Decimal("1500"), market_id="0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc", chain="ethereum", )

### repay

```
repay(
    protocol: str,
    token: str,
    amount: ChainedAmount,
    repay_full: bool = False,
    interest_rate_mode: InterestRateMode | None = None,
    market_id: str | None = None,
    chain: str | None = None,
) -> RepayIntent
```

Create a repay intent.

Parameters:

| Name                 | Type               | Description                                           | Default                                                                                                                                                   |
| -------------------- | ------------------ | ----------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocol`           | `str`              | Lending protocol (e.g., "aave_v3", "morpho_blue")     | *required*                                                                                                                                                |
| `token`              | `str`              | Token to repay                                        | *required*                                                                                                                                                |
| `amount`             | `ChainedAmount`    | Amount to repay, or "all" to use previous step output | *required*                                                                                                                                                |
| `repay_full`         | `bool`             | If True, repay the full outstanding debt              | `False`                                                                                                                                                   |
| `interest_rate_mode` | \`InterestRateMode | None\`                                                | Interest rate mode for protocols that support it. Aave V3: 'variable' (default). Stable rate is deprecated. Must match the rate mode used when borrowing. |
| `market_id`          | \`str              | None\`                                                | Market identifier for isolated lending protocols (e.g., Morpho Blue). Required for morpho/morpho_blue, ignored for aave_v3.                               |
| `chain`              | \`str              | None\`                                                | Target chain for execution (defaults to strategy's primary chain)                                                                                         |

Returns:

| Name          | Type          | Description              |
| ------------- | ------------- | ------------------------ |
| `RepayIntent` | `RepayIntent` | The created repay intent |

Example

#### Repay 500 USDC on Aave (variable rate)

intent = Intent.repay( protocol="aave_v3", token="USDC", amount=Decimal("500"), interest_rate_mode="variable", )

#### Repay full debt on Morpho Blue

intent = Intent.repay( protocol="morpho_blue", token="USDC", amount=Decimal("0"), # Ignored when repay_full=True repay_full=True, market_id="0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc", )

### supply

```
supply(
    protocol: str,
    token: str,
    amount: ChainedAmount,
    use_as_collateral: bool = True,
    market_id: str | None = None,
    chain: str | None = None,
) -> SupplyIntent
```

Create a supply intent.

Parameters:

| Name                | Type            | Description                                            | Default                                                                                                                     |
| ------------------- | --------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `protocol`          | `str`           | Lending protocol (e.g., "aave_v3", "morpho_blue")      | *required*                                                                                                                  |
| `token`             | `str`           | Token to supply                                        | *required*                                                                                                                  |
| `amount`            | `ChainedAmount` | Amount to supply, or "all" to use previous step output | *required*                                                                                                                  |
| `use_as_collateral` | `bool`          | Whether to enable as collateral (default True)         | `True`                                                                                                                      |
| `market_id`         | \`str           | None\`                                                 | Market identifier for isolated lending protocols (e.g., Morpho Blue). Required for morpho/morpho_blue, ignored for aave_v3. |
| `chain`             | \`str           | None\`                                                 | Target chain for execution (defaults to strategy's primary chain)                                                           |

Returns:

| Name           | Type           | Description               |
| -------------- | -------------- | ------------------------- |
| `SupplyIntent` | `SupplyIntent` | The created supply intent |

Example

#### Supply 1 ETH to Aave V3 on Arbitrum

intent = Intent.supply( protocol="aave_v3", token="WETH", amount=Decimal("1"), chain="arbitrum", )

#### Supply wstETH to Morpho Blue market

intent = Intent.supply( protocol="morpho_blue", token="wstETH", amount=Decimal("1"), market_id="0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc", chain="ethereum", )

### withdraw

```
withdraw(
    protocol: str,
    token: str,
    amount: ChainedAmount,
    withdraw_all: bool = False,
    market_id: str | None = None,
    chain: str | None = None,
) -> WithdrawIntent
```

Create a withdraw intent.

Parameters:

| Name           | Type            | Description                                              | Default                                                                                                                     |
| -------------- | --------------- | -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `protocol`     | `str`           | Lending protocol (e.g., "aave_v3", "morpho_blue")        | *required*                                                                                                                  |
| `token`        | `str`           | Token to withdraw                                        | *required*                                                                                                                  |
| `amount`       | `ChainedAmount` | Amount to withdraw, or "all" to use previous step output | *required*                                                                                                                  |
| `withdraw_all` | `bool`          | If True, withdraw all available balance                  | `False`                                                                                                                     |
| `market_id`    | \`str           | None\`                                                   | Market identifier for isolated lending protocols (e.g., Morpho Blue). Required for morpho/morpho_blue, ignored for aave_v3. |
| `chain`        | \`str           | None\`                                                   | Target chain for execution (defaults to strategy's primary chain)                                                           |

Returns:

| Name             | Type             | Description                 |
| ---------------- | ---------------- | --------------------------- |
| `WithdrawIntent` | `WithdrawIntent` | The created withdraw intent |

Example

#### Withdraw 0.5 ETH from Aave V3

intent = Intent.withdraw( protocol="aave_v3", token="WETH", amount=Decimal("0.5"), )

#### Withdraw all collateral from Morpho Blue

intent = Intent.withdraw( protocol="morpho_blue", token="wstETH", amount=Decimal("0"), # Ignored when withdraw_all=True withdraw_all=True, market_id="0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc", )

### perp_open

```
perp_open(
    market: str,
    collateral_token: str,
    collateral_amount: ChainedAmount,
    size_usd: Decimal,
    is_long: bool = True,
    leverage: Decimal = Decimal("1"),
    max_slippage: Decimal = Decimal("0.01"),
    protocol: str = "gmx_v2",
    chain: str | None = None,
) -> PerpOpenIntent
```

Create a perpetual position open intent.

Parameters:

| Name                | Type            | Description                                                            | Default                                                           |
| ------------------- | --------------- | ---------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `market`            | `str`           | Market identifier (e.g., "ETH/USD") or market address                  | *required*                                                        |
| `collateral_token`  | `str`           | Token symbol or address for collateral                                 | *required*                                                        |
| `collateral_amount` | `ChainedAmount` | Amount of collateral in token terms, or "all" for previous step output | *required*                                                        |
| `size_usd`          | `Decimal`       | Position size in USD terms                                             | *required*                                                        |
| `is_long`           | `bool`          | True for long, False for short (default True)                          | `True`                                                            |
| `leverage`          | `Decimal`       | Target leverage (default 1x)                                           | `Decimal('1')`                                                    |
| `max_slippage`      | `Decimal`       | Maximum acceptable slippage (default 1%)                               | `Decimal('0.01')`                                                 |
| `protocol`          | `str`           | Perpetuals protocol (default "gmx_v2")                                 | `'gmx_v2'`                                                        |
| `chain`             | \`str           | None\`                                                                 | Target chain for execution (defaults to strategy's primary chain) |

Returns:

| Name             | Type             | Description                  |
| ---------------- | ---------------- | ---------------------------- |
| `PerpOpenIntent` | `PerpOpenIntent` | The created perp open intent |

Example

#### Open a 5x long ETH position with 0.1 ETH collateral on Arbitrum

intent = Intent.perp_open( market="ETH/USD", collateral_token="WETH", collateral_amount=Decimal("0.1"), size_usd=Decimal("1750"), # ~5x at $3500 ETH is_long=True, leverage=Decimal("5"), chain="arbitrum", )

#### Use all collateral from previous step

intent = Intent.perp_open( market="ETH/USD", collateral_token="WETH", collateral_amount="all", # Use previous step output size_usd=Decimal("1750"), is_long=True, chain="arbitrum", )

### perp_close

```
perp_close(
    market: str,
    collateral_token: str,
    is_long: bool,
    size_usd: Decimal | None = None,
    max_slippage: Decimal = Decimal("0.01"),
    protocol: str = "gmx_v2",
    chain: str | None = None,
) -> PerpCloseIntent
```

Create a perpetual position close intent.

Parameters:

| Name               | Type      | Description                                           | Default                                                           |
| ------------------ | --------- | ----------------------------------------------------- | ----------------------------------------------------------------- |
| `market`           | `str`     | Market identifier (e.g., "ETH/USD") or market address | *required*                                                        |
| `collateral_token` | `str`     | Token symbol or address for collateral                | *required*                                                        |
| `is_long`          | `bool`    | Position direction                                    | *required*                                                        |
| `size_usd`         | \`Decimal | None\`                                                | Amount to close in USD (None = close full position)               |
| `max_slippage`     | `Decimal` | Maximum acceptable slippage (default 1%)              | `Decimal('0.01')`                                                 |
| `protocol`         | `str`     | Perpetuals protocol (default "gmx_v2")                | `'gmx_v2'`                                                        |
| `chain`            | \`str     | None\`                                                | Target chain for execution (defaults to strategy's primary chain) |

Returns:

| Name              | Type              | Description                   |
| ----------------- | ----------------- | ----------------------------- |
| `PerpCloseIntent` | `PerpCloseIntent` | The created perp close intent |

Example

#### Close entire long ETH position

intent = Intent.perp_close( market="ETH/USD", collateral_token="WETH", is_long=True, )

#### Close $500 of position

intent = Intent.perp_close( market="ETH/USD", collateral_token="WETH", is_long=True, size_usd=Decimal("500"), )

### bridge

```
bridge(
    token: str,
    amount: Decimal | Literal["all"],
    from_chain: str,
    to_chain: str,
    max_slippage: Decimal = Decimal("0.005"),
    preferred_bridge: str | None = None,
    destination_address: str | None = None,
) -> Any
```

Create a bridge intent for cross-chain asset transfer.

Bridge intents represent cross-chain token transfers. They can be used standalone or as part of an IntentSequence for complex multi-step operations like swap -> bridge -> supply.

When amount="all", the bridge will use the entire output from the previous step in a sequence. This is useful for chaining operations.

Parameters:

| Name                  | Type      | Description                                                 | Default                                                                                                                                                                               |
| --------------------- | --------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token`               | `str`     | Token symbol to bridge (e.g., "ETH", "USDC", "WBTC")        | *required*                                                                                                                                                                            |
| `amount`              | \`Decimal | Literal['all']\`                                            | Amount to bridge (Decimal) or "all" to use previous step's output                                                                                                                     |
| `from_chain`          | `str`     | Source chain identifier (e.g., "base", "arbitrum")          | *required*                                                                                                                                                                            |
| `to_chain`            | `str`     | Destination chain identifier (e.g., "arbitrum", "optimism") | *required*                                                                                                                                                                            |
| `max_slippage`        | `Decimal` | Maximum acceptable slippage (default 0.5%)                  | `Decimal('0.005')`                                                                                                                                                                    |
| `preferred_bridge`    | \`str     | None\`                                                      | Optional preferred bridge adapter name (e.g., "across", "stargate")                                                                                                                   |
| `destination_address` | \`str     | None\`                                                      | Optional recipient address on the destination chain. If None, the compiler resolves it from chain_wallets (multi-wallet mode) or uses the source wallet address (single-wallet mode). |

Returns:

| Name           | Type  | Description               |
| -------------- | ----- | ------------------------- |
| `BridgeIntent` | `Any` | The created bridge intent |

Example

#### Bridge 1000 USDC from Base to Arbitrum

intent = Intent.bridge( token="USDC", amount=Decimal("1000"), from_chain="base", to_chain="arbitrum", )

#### Bridge all ETH from previous step (in a sequence)

sequence = Intent.sequence([ Intent.swap("USDC", "ETH", amount=Decimal("1000"), chain="base"), Intent.bridge( token="ETH", amount="all", # Use output from swap from_chain="base", to_chain="arbitrum", ), Intent.supply(protocol="aave_v3", token="WETH", amount="all", chain="arbitrum"), ])

#### Bridge with preferred bridge

intent = Intent.bridge( token="USDC", amount=Decimal("5000"), from_chain="arbitrum", to_chain="optimism", preferred_bridge="across", # Prefer Across for fast finality )

### flash_loan

```
flash_loan(
    provider: Literal["aave", "balancer", "morpho", "auto"],
    token: str,
    amount: Decimal,
    callback_intents: list[FlashLoanCallbackIntent],
    chain: str | None = None,
) -> FlashLoanIntent
```

Create a flash loan intent with callback operations.

A flash loan allows borrowing assets without collateral, provided the borrowed amount plus fees is repaid within the same transaction.

Parameters:

| Name               | Type                                            | Description                                                                                                                                                                                 | Default                                                           |
| ------------------ | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `provider`         | `Literal['aave', 'balancer', 'morpho', 'auto']` | Flash loan provider ("aave", "balancer", or "auto") - "aave": 0.09% fee, high liquidity - "balancer": 0% fee, lower liquidity - "auto": Automatically select based on availability and fees | *required*                                                        |
| `token`            | `str`                                           | Token to borrow via flash loan                                                                                                                                                              | *required*                                                        |
| `amount`           | `Decimal`                                       | Amount to borrow                                                                                                                                                                            | *required*                                                        |
| `callback_intents` | `list[FlashLoanCallbackIntent]`                 | List of intents to execute with borrowed funds. Must return sufficient funds to repay loan + fees.                                                                                          | *required*                                                        |
| `chain`            | \`str                                           | None\`                                                                                                                                                                                      | Target chain for execution (defaults to strategy's primary chain) |

Returns:

| Name              | Type              | Description                   |
| ----------------- | ----------------- | ----------------------------- |
| `FlashLoanIntent` | `FlashLoanIntent` | The created flash loan intent |

Example

#### Flash loan arbitrage: borrow USDC, swap through two DEXs

intent = Intent.flash_loan( provider="aave", token="USDC", amount=Decimal("100000"), callback_intents=[ Intent.swap("USDC", "WETH", amount=Decimal("100000"), protocol="uniswap_v3"), Intent.swap("WETH", "USDC", amount="all", protocol="curve"), ], chain="ethereum" )

### hold

```
hold(
    reason: str | None = None,
    chain: str | None = None,
    reason_code: str | None = None,
    reason_details: dict[str, Any] | None = None,
) -> HoldIntent
```

Create a hold intent (no action).

Parameters:

| Name             | Type             | Description | Default                                                                                              |
| ---------------- | ---------------- | ----------- | ---------------------------------------------------------------------------------------------------- |
| `reason`         | \`str            | None\`      | Optional reason for holding (for logging/debugging)                                                  |
| `chain`          | \`str            | None\`      | Target chain for execution (defaults to strategy's primary chain)                                    |
| `reason_code`    | \`str            | None\`      | Optional structured reason code for alerting/filtering (e.g., "INSUFFICIENT_BALANCE", "RSI_NEUTRAL") |
| `reason_details` | \`dict[str, Any] | None\`      | Optional structured details for the hold reason                                                      |

Returns:

| Name         | Type         | Description             |
| ------------ | ------------ | ----------------------- |
| `HoldIntent` | `HoldIntent` | The created hold intent |

Example

#### Hold with no reason

intent = Intent.hold()

#### Hold with a reason for logging

intent = Intent.hold(reason="RSI in neutral zone, waiting for signal")

#### Hold with structured reason for alerting

intent = Intent.hold( reason="RSI neutral", reason_code="RSI_NEUTRAL", reason_details={"rsi": 52.3, "oversold": 30, "overbought": 70}, )

### stake

```
stake(
    protocol: str,
    token_in: str,
    amount: ChainedAmount,
    receive_wrapped: bool = True,
    chain: str | None = None,
) -> StakeIntent
```

Create a stake intent for liquid staking protocols.

Parameters:

| Name              | Type            | Description                                                                                                                                                                   | Default                                                           |
| ----------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
| `protocol`        | `str`           | Staking protocol (e.g., "lido", "ethena")                                                                                                                                     | *required*                                                        |
| `token_in`        | `str`           | Token to stake (e.g., "ETH" for Lido, "USDe" for Ethena)                                                                                                                      | *required*                                                        |
| `amount`          | `ChainedAmount` | Amount to stake, or "all" to use previous step output                                                                                                                         | *required*                                                        |
| `receive_wrapped` | `bool`          | Whether to receive wrapped version (default True). For Lido: True = wstETH (non-rebasing), False = stETH (rebasing) For Ethena: Always receives sUSDe regardless of this flag | `True`                                                            |
| `chain`           | \`str           | None\`                                                                                                                                                                        | Target chain for execution (defaults to strategy's primary chain) |

Returns:

| Name          | Type          | Description              |
| ------------- | ------------- | ------------------------ |
| `StakeIntent` | `StakeIntent` | The created stake intent |

Example

#### Stake 1 ETH with Lido on Ethereum, receive wstETH

intent = Intent.stake( protocol="lido", token_in="ETH", amount=Decimal("1"), receive_wrapped=True, chain="ethereum", )

#### Stake USDe with Ethena

intent = Intent.stake( protocol="ethena", token_in="USDe", amount=Decimal("10000"), chain="ethereum", )

#### Stake all ETH from previous step in a sequence

intent = Intent.stake( protocol="lido", token_in="ETH", amount="all", chain="ethereum", )

### unstake

```
unstake(
    protocol: str,
    token_in: str,
    amount: ChainedAmount,
    chain: str | None = None,
    protocol_params: dict[str, Any] | None = None,
) -> UnstakeIntent
```

Create an unstake intent for withdrawing from liquid staking protocols.

Parameters:

| Name              | Type             | Description                                                           | Default                                                                        |
| ----------------- | ---------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------ |
| `protocol`        | `str`            | Staking protocol (e.g., "lido", "ethena")                             | *required*                                                                     |
| `token_in`        | `str`            | Staked token to unstake (e.g., "wstETH" for Lido, "sUSDe" for Ethena) | *required*                                                                     |
| `amount`          | `ChainedAmount`  | Amount to unstake, or "all" to use previous step output               | *required*                                                                     |
| `chain`           | \`str            | None\`                                                                | Target chain for execution (defaults to strategy's primary chain)              |
| `protocol_params` | \`dict[str, Any] | None\`                                                                | Optional protocol-specific parameters (e.g., {"phase": "cooldown"} for Ethena) |

Returns:

| Name            | Type            | Description                |
| --------------- | --------------- | -------------------------- |
| `UnstakeIntent` | `UnstakeIntent` | The created unstake intent |

Example

#### Unstake 1 wstETH with Lido on Ethereum

intent = Intent.unstake( protocol="lido", token_in="wstETH", amount=Decimal("1"), chain="ethereum", )

#### Unstake sUSDe with Ethena (initiates cooldown)

intent = Intent.unstake( protocol="ethena", token_in="sUSDe", amount=Decimal("10000"), chain="ethereum", )

#### Unstake all tokens from previous step in a sequence

intent = Intent.unstake( protocol="lido", token_in="wstETH", amount="all", chain="ethereum", )

### wrap

```
wrap(
    token: str,
    amount: ChainedAmount,
    chain: str | None = None,
) -> WrapNativeIntent
```

Create a wrap native token intent (e.g. ETH -> WETH).

Calls the wrapped token's `deposit()` function with `msg.value` to convert native currency to its wrapped ERC-20 equivalent.

Parameters:

| Name     | Type            | Description                                            | Default                    |
| -------- | --------------- | ------------------------------------------------------ | -------------------------- |
| `token`  | `str`           | Wrapped token symbol (e.g., "WETH", "WMATIC", "WAVAX") | *required*                 |
| `amount` | `ChainedAmount` | Amount of native token to wrap, or "all"               | *required*                 |
| `chain`  | \`str           | None\`                                                 | Target chain for execution |

Returns:

| Name               | Type               | Description             |
| ------------------ | ------------------ | ----------------------- |
| `WrapNativeIntent` | `WrapNativeIntent` | The created wrap intent |

Example

intent = Intent.wrap(token="WETH", amount=Decimal("0.01"), chain="arbitrum")

### unwrap

```
unwrap(
    token: str,
    amount: ChainedAmount,
    chain: str | None = None,
) -> UnwrapNativeIntent
```

Create an unwrap native token intent (e.g. WETH -> ETH).

Calls the wrapped token's `withdraw(uint256)` function to convert wrapped native tokens back to the chain's native currency.

Parameters:

| Name     | Type            | Description                                                      | Default                    |
| -------- | --------------- | ---------------------------------------------------------------- | -------------------------- |
| `token`  | `str`           | Wrapped token symbol to unwrap (e.g., "WETH", "WMATIC", "WAVAX") | *required*                 |
| `amount` | `ChainedAmount` | Amount of wrapped token to unwrap, or "all"                      | *required*                 |
| `chain`  | \`str           | None\`                                                           | Target chain for execution |

Returns:

| Name                 | Type                 | Description               |
| -------------------- | -------------------- | ------------------------- |
| `UnwrapNativeIntent` | `UnwrapNativeIntent` | The created unwrap intent |

Example

#### Unwrap 0.01 WETH to ETH on Arbitrum

intent = Intent.unwrap(token="WETH", amount=Decimal("0.01"), chain="arbitrum")

#### Unwrap all WETH from previous step in a sequence

intent = Intent.unwrap(token="WETH", amount="all", chain="arbitrum")

### ensure_balance

```
ensure_balance(
    token: str,
    min_amount: Decimal,
    target_chain: str,
    max_slippage: Decimal = Decimal("0.005"),
    preferred_bridge: str | None = None,
) -> Any
```

Create an ensure_balance intent for automatic cross-chain balance management.

EnsureBalanceIntent expresses the goal of having at least a certain amount of tokens on a specific chain. When resolved (via resolve() method), the system will automatically determine the appropriate action:

1. If target chain has sufficient balance -> HoldIntent (no action)
1. If another chain has sufficient balance -> BridgeIntent (transfer)
1. If no single chain has enough -> InsufficientBalanceError

This simplifies strategy development by abstracting away the complexity of cross-chain balance management.

Parameters:

| Name               | Type      | Description                                                  | Default                                             |
| ------------------ | --------- | ------------------------------------------------------------ | --------------------------------------------------- |
| `token`            | `str`     | Token symbol to ensure (e.g., "ETH", "USDC", "WBTC")         | *required*                                          |
| `min_amount`       | `Decimal` | Minimum amount required on target chain                      | *required*                                          |
| `target_chain`     | `str`     | Chain where the balance is needed (e.g., "arbitrum", "base") | *required*                                          |
| `max_slippage`     | `Decimal` | Maximum acceptable slippage for bridging (default 0.5%)      | `Decimal('0.005')`                                  |
| `preferred_bridge` | \`str     | None\`                                                       | Optional preferred bridge adapter name for transfer |

Returns:

| Name                  | Type  | Description                       |
| --------------------- | ----- | --------------------------------- |
| `EnsureBalanceIntent` | `Any` | The created ensure_balance intent |

Example

#### Ensure at least 1000 USDC on Arbitrum before opening a position

intent = Intent.ensure_balance( token="USDC", min_amount=Decimal("1000"), target_chain="arbitrum", )

#### Ensure at least 2 ETH on Base with custom slippage

intent = Intent.ensure_balance( token="ETH", min_amount=Decimal("2"), target_chain="base", max_slippage=Decimal("0.01"), # 1% max slippage preferred_bridge="across", # Prefer Across bridge )

#### Using ensure_balance in a strategy

def decide(self, market: MultiChainMarketSnapshot) -> DecideResult:

# First ensure we have enough USDC on Arbitrum

ensure_intent = Intent.ensure_balance( token="USDC", min_amount=Decimal("5000"), target_chain="arbitrum", )

```
# Resolve to concrete intent based on current balances
target_balance = market.balance("USDC", chain="arbitrum").balance
chain_balances = {
    chain: market.balance("USDC", chain=chain).balance
    for chain in market.chains
    if chain != "arbitrum"
}
resolved_intent = ensure_intent.resolve(target_balance, chain_balances)

# If resolved to HoldIntent, we can proceed with other actions
# If resolved to BridgeIntent, execute the bridge first
return resolved_intent
```

### prediction_buy

```
prediction_buy(
    market_id: str,
    outcome: Literal["YES", "NO"],
    amount_usd: Decimal | None = None,
    shares: Decimal | None = None,
    max_price: Decimal | None = None,
    order_type: Literal["market", "limit"] = "market",
    time_in_force: Literal["GTC", "IOC", "FOK"] = "GTC",
    expiration_hours: int | None = None,
    protocol: str = "polymarket",
    chain: str | None = None,
    exit_conditions: PredictionExitConditions | None = None,
) -> PredictionBuyIntent
```

Create a prediction buy intent for purchasing outcome shares.

Buy outcome tokens (YES or NO) on a prediction market like Polymarket. Prices represent implied probability (e.g., 0.65 = 65% chance).

Parameters:

| Name               | Type                           | Description                                                       | Default                                                                                                                 |
| ------------------ | ------------------------------ | ----------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `market_id`        | `str`                          | Polymarket market ID or slug (e.g., "will-bitcoin-exceed-100000") | *required*                                                                                                              |
| `outcome`          | `Literal['YES', 'NO']`         | Which outcome to buy ("YES" or "NO")                              | *required*                                                                                                              |
| `amount_usd`       | \`Decimal                      | None\`                                                            | USDC amount to spend (mutually exclusive with shares)                                                                   |
| `shares`           | \`Decimal                      | None\`                                                            | Number of shares to buy (mutually exclusive with amount_usd)                                                            |
| `max_price`        | \`Decimal                      | None\`                                                            | Maximum price per share (0.01-0.99) for limit orders                                                                    |
| `order_type`       | `Literal['market', 'limit']`   | Order type ("market" or "limit", default "market")                | `'market'`                                                                                                              |
| `time_in_force`    | `Literal['GTC', 'IOC', 'FOK']` | How long order remains active ("GTC", "IOC", "FOK")               | `'GTC'`                                                                                                                 |
| `expiration_hours` | \`int                          | None\`                                                            | Hours until order expires (None = no expiry)                                                                            |
| `protocol`         | `str`                          | Protocol to use (defaults to "polymarket")                        | `'polymarket'`                                                                                                          |
| `chain`            | \`str                          | None\`                                                            | Target chain (defaults to "polygon" for Polymarket)                                                                     |
| `exit_conditions`  | \`PredictionExitConditions     | None\`                                                            | Optional exit conditions for automatic position monitoring (stop-loss, take-profit, trailing stop, pre-resolution exit) |

Returns:

| Name                  | Type                  | Description                       |
| --------------------- | --------------------- | --------------------------------- |
| `PredictionBuyIntent` | `PredictionBuyIntent` | The created prediction buy intent |

Example

#### Buy $100 worth of YES shares at market price

intent = Intent.prediction_buy( market_id="will-bitcoin-exceed-100000", outcome="YES", amount_usd=Decimal("100"), )

#### Buy 50 YES shares with limit order at max price of $0.65

intent = Intent.prediction_buy( market_id="will-bitcoin-exceed-100000", outcome="YES", shares=Decimal("50"), max_price=Decimal("0.65"), order_type="limit", )

#### Buy NO shares with IOC (immediate or cancel)

intent = Intent.prediction_buy( market_id="will-bitcoin-exceed-100000", outcome="NO", amount_usd=Decimal("200"), time_in_force="IOC", )

### prediction_sell

```
prediction_sell(
    market_id: str,
    outcome: Literal["YES", "NO"],
    shares: Decimal | Literal["all"],
    min_price: Decimal | None = None,
    order_type: Literal["market", "limit"] = "market",
    time_in_force: Literal["GTC", "IOC", "FOK"] = "GTC",
    protocol: str = "polymarket",
    chain: str | None = None,
) -> PredictionSellIntent
```

Create a prediction sell intent for selling outcome shares.

Sell outcome tokens (YES or NO) on a prediction market like Polymarket. Use shares="all" to sell your entire position.

Parameters:

| Name            | Type                           | Description                                         | Default                                                    |
| --------------- | ------------------------------ | --------------------------------------------------- | ---------------------------------------------------------- |
| `market_id`     | `str`                          | Polymarket market ID or slug                        | *required*                                                 |
| `outcome`       | `Literal['YES', 'NO']`         | Which outcome to sell ("YES" or "NO")               | *required*                                                 |
| `shares`        | \`Decimal                      | Literal['all']\`                                    | Number of shares to sell, or "all" to sell entire position |
| `min_price`     | \`Decimal                      | None\`                                              | Minimum price per share (0.01-0.99) for limit orders       |
| `order_type`    | `Literal['market', 'limit']`   | Order type ("market" or "limit", default "market")  | `'market'`                                                 |
| `time_in_force` | `Literal['GTC', 'IOC', 'FOK']` | How long order remains active ("GTC", "IOC", "FOK") | `'GTC'`                                                    |
| `protocol`      | `str`                          | Protocol to use (defaults to "polymarket")          | `'polymarket'`                                             |
| `chain`         | \`str                          | None\`                                              | Target chain (defaults to "polygon" for Polymarket)        |

Returns:

| Name                   | Type                   | Description                        |
| ---------------------- | ---------------------- | ---------------------------------- |
| `PredictionSellIntent` | `PredictionSellIntent` | The created prediction sell intent |

Example

#### Sell all YES shares at market price

intent = Intent.prediction_sell( market_id="will-bitcoin-exceed-100000", outcome="YES", shares="all", )

#### Sell 25 NO shares with limit order at min $0.40

intent = Intent.prediction_sell( market_id="will-bitcoin-exceed-100000", outcome="NO", shares=Decimal("25"), min_price=Decimal("0.40"), order_type="limit", )

### prediction_redeem

```
prediction_redeem(
    market_id: str,
    outcome: Literal["YES", "NO"] | None = None,
    shares: Decimal | Literal["all"] = "all",
    protocol: str = "polymarket",
    chain: str | None = None,
) -> PredictionRedeemIntent
```

Create a prediction redeem intent for redeeming winning positions.

Redeem winning outcome tokens after a market has resolved. Winning positions redeem for $1 per share in USDC.

Parameters:

| Name        | Type                   | Description                                | Default                                                 |
| ----------- | ---------------------- | ------------------------------------------ | ------------------------------------------------------- |
| `market_id` | `str`                  | Polymarket market ID or slug               | *required*                                              |
| `outcome`   | \`Literal['YES', 'NO'] | None\`                                     | Which outcome to redeem ("YES", "NO", or None for both) |
| `shares`    | \`Decimal              | Literal['all']\`                           | Number of shares to redeem, or "all" (default)          |
| `protocol`  | `str`                  | Protocol to use (defaults to "polymarket") | `'polymarket'`                                          |
| `chain`     | \`str                  | None\`                                     | Target chain (defaults to "polygon" for Polymarket)     |

Returns:

| Name                     | Type                     | Description                          |
| ------------------------ | ------------------------ | ------------------------------------ |
| `PredictionRedeemIntent` | `PredictionRedeemIntent` | The created prediction redeem intent |

Note

Redemption is only possible after the market has resolved. Losing positions are worthless and cannot be redeemed.

Example

#### Redeem all winning positions from a resolved market

intent = Intent.prediction_redeem( market_id="will-bitcoin-exceed-100000", )

#### Redeem only YES shares (if YES won)

intent = Intent.prediction_redeem( market_id="will-bitcoin-exceed-100000", outcome="YES", )

#### Redeem specific number of shares

intent = Intent.prediction_redeem( market_id="will-bitcoin-exceed-100000", outcome="YES", shares=Decimal("50"), )

### vault_deposit

```
vault_deposit(
    protocol: str,
    vault_address: str,
    amount: ChainedAmount,
    deposit_token: str | None = None,
    chain: str | None = None,
) -> VaultDepositIntent
```

Create a vault deposit intent for MetaMorpho ERC-4626 vaults.

Deposits underlying assets into a MetaMorpho vault in exchange for vault shares. The vault manages allocation across Morpho Blue markets.

Parameters:

| Name            | Type            | Description                                      | Default                                               |
| --------------- | --------------- | ------------------------------------------------ | ----------------------------------------------------- |
| `protocol`      | `str`           | Vault protocol (must be "metamorpho")            | *required*                                            |
| `vault_address` | `str`           | MetaMorpho vault contract address                | *required*                                            |
| `amount`        | `ChainedAmount` | Amount of underlying assets to deposit, or "all" | *required*                                            |
| `deposit_token` | \`str           | None\`                                           | Underlying token symbol (e.g. "USDC") for backtesting |
| `chain`         | \`str           | None\`                                           | Target chain (defaults to strategy's primary chain)   |

Returns:

| Name                 | Type                 | Description                      |
| -------------------- | -------------------- | -------------------------------- |
| `VaultDepositIntent` | `VaultDepositIntent` | The created vault deposit intent |

Example

#### Deposit 1000 USDC into Steakhouse vault

intent = Intent.vault_deposit( protocol="metamorpho", vault_address="0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB", amount=Decimal("1000"), deposit_token="USDC", chain="ethereum", )

### vault_redeem

```
vault_redeem(
    protocol: str,
    vault_address: str,
    shares: ChainedAmount,
    deposit_token: str | None = None,
    chain: str | None = None,
) -> VaultRedeemIntent
```

Create a vault redeem intent for MetaMorpho ERC-4626 vaults.

Redeems vault shares to receive underlying assets. No approval needed since the user is redeeming their own shares.

Parameters:

| Name            | Type            | Description                                        | Default                                               |
| --------------- | --------------- | -------------------------------------------------- | ----------------------------------------------------- |
| `protocol`      | `str`           | Vault protocol (must be "metamorpho")              | *required*                                            |
| `vault_address` | `str`           | MetaMorpho vault contract address                  | *required*                                            |
| `shares`        | `ChainedAmount` | Number of shares to redeem, or "all" to redeem all | *required*                                            |
| `deposit_token` | \`str           | None\`                                             | Underlying token symbol (e.g. "USDC") for backtesting |
| `chain`         | \`str           | None\`                                             | Target chain (defaults to strategy's primary chain)   |

Returns:

| Name                | Type                | Description                     |
| ------------------- | ------------------- | ------------------------------- |
| `VaultRedeemIntent` | `VaultRedeemIntent` | The created vault redeem intent |

Example

#### Redeem all shares from Steakhouse vault

intent = Intent.vault_redeem( protocol="metamorpho", vault_address="0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB", shares="all", deposit_token="USDC", chain="ethereum", )

### sequence

```
sequence(
    intents: list[AnyIntent], description: str | None = None
) -> IntentSequence
```

Create an intent sequence for dependent actions that must execute in order.

Use this when you have a series of intents where each step depends on the previous step's output. For example:

- Swap USDC -> ETH, then bridge ETH to another chain
- Bridge tokens, then supply to lending protocol

The intents in a sequence will always execute sequentially. If any step fails, subsequent steps will not execute.

Parameters:

| Name          | Type              | Description                         | Default                                                 |
| ------------- | ----------------- | ----------------------------------- | ------------------------------------------------------- |
| `intents`     | `list[AnyIntent]` | List of intents to execute in order | *required*                                              |
| `description` | \`str             | None\`                              | Optional description of what this sequence accomplishes |

Returns:

| Name             | Type             | Description                 |
| ---------------- | ---------------- | --------------------------- |
| `IntentSequence` | `IntentSequence` | The created intent sequence |

Raises:

| Type                   | Description                  |
| ---------------------- | ---------------------------- |
| `InvalidSequenceError` | If the intents list is empty |

Example

#### Create a sequence: swap -> bridge -> supply

return Intent.sequence([ Intent.swap("USDC", "ETH", amount=Decimal("1000"), chain="base"), Intent.supply(protocol="aave_v3", token="WETH", amount=Decimal("0.5"), chain="arbitrum"), ], description="Move funds from Base to Arbitrum and deposit")

#### In decide(), return multiple sequences for parallel execution

return \[ Intent.sequence([swap1, supply1]), # Execute as sequence Intent.sequence([swap2, supply2]), # Execute in parallel with above \]

### serialize

```
serialize(intent: AnyIntent) -> dict[str, Any]
```

Serialize any intent to a dictionary.

Parameters:

| Name     | Type        | Description             | Default    |
| -------- | ----------- | ----------------------- | ---------- |
| `intent` | `AnyIntent` | The intent to serialize | *required* |

Returns:

| Name   | Type             | Description           |
| ------ | ---------------- | --------------------- |
| `dict` | `dict[str, Any]` | The serialized intent |

### deserialize

```
deserialize(data: dict[str, Any]) -> Any
```

Deserialize a dictionary to the appropriate intent type.

Parameters:

| Name   | Type             | Description                | Default    |
| ------ | ---------------- | -------------------------- | ---------- |
| `data` | `dict[str, Any]` | The serialized intent data | *required* |

Returns:

| Type  | Description                                         |
| ----- | --------------------------------------------------- |
| `Any` | The deserialized intent (AnyIntent or BridgeIntent) |

Raises:

| Type         | Description                   |
| ------------ | ----------------------------- |
| `ValueError` | If the intent type is unknown |

### get_type

```
get_type(intent: AnyIntent) -> IntentType
```

Get the type of an intent.

Parameters:

| Name     | Type        | Description                   | Default    |
| -------- | ----------- | ----------------------------- | ---------- |
| `intent` | `AnyIntent` | The intent to get the type of | *required* |

Returns:

| Name         | Type         | Description            |
| ------------ | ------------ | ---------------------- |
| `IntentType` | `IntentType` | The type of the intent |

### validate_chain

```
validate_chain(
    intent: AnyIntent,
    configured_chains: Sequence[str],
    default_chain: str | None = None,
) -> str
```

Validate and resolve the chain for an intent.

Validates that the intent's chain (if specified) is in the list of configured chains. If no chain is specified on the intent, returns the default chain.

Parameters:

| Name                | Type            | Description                                | Default                                                                                          |
| ------------------- | --------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------ |
| `intent`            | `AnyIntent`     | The intent to validate                     | *required*                                                                                       |
| `configured_chains` | `Sequence[str]` | List of chains configured for the strategy | *required*                                                                                       |
| `default_chain`     | \`str           | None\`                                     | Default chain to use if intent has no chain specified. If None, uses the first configured chain. |

Returns:

| Name  | Type  | Description                         |
| ----- | ----- | ----------------------------------- |
| `str` | `str` | The resolved chain name (lowercase) |

Raises:

| Type                | Description                                       |
| ------------------- | ------------------------------------------------- |
| `InvalidChainError` | If the intent's chain is not in configured_chains |
| `ValueError`        | If no default chain can be determined             |

Example

#### Validate an intent against strategy's configured chains

resolved_chain = Intent.validate_chain( intent, configured_chains=["arbitrum", "optimism"], default_chain="arbitrum", )

### get_chain

```
get_chain(intent: AnyIntent) -> str | None
```

Get the chain specified on an intent.

Parameters:

| Name     | Type        | Description                      | Default    |
| -------- | ----------- | -------------------------------- | ---------- |
| `intent` | `AnyIntent` | The intent to get the chain from | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

### is_sequence

```
is_sequence(item: AnyIntent | IntentSequence) -> bool
```

Check if an item is an IntentSequence.

Parameters:

| Name   | Type        | Description      | Default                           |
| ------ | ----------- | ---------------- | --------------------------------- |
| `item` | \`AnyIntent | IntentSequence\` | Intent or IntentSequence to check |

Returns:

| Name   | Type   | Description                       |
| ------ | ------ | --------------------------------- |
| `bool` | `bool` | True if item is an IntentSequence |

### normalize_decide_result

```
normalize_decide_result(
    result: DecideResult,
) -> list[AnyIntent | IntentSequence]
```

Normalize a decide() result to a list of items to execute.

This helper converts any valid decide() return value into a normalized list that the executor can process:

- None -> empty list (no action)
- Single intent -> list with one intent
- IntentSequence -> list with one sequence
- List -> returned as-is

Parameters:

| Name     | Type           | Description                    | Default    |
| -------- | -------------- | ------------------------------ | ---------- |
| `result` | `DecideResult` | The return value from decide() | *required* |

Returns:

| Type              | Description        |
| ----------------- | ------------------ |
| \`list\[AnyIntent | IntentSequence\]\` |
| \`list\[AnyIntent | IntentSequence\]\` |
| \`list\[AnyIntent | IntentSequence\]\` |

### count_intents

```
count_intents(result: DecideResult) -> int
```

Count the total number of intents in a decide() result.

Parameters:

| Name     | Type           | Description                    | Default    |
| -------- | -------------- | ------------------------------ | ---------- |
| `result` | `DecideResult` | The return value from decide() | *required* |

Returns:

| Type  | Description                                                     |
| ----- | --------------------------------------------------------------- |
| `int` | Total number of intents (counting all intents within sequences) |

### serialize_result

```
serialize_result(
    result: DecideResult,
) -> dict[str, Any] | None
```

Serialize a decide() result to a dictionary.

Parameters:

| Name     | Type           | Description                    | Default    |
| -------- | -------------- | ------------------------------ | ---------- |
| `result` | `DecideResult` | The return value from decide() | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`dict[str, Any] | None\`      |

### deserialize_result

```
deserialize_result(
    data: dict[str, Any] | None,
) -> DecideResult
```

Deserialize a decide() result from a dictionary.

Parameters:

| Name   | Type             | Description | Default                |
| ------ | ---------------- | ----------- | ---------------------- |
| `data` | \`dict[str, Any] | None\`      | Serialized result data |

Returns:

| Type           | Description               |
| -------------- | ------------------------- |
| `DecideResult` | Deserialized DecideResult |

### has_chained_amount

```
has_chained_amount(intent: AnyIntent) -> bool
```

Check if an intent uses a chained amount from a previous step.

An intent has a chained amount when its amount field is set to "all", meaning it should use the actual received amount from the previous step in a sequence (post-slippage, post-fees).

Parameters:

| Name     | Type        | Description         | Default    |
| -------- | ----------- | ------------------- | ---------- |
| `intent` | `AnyIntent` | The intent to check | *required* |

Returns:

| Type   | Description                                           |
| ------ | ----------------------------------------------------- |
| `bool` | True if the intent uses amount="all", False otherwise |

### validate_chained_amounts

```
validate_chained_amounts(sequence: IntentSequence) -> None
```

Validate that chained amounts are used correctly in a sequence.

Validates that:

1. amount="all" is NOT used on the first step of a sequence
1. The sequence has proper dependencies for amount resolution

Parameters:

| Name       | Type             | Description                     | Default    |
| ---------- | ---------------- | ------------------------------- | ---------- |
| `sequence` | `IntentSequence` | The intent sequence to validate | *required* |

Raises:

| Type                 | Description                               |
| -------------------- | ----------------------------------------- |
| `InvalidAmountError` | If amount="all" is used on the first step |

### get_amount_field

```
get_amount_field(intent: AnyIntent) -> ChainedAmount | None
```

Get the amount field value from an intent for chaining purposes.

This returns the amount that flows to the next step in a sequence. Different intents output different amounts:

- SwapIntent: amount (token output) or amount_usd
- SupplyIntent: amount (what was supplied)
- RepayIntent: amount (what was repaid)
- WithdrawIntent: amount (what was withdrawn)
- BorrowIntent: borrow_amount (NOT collateral_amount - this is what's borrowed)
- PerpOpenIntent: collateral_amount (what was deposited)
- BridgeIntent: amount (what was bridged)

Parameters:

| Name     | Type        | Description                       | Default    |
| -------- | ----------- | --------------------------------- | ---------- |
| `intent` | `AnyIntent` | The intent to get the amount from | *required* |

Returns:

| Type            | Description |
| --------------- | ----------- |
| \`ChainedAmount | None\`      |

### set_resolved_amount

```
set_resolved_amount(
    intent: AnyIntent, resolved_amount: Decimal
) -> AnyIntent
```

Create a copy of an intent with the amount resolved from "all" to a concrete value.

This is used at execution time to resolve amount="all" to the actual received amount from the previous step.

Parameters:

| Name              | Type        | Description                | Default    |
| ----------------- | ----------- | -------------------------- | ---------- |
| `intent`          | `AnyIntent` | The intent to update       | *required* |
| `resolved_amount` | `Decimal`   | The concrete amount to use | *required* |

Returns:

| Type        | Description                                    |
| ----------- | ---------------------------------------------- |
| `AnyIntent` | A new intent instance with the resolved amount |

Note

This creates a new intent instance; it does not mutate the original.

## IntentType

## almanak.framework.intents.IntentType

Bases: `Enum`

Types of intents that strategies can express.

## HoldIntent

## almanak.framework.intents.HoldIntent

Bases: `AlmanakImmutableModel`

Intent to take no action (wait).

This is useful when a strategy explicitly decides not to act, as opposed to returning None which might indicate an error.

Attributes:

| Name             | Type             | Description                           |
| ---------------- | ---------------- | ------------------------------------- |
| `reason`         | \`str            | None\`                                |
| `reason_code`    | \`str            | None\`                                |
| `reason_details` | \`dict[str, Any] | None\`                                |
| `chain`          | \`str            | None\`                                |
| `intent_id`      | `str`            | Unique identifier for this intent     |
| `created_at`     | `datetime`       | Timestamp when the intent was created |

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> HoldIntent
```

Deserialize a dictionary to a HoldIntent.

## SwapIntent

## almanak.framework.intents.SwapIntent

Bases: `AlmanakImmutableModel`

Intent to swap one token for another.

Attributes:

| Name                        | Type                    | Description                                                                                                                                                                                                                                |
| --------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `from_token`                | `str`                   | Symbol or address of the token to swap from                                                                                                                                                                                                |
| `to_token`                  | `str`                   | Symbol or address of the token to swap to                                                                                                                                                                                                  |
| `amount_usd`                | `OptionalSafeDecimal`   | Amount to swap in USD terms (mutually exclusive with amount)                                                                                                                                                                               |
| `amount`                    | `OptionalChainedAmount` | Amount to swap in token terms, or "all" to use output from previous step                                                                                                                                                                   |
| `max_slippage`              | `SafeDecimal`           | Maximum acceptable slippage (e.g., 0.005 = 0.5%)                                                                                                                                                                                           |
| `max_price_impact`          | `OptionalSafeDecimal`   | Maximum acceptable price impact vs oracle price (e.g., 0.50 = 50%). If the on-chain quoter returns an amount deviating more than this from the oracle estimate, compilation fails. Defaults to None (uses compiler config default of 30%). |
| `protocol`                  | \`str                   | None\`                                                                                                                                                                                                                                     |
| `chain`                     | \`str                   | None\`                                                                                                                                                                                                                                     |
| `destination_chain`         | \`str                   | None\`                                                                                                                                                                                                                                     |
| `priority_fee_level`        | \`str                   | None\`                                                                                                                                                                                                                                     |
| `priority_fee_max_lamports` | \`int                   | None\`                                                                                                                                                                                                                                     |
| `intent_id`                 | `str`                   | Unique identifier for this intent                                                                                                                                                                                                          |
| `created_at`                | `datetime`              | Timestamp when the intent was created                                                                                                                                                                                                      |

Note

When amount="all", the swap will use the entire output from the previous step in a sequence. This is useful for chaining operations like: bridge -> swap -> supply. The actual amount is resolved at execution time.

For cross-chain swaps, set destination_chain to the target chain name. Cross-chain swaps require protocol="enso" as Enso handles the bridging.

Example

### Same-chain swap

Intent.swap("USDC", "WETH", amount_usd=1000, chain="arbitrum")

### Cross-chain swap: Base USDC -> Arbitrum WETH

Intent.swap("USDC", "WETH", amount_usd=1000, chain="base", destination_chain="arbitrum", protocol="enso")

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### is_cross_chain

```
is_cross_chain: bool
```

Check if this is a cross-chain swap.

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_swap_intent

```
validate_swap_intent() -> SwapIntent
```

Validate that either amount_usd or amount is provided.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

Backward compatible with existing serialization format.

### deserialize

```
deserialize(data: dict[str, Any]) -> SwapIntent
```

Deserialize a dictionary to a SwapIntent.

Backward compatible with existing serialization format.

## LPOpenIntent

## almanak.framework.intents.LPOpenIntent

Bases: `AlmanakImmutableModel`

Intent to open a liquidity position.

Attributes:

| Name              | Type             | Description                                  |
| ----------------- | ---------------- | -------------------------------------------- |
| `pool`            | `str`            | Pool address or identifier                   |
| `amount0`         | `SafeDecimal`    | Amount of token0 to provide                  |
| `amount1`         | `SafeDecimal`    | Amount of token1 to provide                  |
| `range_lower`     | `SafeDecimal`    | Lower price bound for concentrated liquidity |
| `range_upper`     | `SafeDecimal`    | Upper price bound for concentrated liquidity |
| `protocol`        | `str`            | LP protocol (e.g., "uniswap_v3", "camelot")  |
| `chain`           | \`str            | None\`                                       |
| `protocol_params` | \`dict[str, Any] | None\`                                       |
| `intent_id`       | `str`            | Unique identifier for this intent            |
| `created_at`      | `datetime`       | Timestamp when the intent was created        |

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_lp_open_intent

```
validate_lp_open_intent() -> LPOpenIntent
```

Validate LP open parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> LPOpenIntent
```

Deserialize a dictionary to an LPOpenIntent.

## LPCloseIntent

## almanak.framework.intents.LPCloseIntent

Bases: `AlmanakImmutableModel`

Intent to close a liquidity position.

Attributes:

| Name              | Type             | Description                                              |
| ----------------- | ---------------- | -------------------------------------------------------- |
| `position_id`     | `str`            | Identifier of the position to close (e.g., NFT token ID) |
| `pool`            | \`str            | None\`                                                   |
| `collect_fees`    | `bool`           | Whether to collect accumulated fees                      |
| `protocol`        | `str`            | LP protocol (e.g., "uniswap_v3", "camelot")              |
| `chain`           | \`str            | None\`                                                   |
| `protocol_params` | \`dict[str, Any] | None\`                                                   |
| `intent_id`       | `str`            | Unique identifier for this intent                        |
| `created_at`      | `datetime`       | Timestamp when the intent was created                    |

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> LPCloseIntent
```

Deserialize a dictionary to an LPCloseIntent.

## BorrowIntent

## almanak.framework.intents.BorrowIntent

Bases: `AlmanakImmutableModel`

Intent to borrow tokens from a lending protocol.

Attributes:

| Name                 | Type               | Description                                                       |
| -------------------- | ------------------ | ----------------------------------------------------------------- |
| `protocol`           | `str`              | Lending protocol (e.g., "aave_v3", "morpho")                      |
| `collateral_token`   | `str`              | Token to use as collateral                                        |
| `collateral_amount`  | `ChainedAmount`    | Amount of collateral to supply, or "all" for previous step output |
| `borrow_token`       | `str`              | Token to borrow                                                   |
| `borrow_amount`      | `SafeDecimal`      | Amount to borrow                                                  |
| `interest_rate_mode` | \`InterestRateMode | None\`                                                            |
| `chain`              | \`str              | None\`                                                            |
| `intent_id`          | `str`              | Unique identifier for this intent                                 |
| `created_at`         | `datetime`         | Timestamp when the intent was created                             |

Note

When collateral_amount="all", the borrow will use the entire output from the previous step in a sequence as collateral.

The interest_rate_mode parameter is protocol-specific:

- Aave V3: Supports 'variable' (default). Stable rate is deprecated.
- Morpho: Does not support rate mode selection (parameter is rejected)
- Compound V3: Does not support rate mode selection (parameter is rejected)

The market_id parameter is required for protocols with isolated markets:

- Morpho Blue: Required - identifies the specific lending market
- Aave V3: Not used - uses unified pool

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_borrow_intent

```
validate_borrow_intent() -> BorrowIntent
```

Validate borrow parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> BorrowIntent
```

Deserialize a dictionary to a BorrowIntent.

## RepayIntent

## almanak.framework.intents.RepayIntent

Bases: `AlmanakImmutableModel`

Intent to repay borrowed tokens.

Attributes:

| Name                 | Type               | Description                                                |
| -------------------- | ------------------ | ---------------------------------------------------------- |
| `protocol`           | `str`              | Lending protocol (e.g., "aave_v3", "morpho")               |
| `token`              | `str`              | Token to repay                                             |
| `amount`             | `ChainedAmount`    | Amount to repay, or "all" to use output from previous step |
| `repay_full`         | `bool`             | If True, repay the full outstanding debt                   |
| `interest_rate_mode` | \`InterestRateMode | None\`                                                     |
| `chain`              | \`str              | None\`                                                     |
| `intent_id`          | `str`              | Unique identifier for this intent                          |
| `created_at`         | `datetime`         | Timestamp when the intent was created                      |

Note

When amount="all", the repay will use the entire output from the previous step in a sequence.

The interest_rate_mode parameter is protocol-specific:

- Aave V3: Supports 'variable' (default). Stable rate is deprecated. Must match the rate mode used when borrowing.
- Morpho: Does not support rate mode selection (parameter is rejected)
- Compound V3: Does not support rate mode selection (parameter is rejected)

The market_id parameter is required for protocols with isolated markets:

- Morpho Blue: Required - identifies the specific lending market
- Aave V3: Not used - uses unified pool

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_repay_intent

```
validate_repay_intent() -> RepayIntent
```

Validate repay parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> RepayIntent
```

Deserialize a dictionary to a RepayIntent.

## SupplyIntent

## almanak.framework.intents.SupplyIntent

Bases: `AlmanakImmutableModel`

Intent to supply tokens to a lending protocol.

Attributes:

| Name                | Type            | Description                                                                                                                                                                                                           |
| ------------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `protocol`          | `str`           | Lending protocol (e.g., "aave_v3")                                                                                                                                                                                    |
| `token`             | `str`           | Token to supply                                                                                                                                                                                                       |
| `amount`            | `ChainedAmount` | Amount to supply, or "all" to use output from previous step                                                                                                                                                           |
| `use_as_collateral` | `bool`          | Whether to enable the asset as collateral (default True). Also known as 'enable_as_collateral' - this is an Aave-specific parameter that controls whether the supplied asset can be used as collateral for borrowing. |
| `chain`             | \`str           | None\`                                                                                                                                                                                                                |
| `intent_id`         | `str`           | Unique identifier for this intent                                                                                                                                                                                     |
| `created_at`        | `datetime`      | Timestamp when the intent was created                                                                                                                                                                                 |

Note

When amount="all", the supply will use the entire output from the previous step in a sequence. This is useful for chaining operations like: swap -> supply or bridge -> supply.

The use_as_collateral parameter is protocol-specific:

- Aave V3: Supports collateral toggle (default True)
- Compound V3: Supports collateral toggle
- Morpho: Does not support collateral toggle (all supplied assets are collateral)

The market_id parameter is required for protocols with isolated markets:

- Morpho Blue: Required - identifies the specific lending market
- Aave V3: Not used - uses unified pool

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_supply_intent

```
validate_supply_intent() -> SupplyIntent
```

Validate supply parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> SupplyIntent
```

Deserialize a dictionary to a SupplyIntent.

## WithdrawIntent

## almanak.framework.intents.WithdrawIntent

Bases: `AlmanakImmutableModel`

Intent to withdraw tokens from a lending protocol.

Attributes:

| Name           | Type            | Description                                                   |
| -------------- | --------------- | ------------------------------------------------------------- |
| `protocol`     | `str`           | Lending protocol (e.g., "aave_v3")                            |
| `token`        | `str`           | Token to withdraw                                             |
| `amount`       | `ChainedAmount` | Amount to withdraw, or "all" to use output from previous step |
| `withdraw_all` | `bool`          | If True, withdraw all available balance                       |
| `chain`        | \`str           | None\`                                                        |
| `intent_id`    | `str`           | Unique identifier for this intent                             |
| `created_at`   | `datetime`      | Timestamp when the intent was created                         |

Note

When amount="all", the withdraw will use the entire output from the previous step in a sequence. This is different from withdraw_all which withdraws all available balance from the protocol.

The market_id parameter is required for protocols with isolated markets:

- Morpho Blue: Required - identifies the specific lending market
- Aave V3: Not used - uses unified pool

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_withdraw_intent

```
validate_withdraw_intent() -> WithdrawIntent
```

Validate withdraw parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> WithdrawIntent
```

Deserialize a dictionary to a WithdrawIntent.

## PerpOpenIntent

## almanak.framework.intents.PerpOpenIntent

Bases: `AlmanakImmutableModel`

Intent to open a perpetual futures position.

Attributes:

| Name                | Type            | Description                                                            |
| ------------------- | --------------- | ---------------------------------------------------------------------- |
| `market`            | `str`           | Market identifier (e.g., "ETH/USD") or market address                  |
| `collateral_token`  | `str`           | Token symbol or address for collateral                                 |
| `collateral_amount` | `ChainedAmount` | Amount of collateral in token terms, or "all" for previous step output |
| `size_usd`          | `SafeDecimal`   | Position size in USD terms                                             |
| `is_long`           | `bool`          | True for long position, False for short                                |
| `leverage`          | `SafeDecimal`   | Target leverage for the position (protocol-specific limits apply)      |
| `max_slippage`      | `SafeDecimal`   | Maximum acceptable slippage (e.g., 0.01 = 1%)                          |
| `protocol`          | `str`           | Perpetuals protocol (default "gmx_v2")                                 |
| `chain`             | \`str           | None\`                                                                 |
| `intent_id`         | `str`           | Unique identifier for this intent                                      |
| `created_at`        | `datetime`      | Timestamp when the intent was created                                  |

Note

When collateral_amount="all", the perp open will use the entire output from the previous step in a sequence. This is useful for chaining operations like: swap -> perp_open.

The leverage parameter is validated against protocol-specific limits:

- GMX V2: Supports leverage from 1.1x to 100x
- Hyperliquid: Supports leverage from 1x to 50x

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_perp_open_intent

```
validate_perp_open_intent() -> PerpOpenIntent
```

Validate perp open parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> PerpOpenIntent
```

Deserialize a dictionary to a PerpOpenIntent.

## PerpCloseIntent

## almanak.framework.intents.PerpCloseIntent

Bases: `AlmanakImmutableModel`

Intent to close a perpetual futures position.

Attributes:

| Name               | Type                  | Description                                           |
| ------------------ | --------------------- | ----------------------------------------------------- |
| `market`           | `str`                 | Market identifier (e.g., "ETH/USD") or market address |
| `collateral_token` | `str`                 | Token symbol or address for collateral                |
| `is_long`          | `bool`                | Position direction                                    |
| `size_usd`         | `OptionalSafeDecimal` | Amount to close in USD (None = close full position)   |
| `max_slippage`     | `SafeDecimal`         | Maximum acceptable slippage (e.g., 0.01 = 1%)         |
| `protocol`         | `str`                 | Perpetuals protocol (default "gmx_v2")                |
| `chain`            | \`str                 | None\`                                                |
| `intent_id`        | `str`                 | Unique identifier for this intent                     |
| `created_at`       | `datetime`            | Timestamp when the intent was created                 |

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### close_full_position

```
close_full_position: bool
```

Check if this intent is to close the full position.

### validate_perp_close_intent

```
validate_perp_close_intent() -> PerpCloseIntent
```

Validate perp close parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> PerpCloseIntent
```

Deserialize a dictionary to a PerpCloseIntent.

## StakeIntent

## almanak.framework.intents.StakeIntent

Bases: `AlmanakImmutableModel`

Intent to stake tokens with a liquid staking protocol.

StakeIntent represents staking tokens (like ETH) with a liquid staking protocol (like Lido or Ethena) to receive a liquid staking derivative (like stETH or sUSDe).

Attributes:

| Name              | Type            | Description                                                                                                            |
| ----------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `protocol`        | `str`           | Staking protocol (e.g., "lido", "ethena")                                                                              |
| `token_in`        | `str`           | Token to stake (e.g., "ETH" for Lido, "USDe" for Ethena)                                                               |
| `amount`          | `ChainedAmount` | Amount to stake, or "all" to use output from previous step                                                             |
| `receive_wrapped` | `bool`          | Whether to receive the wrapped version (e.g., wstETH instead of stETH). Default is True for better DeFi composability. |
| `chain`           | \`str           | None\`                                                                                                                 |
| `intent_id`       | `str`           | Unique identifier for this intent                                                                                      |
| `created_at`      | `datetime`      | Timestamp when the intent was created                                                                                  |

Note

When amount="all", the stake will use the entire output from the previous step in a sequence. This is useful for chaining operations like: swap -> stake or bridge -> stake.

Protocol-specific behavior:

- Lido: Stakes ETH, receives stETH (rebasing) or wstETH (non-rebasing)
- Ethena: Stakes USDe, receives sUSDe (ERC4626 vault)

Example

### Stake 1 ETH with Lido, receive wstETH (wrapped, non-rebasing)

intent = Intent.stake( protocol="lido", token_in="ETH", amount=Decimal("1"), receive_wrapped=True, # Get wstETH chain="ethereum", )

### Stake USDe with Ethena, receive sUSDe

intent = Intent.stake( protocol="ethena", token_in="USDe", amount=Decimal("10000"), chain="ethereum", )

### Stake all ETH from previous step

intent = Intent.stake( protocol="lido", token_in="ETH", amount="all", chain="ethereum", )

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_stake_intent

```
validate_stake_intent() -> StakeIntent
```

Validate stake parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> StakeIntent
```

Deserialize a dictionary to a StakeIntent.

## UnstakeIntent

## almanak.framework.intents.UnstakeIntent

Bases: `AlmanakImmutableModel`

Intent to unstake/withdraw tokens from a liquid staking protocol.

UnstakeIntent represents withdrawing staked tokens from a liquid staking protocol (like Lido or Ethena) to receive back the underlying tokens.

Attributes:

| Name         | Type            | Description                                                           |
| ------------ | --------------- | --------------------------------------------------------------------- |
| `protocol`   | `str`           | Staking protocol (e.g., "lido", "ethena")                             |
| `token_in`   | `str`           | Staked token to unstake (e.g., "wstETH" for Lido, "sUSDe" for Ethena) |
| `amount`     | `ChainedAmount` | Amount to unstake, or "all" to use output from previous step          |
| `chain`      | \`str           | None\`                                                                |
| `intent_id`  | `str`           | Unique identifier for this intent                                     |
| `created_at` | `datetime`      | Timestamp when the intent was created                                 |

Note

When amount="all", the unstake will use the entire output from the previous step in a sequence. This is useful for chaining operations.

Protocol-specific behavior:

- Lido: Unwrap wstETH to stETH, or request withdrawal from stETH
- Ethena: Initiates cooldown on sUSDe (unstaking has a cooldown period)

Example

### Unstake 1 wstETH with Lido

intent = Intent.unstake( protocol="lido", token_in="wstETH", amount=Decimal("1"), chain="ethereum", )

### Unstake sUSDe with Ethena (starts cooldown)

intent = Intent.unstake( protocol="ethena", token_in="sUSDe", amount=Decimal("10000"), chain="ethereum", )

### Unstake all tokens from previous step

intent = Intent.unstake( protocol="lido", token_in="wstETH", amount="all", chain="ethereum", )

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_unstake_intent

```
validate_unstake_intent() -> UnstakeIntent
```

Validate unstake parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> UnstakeIntent
```

Deserialize a dictionary to an UnstakeIntent.

## BridgeIntent

## almanak.framework.intents.BridgeIntent

Bases: `AlmanakImmutableModel`

Intent to bridge tokens from one chain to another.

BridgeIntent represents a cross-chain asset transfer. It can be used standalone or as part of an IntentSequence for complex multi-step operations.

When amount="all", the bridge will use the entire output from the previous step in a sequence. This is useful for chaining operations like swap -> bridge -> supply.

Attributes:

| Name               | Type            | Description                                                 |
| ------------------ | --------------- | ----------------------------------------------------------- |
| `token`            | `str`           | Token symbol to bridge (e.g., "ETH", "USDC", "WBTC")        |
| `amount`           | `ChainedAmount` | Amount to bridge (Decimal or "all" for chained amounts)     |
| `from_chain`       | `str`           | Source chain identifier (e.g., "base", "arbitrum")          |
| `to_chain`         | `str`           | Destination chain identifier (e.g., "arbitrum", "optimism") |
| `max_slippage`     | `SafeDecimal`   | Maximum acceptable slippage (e.g., 0.005 = 0.5%)            |
| `preferred_bridge` | \`str           | None\`                                                      |
| `intent_id`        | `str`           | Unique identifier for this intent                           |
| `created_at`       | `datetime`      | Timestamp when the intent was created                       |

Example

### Bridge 1000 USDC from Base to Arbitrum with 0.5% max slippage

intent = BridgeIntent( token="USDC", amount=Decimal("1000"), from_chain="base", to_chain="arbitrum", max_slippage=Decimal("0.005"), )

### Bridge all ETH from previous step output

intent = BridgeIntent( token="ETH", amount="all", from_chain="optimism", to_chain="arbitrum", )

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### chain

```
chain: str
```

Return the source chain for compatibility with Intent.get_chain().

For bridge intents, the 'chain' property returns the source chain (from_chain) since that's where the transaction originates.

### validate_destination_address

```
validate_destination_address(v: str | None) -> str | None
```

Validate destination_address is non-empty when provided.

### validate_token

```
validate_token(v: str) -> str
```

Validate token is non-empty.

### normalize_chain

```
normalize_chain(v: str) -> str
```

Validate and normalize chain names to lowercase.

### validate_bridge_intent

```
validate_bridge_intent() -> BridgeIntent
```

Validate bridge intent parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> BridgeIntent
```

Deserialize a dictionary to a BridgeIntent.

### validate_chains

```
validate_chains(
    from_chain: str,
    to_chain: str,
    configured_chains: Sequence[str],
) -> None
```

Validate that both chains are configured for the strategy.

Parameters:

| Name                | Type            | Description                                | Default    |
| ------------------- | --------------- | ------------------------------------------ | ---------- |
| `from_chain`        | `str`           | Source chain identifier                    | *required* |
| `to_chain`          | `str`           | Destination chain identifier               | *required* |
| `configured_chains` | `Sequence[str]` | List of chains configured for the strategy | *required* |

Raises:

| Type               | Description                       |
| ------------------ | --------------------------------- |
| `BridgeChainError` | If either chain is not configured |

## PredictionBuyIntent

## almanak.framework.intents.PredictionBuyIntent

Bases: `AlmanakImmutableModel`

Intent to buy shares in a prediction market.

This intent is used to buy outcome tokens (YES or NO) on Polymarket or similar prediction market platforms.

Attributes:

| Name               | Type                       | Description                                                       |
| ------------------ | -------------------------- | ----------------------------------------------------------------- |
| `market_id`        | `str`                      | Polymarket market ID or slug (e.g., "will-bitcoin-exceed-100000") |
| `outcome`          | `PredictionOutcome`        | Which outcome to buy ("YES" or "NO")                              |
| `amount_usd`       | `OptionalSafeDecimal`      | USDC amount to spend (mutually exclusive with shares)             |
| `shares`           | `OptionalSafeDecimal`      | Number of shares to buy (mutually exclusive with amount_usd)      |
| `max_price`        | `OptionalSafeDecimal`      | Maximum price per share (0.01-0.99) for limit orders              |
| `order_type`       | `PredictionOrderType`      | Order type ("market" or "limit")                                  |
| `time_in_force`    | `PredictionTimeInForce`    | How long order remains active ("GTC", "IOC", "FOK")               |
| `expiration_hours` | \`int                      | None\`                                                            |
| `protocol`         | `str`                      | Protocol to use (defaults to "polymarket")                        |
| `chain`            | \`str                      | None\`                                                            |
| `exit_conditions`  | \`PredictionExitConditions | None\`                                                            |
| `intent_id`        | `str`                      | Unique identifier for this intent                                 |
| `created_at`       | `datetime`                 | Timestamp when the intent was created                             |

Note

- Prices represent implied probability (0.65 = 65% chance of YES)
- Market orders use aggressive pricing for immediate execution
- Limit orders rest in the orderbook until matched or cancelled
- GTC (Good Till Cancelled) orders remain until filled or cancelled
- IOC (Immediate or Cancel) fills what it can immediately, cancels rest
- FOK (Fill or Kill) must fill entirely or is cancelled

Example

### Buy $100 worth of YES shares at market price

intent = Intent.prediction_buy( market_id="will-bitcoin-exceed-100000", outcome="YES", amount_usd=Decimal("100"), )

### Buy 50 YES shares with limit order at max price of $0.65

intent = Intent.prediction_buy( market_id="will-bitcoin-exceed-100000", outcome="YES", shares=Decimal("50"), max_price=Decimal("0.65"), order_type="limit", )

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_prediction_buy_intent

```
validate_prediction_buy_intent() -> PredictionBuyIntent
```

Validate prediction buy parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> PredictionBuyIntent
```

Deserialize a dictionary to a PredictionBuyIntent.

## PredictionSellIntent

## almanak.framework.intents.PredictionSellIntent

Bases: `AlmanakImmutableModel`

Intent to sell shares in a prediction market.

This intent is used to sell outcome tokens (YES or NO) on Polymarket or similar prediction market platforms.

Attributes:

| Name            | Type                    | Description                                                |
| --------------- | ----------------------- | ---------------------------------------------------------- |
| `market_id`     | `str`                   | Polymarket market ID or slug                               |
| `outcome`       | `PredictionOutcome`     | Which outcome to sell ("YES" or "NO")                      |
| `shares`        | `PredictionShareAmount` | Number of shares to sell, or "all" to sell entire position |
| `min_price`     | `OptionalSafeDecimal`   | Minimum price per share (0.01-0.99) for limit orders       |
| `order_type`    | `PredictionOrderType`   | Order type ("market" or "limit")                           |
| `time_in_force` | `PredictionTimeInForce` | How long order remains active ("GTC", "IOC", "FOK")        |
| `protocol`      | `str`                   | Protocol to use (defaults to "polymarket")                 |
| `chain`         | \`str                   | None\`                                                     |
| `intent_id`     | `str`                   | Unique identifier for this intent                          |
| `created_at`    | `datetime`              | Timestamp when the intent was created                      |

Note

- Use shares="all" to sell your entire position
- Market orders execute immediately at best available price
- Limit orders only execute at min_price or better

Example

### Sell all YES shares at market price

intent = Intent.prediction_sell( market_id="will-bitcoin-exceed-100000", outcome="YES", shares="all", )

### Sell 25 NO shares with limit order at min $0.40

intent = Intent.prediction_sell( market_id="will-bitcoin-exceed-100000", outcome="NO", shares=Decimal("25"), min_price=Decimal("0.40"), order_type="limit", )

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_prediction_sell_intent

```
validate_prediction_sell_intent() -> PredictionSellIntent
```

Validate prediction sell parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> PredictionSellIntent
```

Deserialize a dictionary to a PredictionSellIntent.

## PredictionRedeemIntent

## almanak.framework.intents.PredictionRedeemIntent

Bases: `AlmanakImmutableModel`

Intent to redeem winning prediction market positions.

This intent is used to redeem outcome tokens after a market has resolved. Winning tokens can be redeemed for $1 each (in USDC).

Attributes:

| Name         | Type                    | Description                                    |
| ------------ | ----------------------- | ---------------------------------------------- |
| `market_id`  | `str`                   | Polymarket market ID or slug                   |
| `outcome`    | \`PredictionOutcome     | None\`                                         |
| `shares`     | `PredictionShareAmount` | Number of shares to redeem, or "all" (default) |
| `protocol`   | `str`                   | Protocol to use (defaults to "polymarket")     |
| `chain`      | \`str                   | None\`                                         |
| `intent_id`  | `str`                   | Unique identifier for this intent              |
| `created_at` | `datetime`              | Timestamp when the intent was created          |

Note

- Redemption is only possible after the market has resolved
- Winning positions redeem for $1 per share
- Losing positions are worthless
- Use outcome=None to redeem all winning positions

Example

### Redeem all winning positions from a market

intent = Intent.prediction_redeem( market_id="will-bitcoin-exceed-100000", )

### Redeem only YES shares (if YES won)

intent = Intent.prediction_redeem( market_id="will-bitcoin-exceed-100000", outcome="YES", shares="all", )

### is_chained_amount

```
is_chained_amount: bool
```

Check if this intent uses a chained amount from previous step.

### intent_type

```
intent_type: IntentType
```

Return the type of this intent.

### validate_prediction_redeem_intent

```
validate_prediction_redeem_intent() -> (
    PredictionRedeemIntent
)
```

Validate prediction redeem parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> PredictionRedeemIntent
```

Deserialize a dictionary to a PredictionRedeemIntent.

## EnsureBalanceIntent

## almanak.framework.intents.EnsureBalanceIntent

Bases: `AlmanakImmutableModel`

Intent to ensure a minimum token balance on a target chain.

EnsureBalanceIntent is a high-level intent that expresses the goal of having at least a certain amount of tokens on a specific chain. The framework will automatically determine how to achieve this goal:

1. Check current balance on target chain
1. If balance >= min_amount: Return HoldIntent (no action needed)
1. If balance < min_amount: Find a source chain with sufficient balance
1. Generate a BridgeIntent to transfer the needed tokens

This simplifies strategy development by abstracting away the complexity of cross-chain balance management.

Attributes:

| Name               | Type          | Description                                                   |
| ------------------ | ------------- | ------------------------------------------------------------- |
| `token`            | `str`         | Token symbol to ensure (e.g., "ETH", "USDC", "WBTC")          |
| `min_amount`       | `SafeDecimal` | Minimum amount required on target chain                       |
| `target_chain`     | `str`         | Chain where the balance is needed (e.g., "arbitrum", "base")  |
| `max_slippage`     | `SafeDecimal` | Maximum acceptable slippage for bridging (e.g., 0.005 = 0.5%) |
| `preferred_bridge` | \`str         | None\`                                                        |
| `intent_id`        | `str`         | Unique identifier for this intent                             |
| `created_at`       | `datetime`    | Timestamp when the intent was created                         |

Example

### Ensure at least 1000 USDC on Arbitrum before opening a position

intent = EnsureBalanceIntent( token="USDC", min_amount=Decimal("1000"), target_chain="arbitrum", )

### Ensure at least 2 ETH on Base with specific slippage

intent = EnsureBalanceIntent( token="ETH", min_amount=Decimal("2"), target_chain="base", max_slippage=Decimal("0.01"), # 1% max slippage )

### intent_type

```
intent_type: EnsureBalanceIntentType
```

Return the type of this intent.

### chain

```
chain: str
```

Return the target chain for compatibility with Intent.get_chain().

For ensure_balance intents, the 'chain' property returns the target chain since that's where the balance needs to be ensured.

### validate_token

```
validate_token(v: str) -> str
```

Validate token is non-empty.

### normalize_chain

```
normalize_chain(v: str) -> str
```

Validate and normalize chain name to lowercase.

### validate_ensure_balance_intent

```
validate_ensure_balance_intent() -> EnsureBalanceIntent
```

Validate ensure balance intent parameters.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the intent to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> EnsureBalanceIntent
```

Deserialize a dictionary to an EnsureBalanceIntent.

### validate_target_chain

```
validate_target_chain(
    target_chain: str, configured_chains: Sequence[str]
) -> None
```

Validate that the target chain is configured for the strategy.

Parameters:

| Name                | Type            | Description                                | Default    |
| ------------------- | --------------- | ------------------------------------------ | ---------- |
| `target_chain`      | `str`           | Target chain identifier                    | *required* |
| `configured_chains` | `Sequence[str]` | List of chains configured for the strategy | *required* |

Raises:

| Type                        | Description                       |
| --------------------------- | --------------------------------- |
| `InvalidEnsureBalanceError` | If target chain is not configured |

### resolve

```
resolve(
    target_balance: Decimal,
    chain_balances: dict[str, Decimal],
) -> Union[HoldIntent, BridgeIntent]
```

Resolve this ensure_balance intent to a concrete intent.

This method determines the appropriate action based on current balances:

1. If target chain has sufficient balance -> HoldIntent
1. If a source chain has sufficient balance -> BridgeIntent
1. If no single chain has sufficient balance -> raise InsufficientBalanceError

Parameters:

| Name             | Type                 | Description                                                                     | Default    |
| ---------------- | -------------------- | ------------------------------------------------------------------------------- | ---------- |
| `target_balance` | `Decimal`            | Current balance of the token on the target chain                                | *required* |
| `chain_balances` | `dict[str, Decimal]` | Dict of {chain: balance} for all configured chains (excluding the target chain) | *required* |

Returns:

| Type                              | Description                                                       |
| --------------------------------- | ----------------------------------------------------------------- |
| `Union[HoldIntent, BridgeIntent]` | HoldIntent if no action needed, BridgeIntent if transfer required |

Raises:

| Type                       | Description                               |
| -------------------------- | ----------------------------------------- |
| `InsufficientBalanceError` | If no single chain has sufficient balance |

## IntentSequence

## almanak.framework.intents.IntentSequence

```
IntentSequence(
    intents: list[AnyIntent],
    sequence_id: str = (lambda: str(uuid.uuid4()))(),
    created_at: datetime = (lambda: datetime.now(UTC))(),
    description: str | None = None,
)
```

A sequence of intents that must execute in order (dependent actions).

IntentSequence wraps a list of intents that have dependencies between them and must execute sequentially. This is used when the output of one intent feeds into the input of the next (e.g., swap output -> bridge input).

Intents that are NOT in a sequence can execute in parallel if they are independent (e.g., two swaps on different chains).

Attributes:

| Name          | Type              | Description                             |
| ------------- | ----------------- | --------------------------------------- |
| `intents`     | `list[AnyIntent]` | List of intents to execute in order     |
| `sequence_id` | `str`             | Unique identifier for this sequence     |
| `created_at`  | `datetime`        | Timestamp when the sequence was created |
| `description` | \`str             | None\`                                  |

Example

### Create a sequence of dependent actions

sequence = Intent.sequence([ Intent.swap("USDC", "ETH", amount=Decimal("1000"), chain="base"), Intent.bridge(token="ETH", amount="all", from_chain="base", to_chain="arbitrum"), Intent.supply(protocol="aave_v3", token="WETH", amount="all", chain="arbitrum"), ])

### Return from decide() - will execute sequentially

return sequence

### first

```
first: AnyIntent
```

Get the first intent in the sequence.

### last

```
last: AnyIntent
```

Get the last intent in the sequence.

### __post_init__

```
__post_init__() -> None
```

Validate the sequence.

### __len__

```
__len__() -> int
```

Return the number of intents in the sequence.

### __iter__

```
__iter__()
```

Iterate over intents in the sequence.

### __getitem__

```
__getitem__(index: int) -> AnyIntent
```

Get intent at index.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the sequence to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> IntentSequence
```

Deserialize a dictionary to an IntentSequence.

Note: This requires the Intent.deserialize function to be available, which creates a circular dependency. The actual deserialization is done in the Intent class.

## ChainedAmount

## almanak.framework.intents.ChainedAmount

```
ChainedAmount = Decimal | Literal['all']
```

# Logging

Structured logging utilities built on `structlog`.

## configure_logging

## almanak.framework.utils.logging.configure_logging

```
configure_logging(
    level: LogLevel = LogLevel.INFO,
    format: LogFormat = LogFormat.JSON,
    stream: Any | None = None,
) -> None
```

Configure structured logging for the application.

This should be called once at application startup. Subsequent calls will reconfigure logging (useful for testing).

Parameters:

| Name     | Type        | Description                                                      | Default                                |
| -------- | ----------- | ---------------------------------------------------------------- | -------------------------------------- |
| `level`  | `LogLevel`  | The minimum log level to output                                  | `INFO`                                 |
| `format` | `LogFormat` | The output format (JSON for production, CONSOLE for development) | `JSON`                                 |
| `stream` | \`Any       | None\`                                                           | Output stream (defaults to sys.stdout) |

Example

### Local development

configure_logging(level=LogLevel.DEBUG, format=LogFormat.CONSOLE)

### Production

configure_logging(level=LogLevel.INFO, format=LogFormat.JSON)

## get_logger

## almanak.framework.utils.logging.get_logger

```
get_logger(name: str) -> structlog.stdlib.BoundLogger
```

Get a structured logger for the given module name.

This returns a structlog BoundLogger that wraps the stdlib logger. If logging hasn't been configured, it will use sensible defaults.

Parameters:

| Name   | Type  | Description                      | Default    |
| ------ | ----- | -------------------------------- | ---------- |
| `name` | `str` | The logger name (typically name) | *required* |

Returns:

| Type          | Description                  |
| ------------- | ---------------------------- |
| `BoundLogger` | A structured logger instance |

Example

logger = get_logger(**name**) logger.info("processing_started", strategy_id="momentum_v1")

## LogLevel

## almanak.framework.utils.logging.LogLevel

Bases: `StrEnum`

Log level enumeration.

## LogFormat

## almanak.framework.utils.logging.LogFormat

Bases: `StrEnum`

Log format enumeration.

## Context Management

## almanak.framework.utils.logging.add_context

```
add_context(**kwargs: Any) -> None
```

Add persistent context to all subsequent log messages.

Context is stored in a context variable and automatically added to all log messages. Use this for values like correlation_id or strategy_id that should appear in all logs.

Parameters:

| Name       | Type  | Description                       | Default |
| ---------- | ----- | --------------------------------- | ------- |
| `**kwargs` | `Any` | Key-value pairs to add to context | `{}`    |

Example

add_context(correlation_id="abc-123", strategy_id="momentum_v1") logger.info("processing") # Will include correlation_id and strategy_id

## almanak.framework.utils.logging.clear_context

```
clear_context() -> None
```

Clear all context variables.

Call this to reset the logging context, typically at the start of a new request or iteration.

Example

clear_context() add_context(correlation_id="new-123")

## almanak.framework.utils.logging.with_context

```
with_context(**kwargs: Any) -> AbstractContextManager[None]
```

Context manager for temporary logging context.

Adds context for the duration of a block, then removes it.

Parameters:

| Name       | Type  | Description                        | Default |
| ---------- | ----- | ---------------------------------- | ------- |
| `**kwargs` | `Any` | Key-value pairs to add temporarily | `{}`    |

Example

with with_context(tx_hash="0x123"): logger.info("signing") # Includes tx_hash logger.info("submitting") # Includes tx_hash logger.info("done") # Does not include tx_hash

Returns:

| Type                           | Description     |
| ------------------------------ | --------------- |
| `AbstractContextManager[None]` | Context manager |

# Services

Operational services for strategy monitoring, stuck detection, and emergency management.

## StuckDetector

Detects when a strategy is stuck and unable to make progress.

## almanak.framework.services.StuckDetector

```
StuckDetector(
    stuck_threshold_seconds: int = DEFAULT_STUCK_THRESHOLD_SECONDS,
    emit_events: bool = True,
)
```

Detects and classifies stuck strategies.

The detector analyzes various aspects of strategy state to determine:

1. Whether the strategy is stuck (in same state too long)
1. Why it's stuck (classification using StuckReason)

Detection is performed by checking:

- Pending transactions and gas prices
- Token balances and gas availability
- Token allowances
- Market conditions (slippage, liquidity)
- Protocol status (oracle freshness, paused state)
- RPC health
- Risk guard and circuit breaker state

Initialize the stuck detector.

Parameters:

| Name                      | Type   | Description                                                                 | Default                           |
| ------------------------- | ------ | --------------------------------------------------------------------------- | --------------------------------- |
| `stuck_threshold_seconds` | `int`  | Time in seconds before a strategy is considered stuck (default 10 minutes). | `DEFAULT_STUCK_THRESHOLD_SECONDS` |
| `emit_events`             | `bool` | Whether to emit timeline events when stuck is detected.                     | `True`                            |

### detect_stuck

```
detect_stuck(
    snapshot: StrategySnapshot,
) -> StuckDetectionResult
```

Detect if a strategy is stuck and classify the reason.

This is the main entry point for stuck detection. It checks if the strategy has been in the same state longer than the threshold, and if so, attempts to classify the reason.

Parameters:

| Name       | Type               | Description                     | Default    |
| ---------- | ------------------ | ------------------------------- | ---------- |
| `snapshot` | `StrategySnapshot` | Current strategy state snapshot | *required* |

Returns:

| Type                   | Description                                                     |
| ---------------------- | --------------------------------------------------------------- |
| `StuckDetectionResult` | StuckDetectionResult with stuck status and reason if applicable |

## EmergencyManager

Handles emergency scenarios like position unwinding.

## almanak.framework.services.EmergencyManager

```
EmergencyManager(
    alert_manager: AlertManager | None = None,
    pause_callback: PauseStrategyCallback | None = None,
    position_callback: GetPositionCallback | None = None,
    dashboard_base_url: str | None = None,
)
```

Manages emergency stop procedures for strategies.

The EmergencyManager handles the full emergency stop workflow:

1. Immediately pause the strategy
1. Gather complete position summary
1. Generate EMERGENCY_STOP OperatorCard with suggested actions
1. Send CRITICAL alerts to configured channels
1. Return complete EmergencyResult

Attributes:

| Name                | Type | Description                              |
| ------------------- | ---- | ---------------------------------------- |
| `alert_manager`     |      | AlertManager for sending CRITICAL alerts |
| `pause_callback`    |      | Callback to pause a strategy             |
| `position_callback` |      | Callback to get position summary         |

Initialize the EmergencyManager.

Parameters:

| Name                 | Type                    | Description | Default                                   |
| -------------------- | ----------------------- | ----------- | ----------------------------------------- |
| `alert_manager`      | \`AlertManager          | None\`      | AlertManager instance for sending alerts  |
| `pause_callback`     | \`PauseStrategyCallback | None\`      | Callback function to pause a strategy     |
| `position_callback`  | \`GetPositionCallback   | None\`      | Callback function to get position summary |
| `dashboard_base_url` | \`str                   | None\`      | Base URL for dashboard links              |

### emergency_stop

```
emergency_stop(
    strategy_id: str,
    reason: str,
    chain: str = "",
    trigger_context: dict[str, Any] | None = None,
) -> EmergencyResult
```

Execute an emergency stop for a strategy.

This method:

1. Immediately pauses the strategy
1. Gets the full position summary (all tokens, LP positions, borrows)
1. Generates an OperatorCard with EMERGENCY_STOP event
1. Suggests actions: EMERGENCY_UNWIND, MANUAL_REVIEW
1. Sends CRITICAL alerts
1. Returns EmergencyResult with position summary

Parameters:

| Name              | Type             | Description                                     | Default                                                        |
| ----------------- | ---------------- | ----------------------------------------------- | -------------------------------------------------------------- |
| `strategy_id`     | `str`            | The ID of the strategy to stop                  | *required*                                                     |
| `reason`          | `str`            | Human-readable reason for the emergency stop    | *required*                                                     |
| `chain`           | `str`            | The blockchain network the strategy operates on | `''`                                                           |
| `trigger_context` | \`dict[str, Any] | None\`                                          | Optional additional context about what triggered the emergency |

Returns:

| Type              | Description                                                       |
| ----------------- | ----------------------------------------------------------------- |
| `EmergencyResult` | EmergencyResult with full details of the emergency stop operation |

### emergency_stop_async

```
emergency_stop_async(
    strategy_id: str,
    reason: str,
    chain: str = "",
    trigger_context: dict[str, Any] | None = None,
) -> EmergencyResult
```

Async version of emergency_stop.

Parameters:

| Name              | Type             | Description                                     | Default                                                        |
| ----------------- | ---------------- | ----------------------------------------------- | -------------------------------------------------------------- |
| `strategy_id`     | `str`            | The ID of the strategy to stop                  | *required*                                                     |
| `reason`          | `str`            | Human-readable reason for the emergency stop    | *required*                                                     |
| `chain`           | `str`            | The blockchain network the strategy operates on | `''`                                                           |
| `trigger_context` | \`dict[str, Any] | None\`                                          | Optional additional context about what triggered the emergency |

Returns:

| Type              | Description                                                       |
| ----------------- | ----------------------------------------------------------------- |
| `EmergencyResult` | EmergencyResult with full details of the emergency stop operation |

## OperatorCardGenerator

Generates operator cards for strategy issues.

## almanak.framework.services.OperatorCardGenerator

```
OperatorCardGenerator(
    dashboard_base_url: str | None = None,
)
```

Generates OperatorCards from strategy state and events.

The generator automatically:

- Detects the StuckReason from error type and context
- Calculates severity based on position at risk and time stuck
- Looks up suggested actions from REMEDIATION_MAP
- Generates human-readable risk descriptions
- Sets up auto-remediation when applicable

Initialize the generator.

Parameters:

| Name                 | Type  | Description | Default                                            |
| -------------------- | ----- | ----------- | -------------------------------------------------- |
| `dashboard_base_url` | \`str | None\`      | Base URL for dashboard links in risk descriptions. |

### generate_card

```
generate_card(
    strategy_state: StrategyState,
    error_context: ErrorContext | None = None,
    event_type: EventType = EventType.STUCK,
) -> OperatorCard
```

Generate an OperatorCard from strategy state and error context.

Parameters:

| Name             | Type            | Description                        | Default                                               |
| ---------------- | --------------- | ---------------------------------- | ----------------------------------------------------- |
| `strategy_state` | `StrategyState` | Current state of the strategy.     | *required*                                            |
| `error_context`  | \`ErrorContext  | None\`                             | Optional context about the error that triggered this. |
| `event_type`     | `EventType`     | Type of event (defaults to STUCK). | `STUCK`                                               |

Returns:

| Type           | Description                     |
| -------------- | ------------------------------- |
| `OperatorCard` | A fully populated OperatorCard. |

## PredictionPositionMonitor

Monitors prediction market positions for resolution events.

## almanak.framework.services.PredictionPositionMonitor

```
PredictionPositionMonitor(
    strategy_id: str = "",
    check_interval: int = DEFAULT_CHECK_INTERVAL,
    emit_events: bool = True,
    event_callback: EventCallback | None = None,
    default_exit_before_resolution_hours: int | None = None,
    allow_partial_exits: bool = True,
)
```

Monitors prediction market positions for lifecycle events.

The monitor tracks positions and evaluates exit conditions:

- Market resolution detection
- Price threshold alerts (stop-loss, take-profit)
- Time-based alerts (approaching resolution)
- Trailing stop calculations
- Liquidity and spread warnings
- Partial exits when liquidity is insufficient

Example

monitor = PredictionPositionMonitor( strategy_id="my-strategy", check_interval=60, default_exit_before_resolution_hours=24, # Strategy-level default )

### Add a position to monitor

monitor.add_position( position=MonitoredPosition( market_id="will-btc-reach-100k", condition_id="0x...", token_id="12345...", outcome="YES", size=Decimal("100"), entry_price=Decimal("0.65"), entry_time=datetime.now(UTC), exit_conditions=PredictionExitConditions( stop_loss_price=Decimal("0.50"), take_profit_price=Decimal("0.85"), exit_before_resolution_hours=24, ), ), )

### Check positions (call periodically)

results = monitor.check_positions(snapshots) for result in results: if result.triggered: print(f"Event: {result.event}, Action: {result.suggested_action}")

Initialize the position monitor.

Parameters:

| Name                                   | Type            | Description                                                                                                              | Default                                                                                                                                              |
| -------------------------------------- | --------------- | ------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| `strategy_id`                          | `str`           | Strategy identifier for event emission.                                                                                  | `''`                                                                                                                                                 |
| `check_interval`                       | `int`           | Seconds between position checks.                                                                                         | `DEFAULT_CHECK_INTERVAL`                                                                                                                             |
| `emit_events`                          | `bool`          | Whether to emit timeline events.                                                                                         | `True`                                                                                                                                               |
| `event_callback`                       | \`EventCallback | None\`                                                                                                                   | Optional callback for events.                                                                                                                        |
| `default_exit_before_resolution_hours` | \`int           | None\`                                                                                                                   | Strategy-level default for exit_before_resolution_hours. Applied to positions that don't have their own exit conditions or don't specify this value. |
| `allow_partial_exits`                  | `bool`          | If True, generate partial sell intents when orderbook liquidity is insufficient for the full position. Defaults to True. | `True`                                                                                                                                               |

### positions

```
positions: dict[str, MonitoredPosition]
```

Get all monitored positions.

### add_position

```
add_position(position: MonitoredPosition) -> None
```

Add a position to monitor.

Parameters:

| Name       | Type                | Description              | Default    |
| ---------- | ------------------- | ------------------------ | ---------- |
| `position` | `MonitoredPosition` | The position to monitor. | *required* |

### remove_position

```
remove_position(market_id: str) -> MonitoredPosition | None
```

Remove a position from monitoring.

Parameters:

| Name        | Type  | Description          | Default    |
| ----------- | ----- | -------------------- | ---------- |
| `market_id` | `str` | Market ID to remove. | *required* |

Returns:

| Type                | Description |
| ------------------- | ----------- |
| \`MonitoredPosition | None\`      |

### get_position

```
get_position(market_id: str) -> MonitoredPosition | None
```

Get a monitored position by market ID.

Parameters:

| Name        | Type  | Description           | Default    |
| ----------- | ----- | --------------------- | ---------- |
| `market_id` | `str` | Market ID to look up. | *required* |

Returns:

| Type                | Description |
| ------------------- | ----------- |
| \`MonitoredPosition | None\`      |

### update_position_price

```
update_position_price(
    market_id: str, current_price: Decimal
) -> None
```

Update the current price for a position.

This also updates the highest_price for trailing stop tracking.

Parameters:

| Name            | Type      | Description          | Default    |
| --------------- | --------- | -------------------- | ---------- |
| `market_id`     | `str`     | Market ID to update. | *required* |
| `current_price` | `Decimal` | New current price.   | *required* |

### check_position

```
check_position(
    position: MonitoredPosition, snapshot: PositionSnapshot
) -> MonitoringResult
```

Check a single position against its exit conditions.

Evaluates all exit conditions and returns the most urgent triggered event, if any.

Parameters:

| Name       | Type                | Description                   | Default    |
| ---------- | ------------------- | ----------------------------- | ---------- |
| `position` | `MonitoredPosition` | The position to check.        | *required* |
| `snapshot` | `PositionSnapshot`  | Current market data snapshot. | *required* |

Returns:

| Type               | Description                                 |
| ------------------ | ------------------------------------------- |
| `MonitoringResult` | MonitoringResult with any triggered events. |

### check_positions

```
check_positions(
    snapshots: dict[str, PositionSnapshot],
) -> list[MonitoringResult]
```

Check all monitored positions against provided snapshots.

Parameters:

| Name        | Type                          | Description                               | Default    |
| ----------- | ----------------------------- | ----------------------------------------- | ---------- |
| `snapshots` | `dict[str, PositionSnapshot]` | Market data snapshots keyed by market_id. | *required* |

Returns:

| Type                     | Description                                   |
| ------------------------ | --------------------------------------------- |
| `list[MonitoringResult]` | List of monitoring results for all positions. |

### clear

```
clear() -> None
```

Clear all monitored positions.

### calculate_safe_exit_size

```
calculate_safe_exit_size(
    position: MonitoredPosition, snapshot: PositionSnapshot
) -> tuple[Decimal | None, bool]
```

Calculate the safe exit size based on available liquidity.

When orderbook depth is insufficient for the full position, this method calculates a partial exit size that respects the available liquidity.

Parameters:

| Name       | Type                | Description                                    | Default    |
| ---------- | ------------------- | ---------------------------------------------- | ---------- |
| `position` | `MonitoredPosition` | The position to potentially exit.              | *required* |
| `snapshot` | `PositionSnapshot`  | Current market data including orderbook depth. | *required* |

Returns:

| Type             | Description                                                    |
| ---------------- | -------------------------------------------------------------- |
| \`Decimal        | None\`                                                         |
| `bool`           | (None, False): No orderbook data available, proceed with "all" |
| \`tuple\[Decimal | None, bool\]\`                                                 |
| \`tuple\[Decimal | None, bool\]\`                                                 |
| \`tuple\[Decimal | None, bool\]\`                                                 |
| \`tuple\[Decimal | None, bool\]\`                                                 |
| \`tuple\[Decimal | None, bool\]\`                                                 |

### generate_sell_intent

```
generate_sell_intent(
    result: MonitoringResult,
    snapshot: PositionSnapshot | None = None,
) -> PredictionSellIntent | None
```

Generate a PredictionSellIntent for a triggered exit condition.

When an exit condition is triggered (stop-loss, take-profit, trailing stop, or pre-resolution exit), this method generates the appropriate sell intent to exit the position.

If partial exits are enabled and the snapshot indicates insufficient liquidity, this method will generate a partial sell intent instead of a full exit.

Parameters:

| Name       | Type               | Description                                            | Default                                                                                                                                                                                |
| ---------- | ------------------ | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `result`   | `MonitoringResult` | The monitoring result with a triggered exit condition. | *required*                                                                                                                                                                             |
| `snapshot` | \`PositionSnapshot | None\`                                                 | Optional market snapshot for calculating partial exit size. When provided and allow_partial_exits is True, the method will check orderbook depth and generate partial exits if needed. |

Returns:

| Type                   | Description |
| ---------------------- | ----------- |
| \`PredictionSellIntent | None\`      |
| \`PredictionSellIntent | None\`      |
| \`PredictionSellIntent | None\`      |

Example

results = monitor.check_positions(snapshots) for result in results: if result.triggered and result.suggested_action == "SELL": snapshot = snapshots.get(result.position.market_id) sell_intent = monitor.generate_sell_intent(result, snapshot) if sell_intent:

# Execute the sell intent

compiler.compile(sell_intent)

## AutoRedemptionService

Automatically redeems resolved prediction market positions.

## almanak.framework.services.AutoRedemptionService

```
AutoRedemptionService(
    sdk: Any,
    private_key: str,
    strategy_id: str = "",
    enabled: bool = True,
    max_retries: int = DEFAULT_MAX_RETRIES,
    retry_delay_seconds: int = DEFAULT_RETRY_DELAY_SECONDS,
    emit_events: bool = True,
    redemption_callback: RedemptionCallback | None = None,
    receipt_timeout_seconds: int = DEFAULT_RECEIPT_TIMEOUT_SECONDS,
    receipt_poll_interval_seconds: float = DEFAULT_RECEIPT_POLL_INTERVAL_SECONDS,
)
```

Automatically redeems winning prediction market positions.

This service listens for MARKET_RESOLVED events and automatically builds and submits redemption transactions for winning positions.

Key features:

- Configurable per-strategy enable/disable
- Retry logic with exponential backoff
- Timeline event emission for tracking
- Callback support for custom handling
- Transaction receipt verification with configurable timeout
- Proper extraction of redemption amounts from PayoutRedemption events

Thread Safety

This class is NOT thread-safe. Use separate instances per thread.

Example

> > > service = AutoRedemptionService( ... sdk=sdk, ... private_key="0x...", ... strategy_id="my-strategy", ... enabled=True, ... receipt_timeout_seconds=120, ... )
> > >
> > > ### Handle market resolution
> > >
> > > event = MarketResolvedEvent(position=pos, winning_outcome="YES", is_winner=True) result = service.on_market_resolved(event) if result.status == RedemptionStatus.SUCCESS: ... print(f"Redeemed {result.amount_received} USDC")

Initialize the auto-redemption service.

Parameters:

| Name                            | Type                 | Description                                                 | Default                                   |
| ------------------------------- | -------------------- | ----------------------------------------------------------- | ----------------------------------------- |
| `sdk`                           | `Any`                | PolymarketSDK instance for redemption operations.           | *required*                                |
| `private_key`                   | `str`                | Private key for signing redemption transactions.            | *required*                                |
| `strategy_id`                   | `str`                | Strategy identifier for event emission.                     | `''`                                      |
| `enabled`                       | `bool`               | Whether auto-redemption is enabled.                         | `True`                                    |
| `max_retries`                   | `int`                | Maximum number of retry attempts.                           | `DEFAULT_MAX_RETRIES`                     |
| `retry_delay_seconds`           | `int`                | Initial delay between retries.                              | `DEFAULT_RETRY_DELAY_SECONDS`             |
| `emit_events`                   | `bool`               | Whether to emit timeline events.                            | `True`                                    |
| `redemption_callback`           | \`RedemptionCallback | None\`                                                      | Optional callback for redemption results. |
| `receipt_timeout_seconds`       | `int`                | Timeout for waiting for transaction receipt (default 120s). | `DEFAULT_RECEIPT_TIMEOUT_SECONDS`         |
| `receipt_poll_interval_seconds` | `float`              | Interval between receipt polling attempts (default 2s).     | `DEFAULT_RECEIPT_POLL_INTERVAL_SECONDS`   |

### redemptions

```
redemptions: dict[str, RedemptionAttempt]
```

Get all redemption attempts.

### get_redemption

```
get_redemption(market_id: str) -> RedemptionAttempt | None
```

Get redemption attempt for a market.

Parameters:

| Name        | Type  | Description           | Default    |
| ----------- | ----- | --------------------- | ---------- |
| `market_id` | `str` | Market ID to look up. | *required* |

Returns:

| Type                | Description |
| ------------------- | ----------- |
| \`RedemptionAttempt | None\`      |

### on_event

```
on_event(
    position: MonitoredPosition,
    event: PredictionEvent,
    details: dict[str, Any],
) -> None
```

Handle prediction events from the position monitor.

This method can be registered as a callback with the PredictionPositionMonitor to automatically handle events.

Parameters:

| Name       | Type                | Description                            | Default    |
| ---------- | ------------------- | -------------------------------------- | ---------- |
| `position` | `MonitoredPosition` | The position that triggered the event. | *required* |
| `event`    | `PredictionEvent`   | The type of event.                     | *required* |
| `details`  | `dict[str, Any]`    | Additional event details.              | *required* |

### on_market_resolved

```
on_market_resolved(
    event: MarketResolvedEvent,
) -> RedemptionAttempt
```

Handle market resolution and redeem winning positions.

This method is called when a market is resolved. It checks if the position is a winner and initiates redemption if enabled.

Parameters:

| Name    | Type                  | Description                                      | Default    |
| ------- | --------------------- | ------------------------------------------------ | ---------- |
| `event` | `MarketResolvedEvent` | The market resolved event with position details. | *required* |

Returns:

| Type                | Description                                       |
| ------------------- | ------------------------------------------------- |
| `RedemptionAttempt` | RedemptionAttempt tracking the redemption status. |

### enable

```
enable() -> None
```

Enable auto-redemption.

### disable

```
disable() -> None
```

Disable auto-redemption.

### clear_history

```
clear_history() -> None
```

Clear redemption history.

## Models

### StuckDetectionResult

## almanak.framework.services.StuckDetectionResult

```
StuckDetectionResult(
    is_stuck: bool,
    reason: StuckReason | None = None,
    time_in_state_seconds: float = 0,
    details: dict[str, Any] | None = None,
)
```

Result of stuck detection analysis.

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

### StrategySnapshot

## almanak.framework.services.StrategySnapshot

```
StrategySnapshot(
    strategy_id: str,
    chain: str,
    current_state: str,
    state_entered_at: datetime,
    pending_transactions: list[PendingTransaction],
    current_gas_price: int | None = None,
    balance_info: BalanceInfo | None = None,
    allowance_issues: list[AllowanceInfo] | None = None,
    current_slippage: Decimal | None = None,
    max_allowed_slippage: Decimal | None = None,
    pool_liquidity_usd: Decimal | None = None,
    oracle_last_updated: datetime | None = None,
    protocol_paused: bool = False,
    rpc_healthy: bool = True,
    last_rpc_error: str | None = None,
    risk_guard_blocked: bool = False,
    risk_guard_reason: str | None = None,
    circuit_breaker_triggered: bool = False,
)
```

Snapshot of strategy state for stuck detection.

This represents all the information needed to detect and classify a stuck strategy.

### EmergencyResult

## almanak.framework.services.EmergencyResult

```
EmergencyResult(
    success: bool,
    strategy_id: str,
    timestamp: datetime,
    position_summary: FullPositionSummary,
    operator_card: OperatorCard,
    alert_result: AlertSendResult | None = None,
    error: str | None = None,
    pause_successful: bool = False,
    alerts_sent: bool = False,
)
```

Result of an emergency stop operation.

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert the emergency result to a dictionary for serialization.

### FullPositionSummary

## almanak.framework.services.FullPositionSummary

```
FullPositionSummary(
    strategy_id: str,
    chain: str,
    timestamp: datetime,
    token_positions: list[TokenPosition] = list(),
    lp_positions: list[LPPositionInfo] = list(),
    borrow_positions: list[BorrowPosition] = list(),
    total_token_value_usd: Decimal = Decimal("0"),
    total_lp_value_usd: Decimal = Decimal("0"),
    total_collateral_value_usd: Decimal = Decimal("0"),
    total_borrowed_value_usd: Decimal = Decimal("0"),
)
```

Complete position summary including all tokens, LP positions, and borrows.

### total_value_usd

```
total_value_usd: Decimal
```

Calculate total portfolio value in USD.

### net_exposure_usd

```
net_exposure_usd: Decimal
```

Calculate net exposure (total value - borrowed).

### has_lp_positions

```
has_lp_positions: bool
```

Check if strategy has any LP positions.

### has_borrow_positions

```
has_borrow_positions: bool
```

Check if strategy has any borrow positions.

### min_health_factor

```
min_health_factor: Decimal | None
```

Get the minimum health factor across all borrow positions.

### to_position_summary

```
to_position_summary() -> PositionSummary
```

Convert to the standard PositionSummary format for OperatorCard.

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert the full position summary to a dictionary for serialization.

# State Management

Three-tier state persistence for strategy data.

## StateManager

## almanak.framework.state.StateManager

```
StateManager(
    config: StateManagerConfig | None = None,
    warm_backend: WarmStore | None = None,
)
```

Tiered state manager with HOT and WARM storage tiers.

Provides:

- \<1ms access from HOT (in-memory) cache
- \<10ms access from WARM (PostgreSQL or SQLite) storage
- CAS semantics for safe concurrent updates
- Automatic tier fallback on load
- Metrics tracking for each tier
- Write-through from HOT to WARM tier

The WARM tier backend can be either PostgreSQL (production) or SQLite (development/lightweight). Backend selection is via configuration:

Usage

### PostgreSQL backend (default, production)

config = StateManagerConfig( warm_backend=WarmBackendType.POSTGRESQL, postgres_config=PostgresConfig(host="localhost"), ) manager = StateManager(config) await manager.initialize()

### SQLite backend (development)

config = StateManagerConfig( warm_backend=WarmBackendType.SQLITE, sqlite_config=SQLiteConfigLight(db_path="./state.db"), ) manager = StateManager(config) await manager.initialize()

### Save state (writes to HOT then WARM)

state = StateData(strategy_id="strat-1", version=1, state={"key": "value"}) await manager.save_state(state)

### Load state (reads from fastest available tier)

loaded = await manager.load_state("strat-1")

### CAS update

loaded.state["key"] = "new_value" await manager.save_state(loaded, expected_version=loaded.version)

### Dependency injection: provide custom backend

custom_sqlite = SQLiteStore(SQLiteConfig(db_path="./custom.db")) manager = StateManager(config, warm_backend=custom_sqlite)

Initialize StateManager.

Parameters:

| Name           | Type                 | Description | Default                                                                                                                                                        |
| -------------- | -------------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `config`       | \`StateManagerConfig | None\`      | Configuration for the state manager. Uses defaults if not provided.                                                                                            |
| `warm_backend` | \`WarmStore          | None\`      | Optional pre-configured WARM tier backend. If provided, this backend is used instead of creating one from config. Useful for dependency injection and testing. |

### is_initialized

```
is_initialized: bool
```

Check if StateManager is initialized.

### enabled_tiers

```
enabled_tiers: list[StateTier]
```

Get list of enabled and initialized tiers.

### warm_backend_type

```
warm_backend_type: WarmBackendType | None
```

Get the type of WARM backend being used.

Returns:

| Type              | Description |
| ----------------- | ----------- |
| \`WarmBackendType | None\`      |

### warm_backend

```
warm_backend: WarmStore | None
```

Get the WARM tier backend instance.

Useful for accessing backend-specific functionality like get_version_history() on SQLiteStore.

Returns:

| Type        | Description |
| ----------- | ----------- |
| \`WarmStore | None\`      |

### initialize

```
initialize() -> None
```

Initialize all enabled storage tiers.

If load_state_on_startup is enabled in config, loads all active states from WARM tier to HOT tier for fast access.

### close

```
close() -> None
```

Close all storage connections.

### load_state

```
load_state(strategy_id: str) -> StateData
```

Load state from the fastest available tier.

Tries tiers in order: HOT -> WARM. Populates HOT cache on WARM hit.

Parameters:

| Name          | Type  | Description         | Default    |
| ------------- | ----- | ------------------- | ---------- |
| `strategy_id` | `str` | Strategy identifier | *required* |

Returns:

| Type        | Description                               |
| ----------- | ----------------------------------------- |
| `StateData` | StateData from the fastest available tier |

Raises:

| Type                 | Description                    |
| -------------------- | ------------------------------ |
| `StateNotFoundError` | If state not found in any tier |

### save_state

```
save_state(
    state: StateData, expected_version: int | None = None
) -> StateData
```

Save state to all tiers.

Writes to WARM tier (source of truth) then updates HOT cache.

Parameters:

| Name               | Type        | Description        | Default                                                                                                                                           |
| ------------------ | ----------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `state`            | `StateData` | State data to save | *required*                                                                                                                                        |
| `expected_version` | \`int       | None\`             | Expected version for CAS update. If None and state has version > 1, uses state.version - 1. If None and state has version = 1, creates new state. |

Returns:

| Type        | Description                        |
| ----------- | ---------------------------------- |
| `StateData` | Updated StateData with new version |

Raises:

| Type                 | Description                                 |
| -------------------- | ------------------------------------------- |
| `StateConflictError` | If CAS update fails due to version mismatch |

### delete_state

```
delete_state(strategy_id: str) -> bool
```

Delete state from all tiers.

Parameters:

| Name          | Type  | Description         | Default    |
| ------------- | ----- | ------------------- | ---------- |
| `strategy_id` | `str` | Strategy identifier | *required* |

Returns:

| Type   | Description                                      |
| ------ | ------------------------------------------------ |
| `bool` | True if state was deleted from at least one tier |

### invalidate_hot_cache

```
invalidate_hot_cache(
    strategy_id: str | None = None,
) -> None
```

Invalidate HOT tier cache.

Parameters:

| Name          | Type  | Description | Default                                               |
| ------------- | ----- | ----------- | ----------------------------------------------------- |
| `strategy_id` | \`str | None\`      | Specific strategy to invalidate, or None to clear all |

### get_metrics

```
get_metrics(limit: int = 100) -> list[TierMetrics]
```

Get recent tier metrics.

Parameters:

| Name    | Type  | Description                         | Default |
| ------- | ----- | ----------------------------------- | ------- |
| `limit` | `int` | Maximum number of metrics to return | `100`   |

Returns:

| Type                | Description                       |
| ------------------- | --------------------------------- |
| `list[TierMetrics]` | List of TierMetrics, newest first |

### get_metrics_summary

```
get_metrics_summary() -> dict[str, Any]
```

Get summary of tier metrics.

Returns:

| Type             | Description                                                  |
| ---------------- | ------------------------------------------------------------ |
| `dict[str, Any]` | Dictionary with per-tier average latencies and success rates |

### clear_metrics

```
clear_metrics() -> None
```

Clear all stored metrics.

### save_clob_order

```
save_clob_order(order: ClobOrderState) -> bool
```

Save or update a CLOB order state.

Persists order state to the WARM tier for crash recovery and order tracking across strategy restarts.

Parameters:

| Name    | Type             | Description                | Default    |
| ------- | ---------------- | -------------------------- | ---------- |
| `order` | `ClobOrderState` | ClobOrderState to persist. | *required* |

Returns:

| Type   | Description                                                |
| ------ | ---------------------------------------------------------- |
| `bool` | True if save succeeded, False if no WARM backend or error. |

### get_clob_order

```
get_clob_order(order_id: str) -> ClobOrderState | None
```

Get a CLOB order by order_id.

Parameters:

| Name       | Type  | Description       | Default    |
| ---------- | ----- | ----------------- | ---------- |
| `order_id` | `str` | Order identifier. | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`ClobOrderState | None\`      |

### get_open_clob_orders

```
get_open_clob_orders(
    market_id: str | None = None,
) -> list[ClobOrderState]
```

Get all open CLOB orders, optionally filtered by market.

Open orders are those with status: pending, submitted, live, partially_filled.

Parameters:

| Name        | Type  | Description | Default                          |
| ----------- | ----- | ----------- | -------------------------------- |
| `market_id` | \`str | None\`      | Optional market ID to filter by. |

Returns:

| Type                   | Description                                |
| ---------------------- | ------------------------------------------ |
| `list[ClobOrderState]` | List of open ClobOrderState, newest first. |

### update_clob_order_status

```
update_clob_order_status(
    order_id: str,
    status: ClobOrderStatus,
    fills: list[ClobFill] | None = None,
    filled_size: str | None = None,
    average_fill_price: str | None = None,
    error: str | None = None,
) -> bool
```

Update the status and fill information of a CLOB order.

Parameters:

| Name                 | Type              | Description       | Default                                    |
| -------------------- | ----------------- | ----------------- | ------------------------------------------ |
| `order_id`           | `str`             | Order identifier. | *required*                                 |
| `status`             | `ClobOrderStatus` | New order status. | *required*                                 |
| `fills`              | \`list[ClobFill]  | None\`            | Updated list of fills (replaces existing). |
| `filled_size`        | \`str             | None\`            | Updated filled size.                       |
| `average_fill_price` | \`str             | None\`            | Updated average fill price.                |
| `error`              | \`str             | None\`            | Error message if order failed.             |

Returns:

| Type   | Description                          |
| ------ | ------------------------------------ |
| `bool` | True if order was found and updated. |

### save_portfolio_snapshot

```
save_portfolio_snapshot(snapshot: PortfolioSnapshot) -> int
```

Save a portfolio snapshot.

Persists portfolio value and position data for dashboard display and PnL tracking.

Parameters:

| Name       | Type                | Description                   | Default    |
| ---------- | ------------------- | ----------------------------- | ---------- |
| `snapshot` | `PortfolioSnapshot` | PortfolioSnapshot to persist. | *required* |

Returns:

| Type  | Description                                                   |
| ----- | ------------------------------------------------------------- |
| `int` | Snapshot ID if save succeeded, 0 if no WARM backend or error. |

### get_latest_snapshot

```
get_latest_snapshot(
    strategy_id: str,
) -> PortfolioSnapshot | None
```

Get most recent portfolio snapshot for a strategy.

Parameters:

| Name          | Type  | Description          | Default    |
| ------------- | ----- | -------------------- | ---------- |
| `strategy_id` | `str` | Strategy identifier. | *required* |

Returns:

| Type                | Description |
| ------------------- | ----------- |
| \`PortfolioSnapshot | None\`      |

### get_snapshots_since

```
get_snapshots_since(
    strategy_id: str, since: datetime, limit: int = 168
) -> list[PortfolioSnapshot]
```

Get portfolio snapshots since a timestamp (for charts).

Parameters:

| Name          | Type       | Description                            | Default    |
| ------------- | ---------- | -------------------------------------- | ---------- |
| `strategy_id` | `str`      | Strategy identifier.                   | *required* |
| `since`       | `datetime` | Start timestamp for query.             | *required* |
| `limit`       | `int`      | Maximum number of snapshots to return. | `168`      |

Returns:

| Type                      | Description                              |
| ------------------------- | ---------------------------------------- |
| `list[PortfolioSnapshot]` | List of PortfolioSnapshot, oldest first. |

### get_snapshot_at

```
get_snapshot_at(
    strategy_id: str, timestamp: datetime
) -> PortfolioSnapshot | None
```

Get snapshot closest to a timestamp (for PnL calculation).

Parameters:

| Name          | Type       | Description          | Default    |
| ------------- | ---------- | -------------------- | ---------- |
| `strategy_id` | `str`      | Strategy identifier. | *required* |
| `timestamp`   | `datetime` | Target timestamp.    | *required* |

Returns:

| Type                | Description |
| ------------------- | ----------- |
| \`PortfolioSnapshot | None\`      |

### save_portfolio_metrics

```
save_portfolio_metrics(metrics: PortfolioMetrics) -> bool
```

Save or update portfolio metrics.

Portfolio metrics store baseline values (initial_value_usd) that survive strategy restarts, enabling accurate PnL calculation.

Parameters:

| Name      | Type               | Description                  | Default    |
| --------- | ------------------ | ---------------------------- | ---------- |
| `metrics` | `PortfolioMetrics` | PortfolioMetrics to persist. | *required* |

Returns:

| Type   | Description                                                |
| ------ | ---------------------------------------------------------- |
| `bool` | True if save succeeded, False if no WARM backend or error. |

### get_portfolio_metrics

```
get_portfolio_metrics(
    strategy_id: str,
) -> PortfolioMetrics | None
```

Get portfolio metrics for a strategy.

Parameters:

| Name          | Type  | Description          | Default    |
| ------------- | ----- | -------------------- | ---------- |
| `strategy_id` | `str` | Strategy identifier. | *required* |

Returns:

| Type               | Description |
| ------------------ | ----------- |
| \`PortfolioMetrics | None\`      |

### cleanup_old_snapshots

```
cleanup_old_snapshots(retention_days: int = 7) -> int
```

Clean up old portfolio snapshots.

Parameters:

| Name             | Type  | Description                            | Default |
| ---------------- | ----- | -------------------------------------- | ------- |
| `retention_days` | `int` | Number of days of snapshots to retain. | `7`     |

Returns:

| Type  | Description                  |
| ----- | ---------------------------- |
| `int` | Number of snapshots deleted. |

## StateManagerConfig

## almanak.framework.state.StateManagerConfig

```
StateManagerConfig(
    enable_hot: bool = True,
    enable_warm: bool = True,
    warm_backend: WarmBackendType = WarmBackendType.POSTGRESQL,
    hot_cache_ttl_seconds: int = 0,
    hot_cache_max_size: int = 1000,
    database_url: str | None = None,
    postgres_config: PostgresConfig = PostgresConfig(),
    sqlite_config: SQLiteConfigLight = SQLiteConfigLight(),
    metrics_callback: Callable[[TierMetrics], None]
    | None = None,
    load_state_on_startup: bool = True,
)
```

Configuration for StateManager.

Attributes:

| Name                    | Type                              | Description                                                  |
| ----------------------- | --------------------------------- | ------------------------------------------------------------ |
| `enable_hot`            | `bool`                            | Enable in-memory cache tier                                  |
| `enable_warm`           | `bool`                            | Enable WARM tier (PostgreSQL or SQLite)                      |
| `warm_backend`          | `WarmBackendType`                 | Which backend to use for WARM tier (POSTGRESQL or SQLITE)    |
| `hot_cache_ttl_seconds` | `int`                             | TTL for hot cache entries (0 = no expiry)                    |
| `hot_cache_max_size`    | `int`                             | Maximum entries in hot cache                                 |
| `postgres_config`       | `PostgresConfig`                  | PostgreSQL configuration (used when warm_backend=POSTGRESQL) |
| `sqlite_config`         | `SQLiteConfigLight`               | SQLite configuration (used when warm_backend=SQLITE)         |
| `metrics_callback`      | \`Callable\[[TierMetrics], None\] | None\`                                                       |
| `load_state_on_startup` | `bool`                            | Load all active states from WARM to HOT on startup           |

Example

### PostgreSQL backend (default, production)

config = StateManagerConfig( warm_backend=WarmBackendType.POSTGRESQL, postgres_config=PostgresConfig(host="localhost"), )

### SQLite backend (local development)

config = StateManagerConfig( warm_backend=WarmBackendType.SQLITE, sqlite_config=SQLiteConfigLight(db_path="./state.db"), )

## StateTier

## almanak.framework.state.StateTier

Bases: `IntEnum`

Storage tier for state data.

Ordered by access speed (fastest first).

## StateData

## almanak.framework.state.StateData

```
StateData(
    strategy_id: str,
    version: int,
    state: dict[str, Any],
    schema_version: int = 1,
    checksum: str = "",
    created_at: datetime = (lambda: datetime.now(UTC))(),
    loaded_from: StateTier | None = None,
)
```

Strategy state data container.

Attributes:

| Name             | Type             | Description                                           |
| ---------------- | ---------------- | ----------------------------------------------------- |
| `strategy_id`    | `str`            | Unique identifier for the strategy                    |
| `version`        | `int`            | CAS version number (incremented on each update)       |
| `state`          | `dict[str, Any]` | The actual state data as a dictionary                 |
| `schema_version` | `int`            | Schema version for migrations                         |
| `checksum`       | `str`            | SHA-256 hash of state data for integrity verification |
| `created_at`     | `datetime`       | When this state version was created                   |
| `loaded_from`    | \`StateTier      | None\`                                                |

### __post_init__

```
__post_init__() -> None
```

Calculate checksum if not provided.

### verify_checksum

```
verify_checksum() -> bool
```

Verify the integrity of state data.

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

### from_dict

```
from_dict(data: dict[str, Any]) -> StateData
```

Create StateData from dictionary.

## Migrations

### StateMigration

## almanak.framework.state.StateMigration

```
StateMigration(
    version: int,
    migration_fn: MigrationFunction,
    description: str = "",
    rollback_safe_until_version: int = 1,
    created_at: datetime = (lambda: datetime.now(UTC))(),
)
```

Defines a single state migration.

Attributes:

| Name                          | Type                | Description                                                                                                      |
| ----------------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `version`                     | `int`               | The schema version this migration upgrades TO                                                                    |
| `migration_fn`                | `MigrationFunction` | Function that transforms state from version-1 to version                                                         |
| `description`                 | `str`               | Human-readable description of what this migration does                                                           |
| `rollback_safe_until_version` | `int`               | Minimum version that can safely rollback to this version (i.e., versions >= this can rollback without data loss) |
| `created_at`                  | `datetime`          | When this migration was defined                                                                                  |

### __post_init__

```
__post_init__() -> None
```

Validate migration.

### apply

```
apply(state: dict[str, Any]) -> dict[str, Any]
```

Apply this migration to state.

Creates a deep copy to avoid mutating original state.

Parameters:

| Name    | Type             | Description               | Default    |
| ------- | ---------------- | ------------------------- | ---------- |
| `state` | `dict[str, Any]` | The state dict to migrate | *required* |

Returns:

| Type             | Description                    |
| ---------------- | ------------------------------ |
| `dict[str, Any]` | Migrated state dict (new copy) |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

### MigrationRegistry

## almanak.framework.state.MigrationRegistry

```
MigrationRegistry()
```

Registry of all state migrations.

Tracks migrations and provides version validation and lookup.

### current_version

```
current_version: int
```

Get the current (latest) schema version.

### migrations

```
migrations: dict[int, StateMigration]
```

Get all registered migrations.

### register

```
register(migration: StateMigration) -> None
```

Register a migration.

Parameters:

| Name        | Type             | Description               | Default    |
| ----------- | ---------------- | ------------------------- | ---------- |
| `migration` | `StateMigration` | The migration to register | *required* |

Raises:

| Type         | Description                                    |
| ------------ | ---------------------------------------------- |
| `ValueError` | If a migration for this version already exists |

### get

```
get(version: int) -> StateMigration | None
```

Get migration for a specific version.

Parameters:

| Name      | Type  | Description    | Default    |
| --------- | ----- | -------------- | ---------- |
| `version` | `int` | Target version | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`StateMigration | None\`      |

### get_migrations_path

```
get_migrations_path(
    from_version: int, to_version: int
) -> list[StateMigration]
```

Get list of migrations needed to go from one version to another.

Parameters:

| Name           | Type  | Description      | Default    |
| -------------- | ----- | ---------------- | ---------- |
| `from_version` | `int` | Starting version | *required* |
| `to_version`   | `int` | Target version   | *required* |

Returns:

| Type                   | Description                            |
| ---------------------- | -------------------------------------- |
| `list[StateMigration]` | List of migrations to apply (in order) |

Raises:

| Type                     | Description                          |
| ------------------------ | ------------------------------------ |
| `MigrationNotFoundError` | If any required migration is missing |

### get_rollback_info

```
get_rollback_info(current_version: int) -> RollbackInfo
```

Get rollback safety information for a version.

Parameters:

| Name              | Type  | Description            | Default    |
| ----------------- | ----- | ---------------------- | ---------- |
| `current_version` | `int` | Current schema version | *required* |

Returns:

| Type           | Description                                        |
| -------------- | -------------------------------------------------- |
| `RollbackInfo` | RollbackInfo with safe and unsafe rollback targets |

### clear

```
clear() -> None
```

Clear all registered migrations (mainly for testing).

### MigrationResult

## almanak.framework.state.MigrationResult

```
MigrationResult(
    success: bool,
    from_version: int,
    to_version: int,
    migrations_applied: list[int],
    state: dict[str, Any],
    error: str | None = None,
    duration_ms: float = 0.0,
)
```

Result of applying migrations.

Attributes:

| Name                 | Type             | Description                                |
| -------------------- | ---------------- | ------------------------------------------ |
| `success`            | `bool`           | Whether all migrations succeeded           |
| `from_version`       | `int`            | Starting schema version                    |
| `to_version`         | `int`            | Ending schema version                      |
| `migrations_applied` | `list[int]`      | List of migration versions applied         |
| `state`              | `dict[str, Any]` | The migrated state (or original if failed) |
| `error`              | \`str            | None\`                                     |
| `duration_ms`        | `float`          | Total migration time in milliseconds       |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

## Position Management

### PositionManager

## almanak.framework.state.PositionManager

```
PositionManager(
    chains: list[str],
    initial_positions: list[PositionRecord] | None = None,
)
```

Manages positions across multiple chains with chain dimension support.

Provides methods to store, query, and aggregate positions across chains. Designed to work with StateData.state dictionary for persistence.

Usage

### Create manager for a multi-chain strategy

manager = PositionManager(chains=['arbitrum', 'optimism', 'base'])

### Add positions

manager.add_position(PositionRecord( position_id='pos-1', chain='arbitrum', position_type=PositionType.SUPPLY, protocol='aave_v3', token='WETH', amount=Decimal('1.5'), value_usd=Decimal('3000'), ))

### Query positions

all_positions = manager.positions # All chains arb_positions = manager.positions_on('arbitrum') # Single chain total_usd = manager.total_value_usd # Aggregate value

Attributes:

| Name     | Type        | Description                    |
| -------- | ----------- | ------------------------------ |
| `chains` | `list[str]` | List of configured chain names |

Initialize position manager.

Parameters:

| Name                | Type                   | Description                              | Default                                    |
| ------------------- | ---------------------- | ---------------------------------------- | ------------------------------------------ |
| `chains`            | `list[str]`            | List of chain names this manager handles | *required*                                 |
| `initial_positions` | \`list[PositionRecord] | None\`                                   | Optional list of positions to pre-populate |

### chains

```
chains: list[str]
```

Get list of configured chains.

### positions

```
positions: list[PositionRecord]
```

Get all positions across all chains.

Returns:

| Type                   | Description                                       |
| ---------------------- | ------------------------------------------------- |
| `list[PositionRecord]` | List of all positions from all configured chains. |

### total_value_usd

```
total_value_usd: Decimal
```

Calculate total USD value across all chains.

Returns:

| Type      | Description                                           |
| --------- | ----------------------------------------------------- |
| `Decimal` | Sum of value_usd for all positions across all chains. |

### positions_on

```
positions_on(chain: str) -> list[PositionRecord]
```

Get positions on a specific chain.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `chain` | `str` | Chain name to filter by | *required* |

Returns:

| Type                   | Description                              |
| ---------------------- | ---------------------------------------- |
| `list[PositionRecord]` | List of positions on the specified chain |

Raises:

| Type                 | Description                |
| -------------------- | -------------------------- |
| `ChainNotFoundError` | If chain is not configured |

### total_value_on

```
total_value_on(chain: str) -> Decimal
```

Calculate total USD value on a specific chain.

Parameters:

| Name    | Type  | Description                       | Default    |
| ------- | ----- | --------------------------------- | ---------- |
| `chain` | `str` | Chain name to calculate value for | *required* |

Returns:

| Type      | Description                                           |
| --------- | ----------------------------------------------------- |
| `Decimal` | Sum of value_usd for positions on the specified chain |

Raises:

| Type                 | Description                |
| -------------------- | -------------------------- |
| `ChainNotFoundError` | If chain is not configured |

### add_position

```
add_position(position: PositionRecord) -> None
```

Add or update a position.

If a position with the same position_id exists on the same chain, it will be replaced.

Parameters:

| Name       | Type             | Description            | Default    |
| ---------- | ---------------- | ---------------------- | ---------- |
| `position` | `PositionRecord` | Position to add/update | *required* |

Raises:

| Type                 | Description                           |
| -------------------- | ------------------------------------- |
| `ChainNotFoundError` | If position's chain is not configured |

### remove_position

```
remove_position(position_id: str, chain: str) -> bool
```

Remove a position by ID and chain.

Parameters:

| Name          | Type  | Description              | Default    |
| ------------- | ----- | ------------------------ | ---------- |
| `position_id` | `str` | Position identifier      | *required* |
| `chain`       | `str` | Chain the position is on | *required* |

Returns:

| Type   | Description                                      |
| ------ | ------------------------------------------------ |
| `bool` | True if position was removed, False if not found |

Raises:

| Type                 | Description                |
| -------------------- | -------------------------- |
| `ChainNotFoundError` | If chain is not configured |

### get_position

```
get_position(
    position_id: str, chain: str
) -> PositionRecord | None
```

Get a specific position by ID and chain.

Parameters:

| Name          | Type  | Description              | Default    |
| ------------- | ----- | ------------------------ | ---------- |
| `position_id` | `str` | Position identifier      | *required* |
| `chain`       | `str` | Chain the position is on | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`PositionRecord | None\`      |

Raises:

| Type                 | Description                |
| -------------------- | -------------------------- |
| `ChainNotFoundError` | If chain is not configured |

### find_position

```
find_position(position_id: str) -> PositionRecord | None
```

Find a position by ID across all chains.

Parameters:

| Name          | Type  | Description                       | Default    |
| ------------- | ----- | --------------------------------- | ---------- |
| `position_id` | `str` | Position identifier to search for | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`PositionRecord | None\`      |

### positions_by_type

```
positions_by_type(
    position_type: PositionType,
) -> list[PositionRecord]
```

Get all positions of a specific type across all chains.

Parameters:

| Name            | Type           | Description                   | Default    |
| --------------- | -------------- | ----------------------------- | ---------- |
| `position_type` | `PositionType` | Type of position to filter by | *required* |

Returns:

| Type                   | Description                         |
| ---------------------- | ----------------------------------- |
| `list[PositionRecord]` | List of positions matching the type |

### positions_by_protocol

```
positions_by_protocol(
    protocol: str,
) -> list[PositionRecord]
```

Get all positions for a specific protocol across all chains.

Parameters:

| Name       | Type  | Description                | Default    |
| ---------- | ----- | -------------------------- | ---------- |
| `protocol` | `str` | Protocol name to filter by | *required* |

Returns:

| Type                   | Description                       |
| ---------------------- | --------------------------------- |
| `list[PositionRecord]` | List of positions on the protocol |

### clear

```
clear(chain: str | None = None) -> None
```

Clear positions.

Parameters:

| Name    | Type  | Description | Default                                                                                      |
| ------- | ----- | ----------- | -------------------------------------------------------------------------------------------- |
| `chain` | \`str | None\`      | If provided, clear only positions on this chain. If None, clear all positions on all chains. |

Raises:

| Type                 | Description                          |
| -------------------- | ------------------------------------ |
| `ChainNotFoundError` | If specified chain is not configured |

### to_dict

```
to_dict() -> dict[str, Any]
```

Serialize all positions to dictionary for state storage.

Returns:

| Type             | Description                                                     |
| ---------------- | --------------------------------------------------------------- |
| `dict[str, Any]` | Dictionary with chain -> position_id -> position data structure |

### from_dict

```
from_dict(data: dict[str, Any]) -> PositionManager
```

Deserialize position manager from dictionary.

Parameters:

| Name   | Type             | Description               | Default    |
| ------ | ---------------- | ------------------------- | ---------- |
| `data` | `dict[str, Any]` | Dictionary from to_dict() | *required* |

Returns:

| Type              | Description                             |
| ----------------- | --------------------------------------- |
| `PositionManager` | PositionManager with restored positions |

### get_summary

```
get_summary() -> dict[str, Any]
```

Get a summary of positions across all chains.

Returns:

| Type             | Description                                    |
| ---------------- | ---------------------------------------------- |
| `dict[str, Any]` | Dictionary with per-chain and total statistics |

### __repr__

```
__repr__() -> str
```

String representation of manager.

### PositionRecord

## almanak.framework.state.PositionRecord

```
PositionRecord(
    position_id: str,
    chain: str,
    position_type: PositionType,
    protocol: str | None,
    token: str,
    amount: Decimal,
    value_usd: Decimal,
    created_at: datetime = (lambda: datetime.now(UTC))(),
    updated_at: datetime = (lambda: datetime.now(UTC))(),
    metadata: dict[str, Any] = dict(),
)
```

A single position record with chain dimension.

This is the fundamental unit of position tracking. Every position must have an explicit chain field to support multi-chain strategies.

Attributes:

| Name            | Type             | Description                                                       |
| --------------- | ---------------- | ----------------------------------------------------------------- |
| `position_id`   | `str`            | Unique identifier for this position                               |
| `chain`         | `str`            | The blockchain this position is on (e.g., 'arbitrum', 'optimism') |
| `position_type` | `PositionType`   | Type of position (TOKEN, LP, BORROW, SUPPLY, PERP)                |
| `protocol`      | \`str            | None\`                                                            |
| `token`         | `str`            | Primary token symbol (or pool identifier for LP)                  |
| `amount`        | `Decimal`        | Amount of tokens or liquidity                                     |
| `value_usd`     | `Decimal`        | Current USD value of the position                                 |
| `created_at`    | `datetime`       | When the position was opened                                      |
| `updated_at`    | `datetime`       | Last update timestamp                                             |
| `metadata`      | `dict[str, Any]` | Additional position-specific data (health factor, ranges, etc.)   |

### to_dict

```
to_dict() -> dict[str, Any]
```

Serialize position to dictionary.

### from_dict

```
from_dict(data: dict[str, Any]) -> PositionRecord
```

Deserialize position from dictionary.

## Exceptions

## almanak.framework.state.StateConflictError

```
StateConflictError(
    strategy_id: str,
    expected_version: int,
    actual_version: int,
    message: str | None = None,
)
```

Bases: `Exception`

Raised when CAS update fails due to version mismatch.

This error indicates that another process has modified the state since it was last read. The caller should reload the state and retry.

## almanak.framework.state.StateNotFoundError

```
StateNotFoundError(
    strategy_id: str, message: str | None = None
)
```

Bases: `Exception`

Raised when state is not found in any tier.

# Strategies

Strategy base classes and the market snapshot interface.

## Implementing Teardown

Every strategy **must** implement three teardown methods so operators can safely close positions. Without them, close-requests are silently ignored.

### Required Methods

| Method                                    | Purpose                                                          |
| ----------------------------------------- | ---------------------------------------------------------------- |
| `supports_teardown() -> bool`             | Return `True` to enable teardown                                 |
| `get_open_positions()`                    | Return a `TeardownPositionSummary` listing all open positions    |
| `generate_teardown_intents(mode, market)` | Return an ordered list of `Intent` objects that unwind positions |

### Execution Order

If your strategy holds multiple position types, teardown intents must follow this order:

1. **PERP** -- close perpetual positions first (highest risk)
1. **BORROW** -- repay borrows to free collateral
1. **SUPPLY** -- withdraw supplied collateral
1. **LP** -- close liquidity positions
1. **TOKEN** -- swap remaining tokens to stable

### Example: Swap Strategy Teardown

```
from decimal import Decimal
from almanak import IntentStrategy, Intent

class MyStrategy(IntentStrategy):
    def supports_teardown(self) -> bool:
        return True

    def get_open_positions(self) -> "TeardownPositionSummary":
        from datetime import UTC, datetime
        from almanak.framework.teardown import (
            PositionInfo, PositionType, TeardownPositionSummary,
        )
        return TeardownPositionSummary(
            strategy_id=getattr(self, "strategy_id", "my_strategy"),
            timestamp=datetime.now(UTC),
            positions=[
                PositionInfo(
                    position_type=PositionType.TOKEN,
                    position_id="my_strategy_eth",
                    chain=self.chain,
                    protocol="uniswap_v3",
                    value_usd=Decimal("1000"),  # query on-chain balance, not cache
                    details={"asset": "WETH"},
                )
            ],
        )

    def generate_teardown_intents(self, mode: "TeardownMode", market=None) -> list[Intent]:
        from almanak.framework.teardown import TeardownMode
        max_slippage = Decimal("0.03") if mode == TeardownMode.HARD else Decimal("0.005")
        return [
            Intent.swap(
                from_token="WETH", to_token="USDC",
                amount="all", max_slippage=max_slippage, protocol="uniswap_v3",
            )
        ]
```

Always query on-chain state

`get_open_positions()` must query live on-chain balances, not cached values. Stale data can cause teardown to skip positions or attempt to close positions that no longer exist.

## State Persistence

Strategies that track internal state across iterations (position IDs, phase tracking, trade counters) must implement two hooks so that state survives restarts.

The framework persists runner-level metadata automatically (iteration counts, consecutive errors, execution progress for multi-step intents). But **strategy-specific state is opt-in** -- without these hooks, instance variables are lost when the process stops.

### Required Hooks

| Method                               | Called               | Purpose                           |
| ------------------------------------ | -------------------- | --------------------------------- |
| `get_persistent_state() -> dict`     | After each iteration | Return a dict of state to save    |
| `load_persistent_state(state: dict)` | On startup / resume  | Restore state from the saved dict |

### Example: LP Strategy with Position Tracking

```
from decimal import Decimal
from typing import Any
from almanak import IntentStrategy, Intent, MarketSnapshot

class MyLPStrategy(IntentStrategy):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self._position_id: int | None = None
        self._has_position: bool = False
        self._total_fees_collected: Decimal = Decimal("0")

    def decide(self, market: MarketSnapshot) -> Intent | None:
        if self._has_position:
            return Intent.collect_fees(position_id=self._position_id, protocol="uniswap_v3")
        return Intent.lp_open(
            token_a="WETH", token_b="USDC",
            amount_usd=Decimal("1000"), protocol="uniswap_v3",
        )

    def on_intent_executed(self, intent, success: bool, result):
        """Update state after execution -- persisted via get_persistent_state()."""
        if success and result.position_id:
            self._position_id = result.position_id
            self._has_position = True

    # -- State persistence hooks --

    def get_persistent_state(self) -> dict[str, Any]:
        return {
            "position_id": self._position_id,
            "has_position": self._has_position,
            "total_fees_collected": str(self._total_fees_collected),
        }

    def load_persistent_state(self, state: dict[str, Any]) -> None:
        self._position_id = state.get("position_id")
        self._has_position = state.get("has_position", False)
        self._total_fees_collected = Decimal(state.get("total_fees_collected", "0"))
```

### What the Framework Persists Automatically

These are saved by the runner without any strategy-author code:

| State                                                                    | Persisted to                        | Survives restart                           |
| ------------------------------------------------------------------------ | ----------------------------------- | ------------------------------------------ |
| Iteration count, success count, error count                              | `strategy_state` table              | Yes                                        |
| Multi-step execution progress (which step completed, serialized intents) | `strategy_state.execution_progress` | Yes -- runner resumes from the failed step |
| Operator pause flag                                                      | `strategy_state.is_paused`          | Yes                                        |
| Portfolio snapshots (total value, positions)                             | `portfolio_snapshots` table         | Yes                                        |
| Portfolio PnL baseline (initial value, deposits, gas)                    | `portfolio_metrics` table           | Yes                                        |
| Timeline events (execution audit trail)                                  | `timeline_events` table             | Yes                                        |

### Guidelines

- **Use defensive `.get()` with defaults** in `load_persistent_state()` so older saved state doesn't crash when you add new fields.
- **Store `Decimal` as strings** (`str(amount)`) and parse back (`Decimal(state["amount"])`) for safe JSON round-tripping.
- **`on_intent_executed()` is the natural place to update state** after a trade (e.g., storing a position ID). `get_persistent_state()` then picks it up for the next save.
- **All values must be JSON-serializable.** The state dict is stored as a JSON blob in the database.

Without these hooks, strategy state is lost on restart

If you store state in instance variables but don't implement the persistence hooks, a restart means your strategy has no memory of open positions, completed trades, or internal phase. This is especially dangerous for LP and lending strategies where losing a position ID means the strategy cannot close its own positions.

## IntentStrategy

The primary base class for writing strategies. Implement the `decide()` method to return an `Intent`.

## almanak.framework.strategies.IntentStrategy

```
IntentStrategy(
    config: ConfigT,
    chain: str,
    wallet_address: str,
    risk_guard_config: RiskGuardConfig | None = None,
    notification_callback: NotificationCallback
    | None = None,
    compiler: IntentCompiler | None = None,
    state_machine_config: StateMachineConfig | None = None,
    price_oracle: PriceOracle | None = None,
    rsi_provider: RSIProvider | None = None,
    balance_provider: BalanceProvider | None = None,
    rpc_url: str | None = None,
    wallet_activity_provider: WalletActivityProvider
    | None = None,
    chains: list[str] | None = None,
    chain_wallets: dict[str, str] | None = None,
)
```

Bases: `StrategyBase[ConfigT]`

Base class for Intent-based strategies.

IntentStrategy simplifies strategy development by allowing developers to write just a decide() method that returns an Intent. The framework handles:

1. Market data access via MarketSnapshot
1. Intent compilation to ActionBundle
1. State machine generation for execution
1. Hot-reloadable configuration
1. Error handling and retries

Subclasses must implement the abstract decide() method.

Example

@almanak_strategy(name="simple_strategy") class SimpleStrategy(IntentStrategy): def decide(self, market: MarketSnapshot) -> Optional\[Intent\]: if market.rsi("ETH").is_oversold: return Intent.swap("USDC", "ETH", amount_usd=Decimal("100")) return Intent.hold()

Attributes:

| Name                     | Type                 | Description                                             |
| ------------------------ | -------------------- | ------------------------------------------------------- |
| `compiler`               | `IntentCompiler`     | IntentCompiler for converting intents to action bundles |
| `state_machine_config`   |                      | Configuration for state machine execution               |
| `_current_intent`        | \`AnyIntent          | None\`                                                  |
| `_current_state_machine` | \`IntentStateMachine | None\`                                                  |

Initialize the intent strategy.

Parameters:

| Name                       | Type                     | Description                            | Default                                                                |
| -------------------------- | ------------------------ | -------------------------------------- | ---------------------------------------------------------------------- |
| `config`                   | `ConfigT`                | Hot-reloadable configuration           | *required*                                                             |
| `chain`                    | `str`                    | Chain to operate on (e.g., "arbitrum") | *required*                                                             |
| `wallet_address`           | `str`                    | Wallet address for transactions        | *required*                                                             |
| `risk_guard_config`        | \`RiskGuardConfig        | None\`                                 | Risk guard configuration                                               |
| `notification_callback`    | \`NotificationCallback   | None\`                                 | Callback for operator notifications                                    |
| `compiler`                 | \`IntentCompiler         | None\`                                 | Intent compiler (required for direct run() calls, optional for runner) |
| `state_machine_config`     | \`StateMachineConfig     | None\`                                 | State machine configuration                                            |
| `price_oracle`             | \`PriceOracle            | None\`                                 | Function to fetch prices                                               |
| `rsi_provider`             | \`RSIProvider            | None\`                                 | Function to calculate RSI (token, period[, timeframe=]) -> RSIData     |
| `balance_provider`         | \`BalanceProvider        | None\`                                 | Function to fetch balances                                             |
| `rpc_url`                  | \`str                    | None\`                                 | RPC URL for on-chain queries (needed for LP close)                     |
| `wallet_activity_provider` | \`WalletActivityProvider | None\`                                 | Provider for leader wallet activity signals                            |
| `chains`                   | \`list[str]              | None\`                                 | List of all chains this strategy operates on (multi-chain)             |
| `chain_wallets`            | \`dict[str, str]         | None\`                                 | Per-chain wallet addresses from wallet registry                        |

### chain

```
chain: str
```

Get the primary chain name.

### chains

```
chains: list[str]
```

Get all chains this strategy operates on.

### wallet_address

```
wallet_address: str
```

Get the wallet address.

### compiler

```
compiler: IntentCompiler
```

Get the intent compiler.

Raises:

| Type           | Description                                                                                                                                                         |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `RuntimeError` | If compiler was not provided and is accessed directly. The StrategyRunner creates its own compiler with real prices, so this is only needed for direct run() calls. |

### current_intent

```
current_intent: AnyIntent | None
```

Get the currently executing intent.

### current_state_machine

```
current_state_machine: IntentStateMachine | None
```

Get the current state machine.

### get_wallet_for_chain

```
get_wallet_for_chain(chain: str) -> str
```

Get the wallet address for a specific chain.

If a wallet registry provided per-chain wallets, returns the chain-specific wallet. Otherwise falls back to the default wallet.

Parameters:

| Name    | Type  | Description                           | Default    |
| ------- | ----- | ------------------------------------- | ---------- |
| `chain` | `str` | Chain name (e.g., "arbitrum", "base") | *required* |

Returns:

| Type  | Description                            |
| ----- | -------------------------------------- |
| `str` | Wallet address for the specified chain |

### set_state_manager

```
set_state_manager(
    state_manager: Any, strategy_id: str
) -> None
```

Set the state manager for persistence.

Called by the runner to inject the state manager.

Parameters:

| Name            | Type  | Description                          | Default    |
| --------------- | ----- | ------------------------------------ | ---------- |
| `state_manager` | `Any` | StateManager instance                | *required* |
| `strategy_id`   | `str` | Unique ID for this strategy instance | *required* |

### get_persistent_state

```
get_persistent_state() -> dict[str, Any]
```

Get strategy state to persist.

Override this method to define what state should be persisted. Default implementation returns empty dict (no state).

Returns:

| Type             | Description                              |
| ---------------- | ---------------------------------------- |
| `dict[str, Any]` | Dict of state key-value pairs to persist |

### load_persistent_state

```
load_persistent_state(state: dict[str, Any]) -> None
```

Load persisted state into the strategy.

Override this method to restore state from persistence. Default implementation does nothing.

Parameters:

| Name    | Type             | Description                                       | Default    |
| ------- | ---------------- | ------------------------------------------------- | ---------- |
| `state` | `dict[str, Any]` | Dict of state key-value pairs loaded from storage | *required* |

### save_state

```
save_state() -> None
```

Save current strategy state to persistence.

Called by runner after each iteration.

### flush_pending_saves

```
flush_pending_saves() -> None
```

Wait for any pending save operations to complete.

This should be called before disconnecting from the gateway to ensure all state saves have completed. Handles both successful completion and errors gracefully.

### load_state

```
load_state() -> bool
```

Load strategy state from persistence.

Called by runner on startup.

Returns:

| Type   | Description                                         |
| ------ | --------------------------------------------------- |
| `bool` | True if state was found and loaded, False otherwise |

### load_state_async

```
load_state_async() -> bool
```

Async variant of load_state() — preferred when already in an event loop.

Called by the CLI runner inside its async setup so that state is always restored correctly, regardless of whether a loop is already running.

### decide

```
decide(market: MarketSnapshot) -> DecideResult
```

Decide what action to take based on current market conditions.

This is the main method that strategy developers need to implement. It receives a MarketSnapshot with current market data and should return an Intent, IntentSequence, list of intents, or None.

Parameters:

| Name     | Type             | Description                                              | Default    |
| -------- | ---------------- | -------------------------------------------------------- | ---------- |
| `market` | `MarketSnapshot` | Current market snapshot with prices, balances, RSI, etc. | *required* |

Returns:

| Type           | Description                                                       |
| -------------- | ----------------------------------------------------------------- |
| `DecideResult` | One of:                                                           |
| `DecideResult` | Single Intent: Execute one action                                 |
| `DecideResult` | IntentSequence: Execute multiple actions sequentially (dependent) |
| `DecideResult` | list\[Intent                                                      |
| `DecideResult` | None: Take no action (equivalent to Intent.hold())                |
| `DecideResult` | Returning None is equivalent to returning Intent.hold().          |

Example

def decide(self, market: MarketSnapshot) -> DecideResult:

# Single intent

if market.rsi("ETH").is_oversold: return Intent.swap("USDC", "ETH", amount_usd=Decimal("1000"))

```
# Sequence of dependent actions (execute in order)
if should_move_funds:
    return Intent.sequence([
        Intent.swap("USDC", "ETH", amount=Decimal("1000"), chain="base"),
        Intent.supply(protocol="aave_v3", token="WETH", amount=Decimal("0.5"), chain="arbitrum"),
    ])

# Multiple independent actions (execute in parallel)
if should_rebalance:
    return [
        Intent.swap("USDC", "ETH", amount=Decimal("500"), chain="arbitrum"),
        Intent.swap("USDC", "ETH", amount=Decimal("500"), chain="optimism"),
    ]

# No action
return Intent.hold(reason="RSI in neutral zone")
```

### on_intent_executed

```
on_intent_executed(
    intent: Any, success: bool, result: Any
) -> None
```

Called after each intent execution completes.

Override this method to react to execution results, e.g., to track position IDs, log swap amounts, or update state based on results.

The result object is enriched by the framework with extracted data that "just appears" based on intent type:

- SWAP: result.swap_amounts (SwapAmounts)
- LP_OPEN: result.position_id, result.extracted_data["liquidity"]
- LP_CLOSE: result.lp_close_data (LPCloseData)
- PERP_OPEN: result.extracted_data["entry_price"], ["leverage"]

Parameters:

| Name      | Type   | Description                        | Default    |
| --------- | ------ | ---------------------------------- | ---------- |
| `intent`  | `Any`  | The intent that was executed       | *required* |
| `success` | `bool` | Whether execution succeeded        | *required* |
| `result`  | `Any`  | ExecutionResult with enriched data | *required* |

### valuate

```
valuate(market: MarketSnapshot) -> Decimal
```

Calculate the total portfolio value in USD for vault settlement.

Called by the framework during vault settlement to determine the current value of the strategy's holdings. The returned value is converted to underlying token units and proposed as the new totalAssets for the vault.

The default implementation sums balance_usd for all known token balances in the market snapshot. Override this method for custom valuation logic (e.g., including LP positions, pending rewards, or off-chain assets).

Parameters:

| Name     | Type             | Description                                      | Default    |
| -------- | ---------------- | ------------------------------------------------ | ---------- |
| `market` | `MarketSnapshot` | Current market snapshot with prices and balances | *required* |

Returns:

| Type      | Description                               |
| --------- | ----------------------------------------- |
| `Decimal` | Total portfolio value in USD as a Decimal |

### on_vault_settled

```
on_vault_settled(settlement: SettlementResult) -> None
```

Called after a vault settlement cycle completes.

Override this method to react to settlement results, e.g., to log deposit/redemption amounts or update internal state.

Parameters:

| Name         | Type               | Description                                   | Default    |
| ------------ | ------------------ | --------------------------------------------- | ---------- |
| `settlement` | `SettlementResult` | SettlementResult with deposit/redemption data | *required* |

### set_multi_chain_providers

```
set_multi_chain_providers(
    price_oracle: MultiChainPriceOracle | None = None,
    balance_provider: MultiChainBalanceProvider
    | None = None,
    aave_health_factor_provider: AaveHealthFactorProvider
    | None = None,
) -> None
```

Set multi-chain data providers for cross-chain strategies.

Call this method before running a multi-chain strategy to enable MultiChainMarketSnapshot creation.

Parameters:

| Name                          | Type                        | Description | Default                      |
| ----------------------------- | --------------------------- | ----------- | ---------------------------- |
| `price_oracle`                | \`MultiChainPriceOracle     | None\`      | Multi-chain price oracle     |
| `balance_provider`            | \`MultiChainBalanceProvider | None\`      | Multi-chain balance provider |
| `aave_health_factor_provider` | \`AaveHealthFactorProvider  | None\`      | Aave health factor provider  |

### is_multi_chain

```
is_multi_chain() -> bool
```

Check if this strategy is running in multi-chain mode.

Returns True only when SUPPORTED_CHAINS is explicitly set (manually or by the CLI multi-chain path) AND has >1 chain. Does NOT use decorator metadata because that is portability info, not a runtime signal. The CLI's is_multi_chain_strategy() makes the runtime decision based on config.chains.

Returns:

| Type   | Description                                  |
| ------ | -------------------------------------------- |
| `bool` | True if SUPPORTED_CHAINS has multiple chains |

### get_supported_chains

```
get_supported_chains() -> list[str]
```

Get the chains supported by this strategy.

Returns SUPPORTED_CHAINS if explicitly set, otherwise falls back to STRATEGY_METADATA.supported_chains (decorator portability metadata), then to [self.\_chain].

Returns:

| Type        | Description                   |
| ----------- | ----------------------------- |
| `list[str]` | List of supported chain names |

### create_market_snapshot

```
create_market_snapshot() -> MarketSnapshot
```

Create a market snapshot for the current iteration.

Automatically creates MultiChainMarketSnapshot for multi-chain strategies if multi-chain providers have been set. Otherwise returns single-chain MarketSnapshot.

Override this method to customize how market data is populated.

Returns:

| Type             | Description                                                             |
| ---------------- | ----------------------------------------------------------------------- |
| `MarketSnapshot` | MarketSnapshot (or MultiChainMarketSnapshot for multi-chain strategies) |

### run

```
run() -> ActionBundle | None
```

Execute one iteration of the strategy.

This method:

1. Creates a MarketSnapshot
1. Calls decide() to get an intent or DecideResult
1. Compiles single intents to an ActionBundle
1. Returns the ActionBundle for execution

Note: For multi-intent results (list or IntentSequence), this method only compiles the first intent. Use run_multi() for full multi-intent execution with proper parallel/sequential handling.

Returns:

| Type           | Description |
| -------------- | ----------- |
| \`ActionBundle | None\`      |

### run_multi

```
run_multi() -> DecideResult
```

Execute one iteration of the strategy, returning the full DecideResult.

Unlike run(), this method returns the full DecideResult from decide() without compiling to ActionBundle. This is useful for multi-chain execution via MultiChainOrchestrator.

Returns:

| Name           | Type           | Description                                               |
| -------------- | -------------- | --------------------------------------------------------- |
| `DecideResult` | `DecideResult` | The raw result from decide() (may be None, single intent, |
|                | `DecideResult` | IntentSequence, or list of intents/sequences)             |

### run_with_state_machine

```
run_with_state_machine(
    receipt_provider: Callable[
        [ActionBundle], TransactionReceipt
    ]
    | None = None,
) -> ExecutionResult
```

Execute strategy with full state machine lifecycle.

This method provides full state machine execution including:

- Intent compilation
- Transaction execution (via receipt_provider)
- Validation
- Retry logic on failure

Note: This method only handles single intents for backward compatibility. For multi-intent execution, use run_multi() with MultiChainOrchestrator.

Parameters:

| Name               | Type                                             | Description | Default                                                                                                              |
| ------------------ | ------------------------------------------------ | ----------- | -------------------------------------------------------------------------------------------------------------------- |
| `receipt_provider` | \`Callable\[[ActionBundle], TransactionReceipt\] | None\`      | Function that executes an ActionBundle and returns a TransactionReceipt. If not provided, returns after compilation. |

Returns:

| Type              | Description                                 |
| ----------------- | ------------------------------------------- |
| `ExecutionResult` | ExecutionResult with full execution details |

### get_metadata

```
get_metadata() -> StrategyMetadata | None
```

Get strategy metadata if available.

Returns:

| Type               | Description |
| ------------------ | ----------- |
| \`StrategyMetadata | None\`      |

### to_dict

```
to_dict() -> dict[str, Any]
```

Serialize strategy state to dictionary.

Returns:

| Type             | Description                                 |
| ---------------- | ------------------------------------------- |
| `dict[str, Any]` | Dictionary representation of strategy state |

### on_sadflow_enter

```
on_sadflow_enter(
    error_type: str | None,
    attempt: int,
    context: SadflowContext,
) -> SadflowAction | None
```

Hook called when entering sadflow state.

Override this method to customize sadflow behavior for your strategy. This is called once when first entering sadflow, before any retry attempts.

Parameters:

| Name         | Type             | Description                                            | Default                                                                                                                     |
| ------------ | ---------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------- |
| `error_type` | \`str            | None\`                                                 | Categorized error type (e.g., "INSUFFICIENT_FUNDS", "TIMEOUT", "SLIPPAGE", "REVERT"). May be None for uncategorized errors. |
| `attempt`    | `int`            | Current attempt number (1-indexed).                    | *required*                                                                                                                  |
| `context`    | `SadflowContext` | SadflowContext with error details and execution state. | *required*                                                                                                                  |

Returns:

| Type            | Description |
| --------------- | ----------- |
| \`SadflowAction | None\`      |
| \`SadflowAction | None\`      |
| \`SadflowAction | None\`      |
| \`SadflowAction | None\`      |
| \`SadflowAction | None\`      |
| \`SadflowAction | None\`      |

Example

def on_sadflow_enter(self, error_type, attempt, context):

# Abort immediately on insufficient funds

if error_type == "INSUFFICIENT_FUNDS": return SadflowAction.abort("Not enough funds for transaction")

```
# Increase gas for gas errors
if error_type == "GAS_ERROR" and context.action_bundle:
    modified = self._increase_gas(context.action_bundle)
    return SadflowAction.modify(modified, reason="Increased gas limit")

# Use default retry for other errors
return None
```

### pause

```
pause() -> None
```

Pause the strategy during teardown.

Called by TeardownManager before executing teardown intents. Default is a no-op; override if your strategy needs to stop background tasks or cancel pending orders before teardown.

### get_portfolio_snapshot

```
get_portfolio_snapshot(
    market: MarketSnapshot | None = None,
) -> PortfolioSnapshot
```

Get current portfolio value and positions.

This method is called by the StrategyRunner after each iteration to capture portfolio snapshots for:

- Dashboard value display (Total Value, PnL)
- Historical PnL charts
- Position breakdown by type

Default implementation:

1. Calls get_open_positions() for position values (LP, lending, perps)
1. Adds wallet token balances not captured by positions

Override for strategies needing custom value calculation (CEX, prediction).

Parameters:

| Name     | Type             | Description | Default                                                   |
| -------- | ---------------- | ----------- | --------------------------------------------------------- |
| `market` | \`MarketSnapshot | None\`      | Optional MarketSnapshot. If None, creates one internally. |

Returns:

| Type                | Description                                                 |
| ------------------- | ----------------------------------------------------------- |
| `PortfolioSnapshot` | PortfolioSnapshot with current values and confidence level. |
| `PortfolioSnapshot` | If value cannot be computed, returns snapshot with          |
| `PortfolioSnapshot` | value_confidence=UNAVAILABLE instead of $0.                 |

Example

def get_portfolio_snapshot(self, market=None) -> PortfolioSnapshot: if market is None: market = self.create_market_snapshot()

```
# Custom CEX balance fetch
cex_balance = self._fetch_cex_balance()

return PortfolioSnapshot(
    timestamp=datetime.now(UTC),
    strategy_id=self.strategy_id,
    total_value_usd=cex_balance,
    available_cash_usd=cex_balance,
    value_confidence=ValueConfidence.ESTIMATED,
    chain=self.chain,
)
```

### get_open_positions

```
get_open_positions() -> TeardownPositionSummary
```

Get all open positions for this strategy.

MUST query on-chain state - do not use cached state for safety. Called during teardown preview and execution to determine what positions need to be closed.

For strategies with no positions, use StatelessStrategy as your base class, or return TeardownPositionSummary.empty(self.strategy_id).

Returns:

| Type                      | Description                                        |
| ------------------------- | -------------------------------------------------- |
| `TeardownPositionSummary` | TeardownPositionSummary with all current positions |

Example

from almanak.framework.teardown import TeardownPositionSummary, PositionInfo, PositionType

def get_open_positions(self) -> TeardownPositionSummary: positions = []

```
# Query on-chain LP position
lp_data = self._query_lp_position()
if lp_data:
    positions.append(PositionInfo(
        position_type=PositionType.LP,
        position_id=lp_data["token_id"],
        chain=self.chain,
        protocol="uniswap_v3",
        value_usd=Decimal(str(lp_data["value_usd"])),
    ))

return TeardownPositionSummary(
    strategy_id=self.STRATEGY_NAME,
    timestamp=datetime.now(timezone.utc),
    positions=positions,
)
```

### generate_teardown_intents

```
generate_teardown_intents(
    mode: TeardownMode, market: MarketSnapshot | None = None
) -> list[Intent]
```

Generate intents to close all positions.

Return intents in the correct execution order:

1. PERP - Close perpetuals first (highest liquidation risk)
1. BORROW - Repay borrowed amounts (frees collateral)
1. SUPPLY - Withdraw supplied collateral
1. LP - Close LP positions and collect fees
1. TOKEN - Swap all tokens to target token (USDC)

For strategies with no positions, use StatelessStrategy as your base class, or return an empty list.

Parameters:

| Name     | Type             | Description                                                   | Default                                                                                                                                                                                                          |
| -------- | ---------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode`   | `TeardownMode`   | TeardownMode.SOFT (graceful) or TeardownMode.HARD (emergency) | *required*                                                                                                                                                                                                       |
| `market` | \`MarketSnapshot | None\`                                                        | Optional market snapshot with real prices. When called from the runner, this is the same snapshot used for normal decide() iterations. May be None for backward compatibility or when called outside the runner. |

Returns:

| Type           | Description                         |
| -------------- | ----------------------------------- |
| `list[Intent]` | List of intents to execute in order |

Example

from almanak.framework.teardown import TeardownMode

def generate_teardown_intents(self, mode: TeardownMode, market=None) -> list\[Intent\]: intents = []

```
# Get current positions
positions = self.get_open_positions()

# Use market data if available for smarter teardown
if market:
    eth_price = market.price("ETH")

# Close LP position first
for pos in positions.positions_by_type(PositionType.LP):
    intents.append(Intent.lp_close(
        position_id=pos.position_id,
        pool=pos.details.get("pool"),
        collect_fees=True,
        protocol="uniswap_v3",
    ))

# Swap remaining tokens to USDC
intents.append(Intent.swap(
    from_token="WETH",
    to_token="USDC",
    amount=Decimal("0"),  # All remaining
    swap_all=True,
))

return intents
```

### on_teardown_started

```
on_teardown_started(mode: TeardownMode) -> None
```

Hook called when teardown starts.

Override to perform any setup before teardown begins. This is called after the cancel window expires.

Parameters:

| Name   | Type           | Description                      | Default    |
| ------ | -------------- | -------------------------------- | ---------- |
| `mode` | `TeardownMode` | The teardown mode (SOFT or HARD) | *required* |

Example

def on_teardown_started(self, mode: TeardownMode) -> None: logger.info(f"Teardown starting in {mode.value} mode") self.\_pause_monitoring()

### on_teardown_completed

```
on_teardown_completed(
    success: bool, recovered_usd: Decimal
) -> None
```

Hook called when teardown completes.

Override to perform cleanup after teardown.

Parameters:

| Name            | Type      | Description                                    | Default    |
| --------------- | --------- | ---------------------------------------------- | ---------- |
| `success`       | `bool`    | Whether all positions were closed successfully | *required* |
| `recovered_usd` | `Decimal` | Total USD value recovered                      | *required* |

Example

def on_teardown_completed(self, success: bool, recovered_usd: Decimal) -> None: if success: logger.info(f"Teardown complete. Recovered ${recovered_usd:,.2f}") else: logger.error("Teardown failed - manual intervention required")

### get_teardown_profile

```
get_teardown_profile() -> TeardownProfile
```

Get teardown profile metadata for UX display.

Override to provide better information about teardown expectations. This helps the dashboard show more accurate previews.

Returns:

| Type              | Description                                     |
| ----------------- | ----------------------------------------------- |
| `TeardownProfile` | TeardownProfile with strategy-specific metadata |

Example

from almanak.framework.teardown import TeardownProfile

def get_teardown_profile(self) -> TeardownProfile: return TeardownProfile( natural_exit_assets=["WETH", "USDC"], original_entry_assets=["USDC"], recommended_target="USDC", estimated_steps=3, chains_involved=[self.chain], has_lp_positions=True, )

### acknowledge_teardown_request

```
acknowledge_teardown_request() -> bool
```

Acknowledge a pending teardown request.

Called when the strategy picks up a teardown request and starts processing it.

Returns:

| Type   | Description                                       |
| ------ | ------------------------------------------------- |
| `bool` | True if request was acknowledged, False otherwise |

### should_teardown

```
should_teardown() -> bool
```

Check if the strategy should enter teardown mode.

Checks for:

1. Pending teardown request (from CLI, dashboard, config)
1. Auto-protect triggers (health factor, loss limits)

Returns:

| Type   | Description                          |
| ------ | ------------------------------------ |
| `bool` | True if teardown should be initiated |

### on_sadflow_exit

```
on_sadflow_exit(success: bool, total_attempts: int) -> None
```

Hook called when exiting sadflow (on completion or final failure).

Override this method to perform cleanup or logging after sadflow resolution. This is called once when the intent completes (success or failure) after having been in sadflow.

Parameters:

| Name             | Type   | Description                                              | Default    |
| ---------------- | ------ | -------------------------------------------------------- | ---------- |
| `success`        | `bool` | Whether the intent eventually succeeded after retries.   | *required* |
| `total_attempts` | `int`  | Total number of attempts made (including the final one). | *required* |

Example

def on_sadflow_exit(self, success, total_attempts): if success: logger.info(f"Recovered after {total_attempts} attempts") else: logger.error(f"Failed after {total_attempts} attempts") self.notify_operator("Intent failed after all retries")

### on_retry

```
on_retry(
    context: SadflowContext, action: SadflowAction
) -> SadflowAction
```

Hook called before each retry attempt.

Override this method to customize individual retry behavior. This is called before each retry, after the initial on_sadflow_enter call.

Parameters:

| Name      | Type             | Description                                              | Default    |
| --------- | ---------------- | -------------------------------------------------------- | ---------- |
| `context` | `SadflowContext` | SadflowContext with current error details and state.     | *required* |
| `action`  | `SadflowAction`  | The default SadflowAction (RETRY with calculated delay). | *required* |

Returns:

| Name            | Type            | Description                                                    |
| --------------- | --------------- | -------------------------------------------------------------- |
| `SadflowAction` | `SadflowAction` | The action to take. Return the input action unchanged          |
|                 | `SadflowAction` | for default behavior, or return a modified action:             |
|                 | `SadflowAction` | SadflowAction.retry(custom_delay=5.0): Retry with custom delay |
|                 | `SadflowAction` | SadflowAction.abort(reason): Stop retrying and fail            |
|                 | `SadflowAction` | SadflowAction.modify(bundle): Retry with modified ActionBundle |
|                 | `SadflowAction` | SadflowAction.skip(reason): Skip and mark as completed         |

Example

def on_retry(self, context, action):

# After 2 attempts, try with higher gas

if context.attempt_number > 2 and context.action_bundle: modified = self.\_increase_gas(context.action_bundle) return SadflowAction.modify(modified)

```
# Abort if we've been retrying too long
if context.total_duration_seconds > 120:
    return SadflowAction.abort("Retry timeout exceeded")

# Use default retry
return action
```

## StrategyBase

Lower-level base class for strategies that need direct action control.

## almanak.framework.strategies.StrategyBase

```
StrategyBase(
    config: ConfigT,
    risk_guard_config: RiskGuardConfig | None = None,
    notification_callback: NotificationCallback
    | None = None,
)
```

Bases: `ABC`

Base class for all strategies with hot-reload configuration support.

This class provides:

- Hot-reload configuration updates via update_config()
- RiskGuard validation to prevent dangerous config changes
- Atomic config application with rollback on failure
- CONFIG_UPDATED event emission to timeline
- Operator notification support

Strategies should inherit from this class and implement the abstract run() method.

Attributes:

| Name                    | Type             | Description                                               |
| ----------------------- | ---------------- | --------------------------------------------------------- |
| `config`                |                  | Hot-reloadable configuration                              |
| `risk_guard`            |                  | RiskGuard instance for validation                         |
| `persistent_state`      | `dict[str, Any]` | Dict containing strategy state including config snapshots |
| `notification_callback` | `dict[str, Any]` | Optional callback for operator notifications              |

Initialize the strategy base.

Parameters:

| Name                    | Type                   | Description                  | Default                                     |
| ----------------------- | ---------------------- | ---------------------------- | ------------------------------------------- |
| `config`                | `ConfigT`              | Hot-reloadable configuration | *required*                                  |
| `risk_guard_config`     | \`RiskGuardConfig      | None\`                       | Optional RiskGuard configuration            |
| `notification_callback` | \`NotificationCallback | None\`                       | Optional callback for sending notifications |

### strategy_id

```
strategy_id: str
```

Get the strategy ID.

### chain

```
chain: str
```

Get the chain.

### run

```
run() -> Any
```

Execute one iteration of the strategy.

Must be implemented by subclasses.

Returns:

| Type  | Description                              |
| ----- | ---------------------------------------- |
| `Any` | ActionBundle or None if no action needed |

### update_config

```
update_config(
    updates: dict[str, Any], updated_by: str = "operator"
) -> ConfigUpdateResult
```

Update configuration with validation and persistence.

This method:

1. Validates new config values against the config's schema
1. Validates changes against RiskGuard (can't bypass risk limits)
1. Applies changes atomically
1. Persists config snapshot to persistent_state
1. Emits CONFIG_UPDATED event with old and new values
1. Sends notification to operator

Parameters:

| Name         | Type             | Description                                | Default      |
| ------------ | ---------------- | ------------------------------------------ | ------------ |
| `updates`    | `dict[str, Any]` | Dict of field -> new value to update       | *required*   |
| `updated_by` | `str`            | Who is making the update (for audit trail) | `'operator'` |

Returns:

| Type                 | Description                                      |
| -------------------- | ------------------------------------------------ |
| `ConfigUpdateResult` | ConfigUpdateResult indicating success or failure |

### set_notification_callback

```
set_notification_callback(
    callback: NotificationCallback | None,
) -> None
```

Set the notification callback for operator alerts.

Parameters:

| Name       | Type                   | Description | Default                                      |
| ---------- | ---------------------- | ----------- | -------------------------------------------- |
| `callback` | \`NotificationCallback | None\`      | Callback function that takes an OperatorCard |

### get_config_history

```
get_config_history() -> list[dict[str, Any]]
```

Get the configuration update history.

Returns:

| Type                   | Description                                     |
| ---------------------- | ----------------------------------------------- |
| `list[dict[str, Any]]` | List of config snapshots in chronological order |

### get_current_config_version

```
get_current_config_version() -> int
```

Get the current config version number.

Returns:

| Type  | Description            |
| ----- | ---------------------- |
| `int` | Current config version |

### get_config

```
get_config(key: str, default: Any = None) -> Any
```

Get a configuration value by key, regardless of config type.

Works transparently with:

- Plain `dict` configs (from tests or raw JSON)
- `DictConfigWrapper` objects (from the CLI)
- Dataclasses and Pydantic models (attribute access)

This eliminates the per-strategy boilerplate of checking `isinstance(self.config, dict)` before every `config.get()` call.

Parameters:

| Name      | Type  | Description                             | Default    |
| --------- | ----- | --------------------------------------- | ---------- |
| `key`     | `str` | Configuration key to retrieve           | *required* |
| `default` | `Any` | Value to return if the key is not found | `None`     |

Returns:

| Type  | Description                                      |
| ----- | ------------------------------------------------ |
| `Any` | The configuration value, or default if not found |

Example::

```
# In decide() -- no more boilerplate!
trade_size = self.get_config("trade_size_usd", "100")
rsi_period = self.get_config("rsi_period", 14)
```

### restore_config_from_snapshot

```
restore_config_from_snapshot(
    version: int,
) -> ConfigUpdateResult
```

Restore configuration from a previous snapshot.

Parameters:

| Name      | Type  | Description                      | Default    |
| --------- | ----- | -------------------------------- | ---------- |
| `version` | `int` | The config version to restore to | *required* |

Returns:

| Type                 | Description                                      |
| -------------------- | ------------------------------------------------ |
| `ConfigUpdateResult` | ConfigUpdateResult indicating success or failure |

## MarketSnapshot

Unified interface for accessing market data within `decide()`.

## almanak.framework.strategies.MarketSnapshot

```
MarketSnapshot(
    chain: str,
    wallet_address: str,
    price_oracle: PriceOracle | None = None,
    rsi_provider: RSIProvider | None = None,
    balance_provider: BalanceProvider | None = None,
    timestamp: datetime | None = None,
    wallet_activity_provider: WalletActivityProvider
    | None = None,
    prediction_provider: Any | None = None,
    indicator_provider: IndicatorProvider | None = None,
    multi_dex_service: Any | None = None,
    rate_monitor: Any | None = None,
    funding_rate_provider: Any | None = None,
    default_timeframe: str | None = None,
)
```

Helper class providing market data access for strategy decisions.

MarketSnapshot provides a simple interface for strategies to access:

- Token prices
- RSI values
- Wallet balances
- Position information

The snapshot is populated with data at the start of each iteration, allowing strategies to make decisions based on current market conditions.

Example

def decide(self, market: MarketSnapshot) -> Optional\[Intent\]:

# Get ETH price

eth_price = market.price("ETH")

```
# Get RSI
rsi = market.rsi("ETH", period=14)
if rsi.is_oversold:
    return Intent.swap("USDC", "ETH", amount_usd=Decimal("1000"))

# Check balance
balance = market.balance("USDC")
if balance.balance_usd < Decimal("100"):
    return Intent.hold(reason="Insufficient balance")

return Intent.hold()
```

Initialize market snapshot.

Parameters:

| Name                       | Type                     | Description                               | Default                                                                                                                                                                                                                  |
| -------------------------- | ------------------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `chain`                    | `str`                    | Chain name (e.g., "arbitrum", "ethereum") | *required*                                                                                                                                                                                                               |
| `wallet_address`           | `str`                    | Wallet address for balance queries        | *required*                                                                                                                                                                                                               |
| `price_oracle`             | \`PriceOracle            | None\`                                    | Function to fetch prices (token, quote) -> price                                                                                                                                                                         |
| `rsi_provider`             | \`RSIProvider            | None\`                                    | Function to calculate RSI (token, period[, timeframe=]) -> RSIData                                                                                                                                                       |
| `balance_provider`         | \`BalanceProvider        | None\`                                    | Function to fetch balances (token) -> TokenBalance                                                                                                                                                                       |
| `timestamp`                | \`datetime               | None\`                                    | Snapshot timestamp (defaults to now)                                                                                                                                                                                     |
| `wallet_activity_provider` | \`WalletActivityProvider | None\`                                    | Provider for leader wallet activity signals                                                                                                                                                                              |
| `prediction_provider`      | \`Any                    | None\`                                    | PredictionMarketDataProvider for prediction market data                                                                                                                                                                  |
| `indicator_provider`       | \`IndicatorProvider      | None\`                                    | IndicatorProvider for calculator-backed TA indicators                                                                                                                                                                    |
| `multi_dex_service`        | \`Any                    | None\`                                    | MultiDexService for cross-DEX price comparison                                                                                                                                                                           |
| `rate_monitor`             | \`Any                    | None\`                                    | RateMonitor instance for lending rate queries                                                                                                                                                                            |
| `funding_rate_provider`    | \`Any                    | None\`                                    | FundingRateProvider for perpetual funding rate queries                                                                                                                                                                   |
| `default_timeframe`        | \`str                    | None\`                                    | Default OHLCV timeframe from strategy config (e.g., "15m", "1h"). Used as the default for all indicator methods (rsi, macd, sma, etc.) when no explicit timeframe is passed. Falls back to DEFAULT_TIMEFRAME if not set. |

### chain

```
chain: str
```

Get the chain name.

### wallet_address

```
wallet_address: str
```

Get the wallet address.

### timestamp

```
timestamp: datetime
```

Get the snapshot timestamp.

### fork_rpc_url

```
fork_rpc_url: str | None
```

Get the Anvil fork RPC URL for on-chain reads (paper trading only).

Returns the fork's JSON-RPC endpoint when running in paper trading mode, allowing strategies to perform protocol-level reads directly against the fork. Returns None when not in paper trading mode.

VIB-1956: Enables strategies to do protocol-level reads (e.g., Aave getReserveData, DEX pool state) during paper trading.

WARNING: This is a paper-trading-only escape hatch. In production, this returns None. Do NOT gate trading logic on fork_rpc_url availability — strategies that behave differently based on this property will diverge between paper trading and production.

### fork_block

```
fork_block: int | None
```

Get the current fork block number (paper trading only).

### price

```
price(token: str, quote: str = 'USD') -> Decimal
```

Get the price of a token.

Parameters:

| Name    | Type  | Description                        | Default    |
| ------- | ----- | ---------------------------------- | ---------- |
| `token` | `str` | Token symbol (e.g., "ETH", "WBTC") | *required* |
| `quote` | `str` | Quote currency (default "USD")     | `'USD'`    |

Returns:

| Type      | Description                   |
| --------- | ----------------------------- |
| `Decimal` | Token price in quote currency |

Raises:

| Type         | Description                   |
| ------------ | ----------------------------- |
| `ValueError` | If price cannot be determined |

### price_data

```
price_data(token: str, quote: str = 'USD') -> PriceData
```

Get full price data for a token.

Parameters:

| Name    | Type  | Description                    | Default    |
| ------- | ----- | ------------------------------ | ---------- |
| `token` | `str` | Token symbol                   | *required* |
| `quote` | `str` | Quote currency (default "USD") | `'USD'`    |

Returns:

| Type        | Description                                      |
| ----------- | ------------------------------------------------ |
| `PriceData` | PriceData with current price and historical data |

### rsi

```
rsi(
    token: str,
    period: int = 14,
    timeframe: str | None = None,
) -> RSIData
```

Get RSI (Relative Strength Index) for a token.

Parameters:

| Name        | Type  | Description                         | Default                                                                                            |
| ----------- | ----- | ----------------------------------- | -------------------------------------------------------------------------------------------------- |
| `token`     | `str` | Token symbol                        | *required*                                                                                         |
| `period`    | `int` | RSI calculation period (default 14) | `14`                                                                                               |
| `timeframe` | \`str | None\`                              | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type      | Description                               |
| --------- | ----------------------------------------- |
| `RSIData` | RSIData with current RSI value and signal |

Raises:

| Type         | Description                 |
| ------------ | --------------------------- |
| `ValueError` | If RSI cannot be calculated |

### price_across_dexs

```
price_across_dexs(
    token_in: str,
    token_out: str,
    amount: Decimal,
    dexs: list[str] | None = None,
) -> Any
```

Get prices from multiple DEXs for comparison.

Fetches quotes from all configured DEXs and returns a comparison of prices and execution details.

Parameters:

| Name        | Type        | Description                                | Default                                         |
| ----------- | ----------- | ------------------------------------------ | ----------------------------------------------- |
| `token_in`  | `str`       | Input token symbol (e.g., "USDC", "WETH")  | *required*                                      |
| `token_out` | `str`       | Output token symbol (e.g., "WETH", "USDC") | *required*                                      |
| `amount`    | `Decimal`   | Input amount (human-readable)              | *required*                                      |
| `dexs`      | \`list[str] | None\`                                     | DEXs to query (default: all available on chain) |

Returns:

| Type  | Description                                   |
| ----- | --------------------------------------------- |
| `Any` | MultiDexPriceResult with quotes from each DEX |

Raises:

| Type                  | Description                            |
| --------------------- | -------------------------------------- |
| `NotImplementedError` | If multi-DEX service is not configured |

### best_dex_price

```
best_dex_price(
    token_in: str,
    token_out: str,
    amount: Decimal,
    dexs: list[str] | None = None,
) -> Any
```

Get the best DEX for a trade.

Compares prices from all configured DEXs and returns the one with the highest output amount (best execution).

Parameters:

| Name        | Type        | Description                                | Default                                           |
| ----------- | ----------- | ------------------------------------------ | ------------------------------------------------- |
| `token_in`  | `str`       | Input token symbol (e.g., "USDC", "WETH")  | *required*                                        |
| `token_out` | `str`       | Output token symbol (e.g., "WETH", "USDC") | *required*                                        |
| `amount`    | `Decimal`   | Input amount (human-readable)              | *required*                                        |
| `dexs`      | \`list[str] | None\`                                     | DEXs to compare (default: all available on chain) |

Returns:

| Type  | Description                               |
| ----- | ----------------------------------------- |
| `Any` | BestDexResult with the best DEX and quote |

Raises:

| Type                  | Description                            |
| --------------------- | -------------------------------------- |
| `NotImplementedError` | If multi-DEX service is not configured |

### macd

```
macd(
    token: str,
    fast_period: int = 12,
    slow_period: int = 26,
    signal_period: int = 9,
    timeframe: str | None = None,
) -> MACDData
```

Get MACD (Moving Average Convergence Divergence) for a token.

Parameters:

| Name            | Type  | Description                   | Default                                                                                            |
| --------------- | ----- | ----------------------------- | -------------------------------------------------------------------------------------------------- |
| `token`         | `str` | Token symbol                  | *required*                                                                                         |
| `fast_period`   | `int` | Fast EMA period (default 12)  | `12`                                                                                               |
| `slow_period`   | `int` | Slow EMA period (default 26)  | `26`                                                                                               |
| `signal_period` | `int` | Signal EMA period (default 9) | `9`                                                                                                |
| `timeframe`     | \`str | None\`                        | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type       | Description                                         |
| ---------- | --------------------------------------------------- |
| `MACDData` | MACDData with MACD line, signal line, and histogram |

Raises:

| Type         | Description                   |
| ------------ | ----------------------------- |
| `ValueError` | If MACD data is not available |

Example

macd = market.macd("WETH") if macd.is_bullish_crossover: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))

### bollinger_bands

```
bollinger_bands(
    token: str,
    period: int = 20,
    std_dev: float = 2.0,
    timeframe: str | None = None,
) -> BollingerBandsData
```

Get Bollinger Bands for a token.

Parameters:

| Name        | Type    | Description                                 | Default                                                                                            |
| ----------- | ------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `token`     | `str`   | Token symbol                                | *required*                                                                                         |
| `period`    | `int`   | SMA period (default 20)                     | `20`                                                                                               |
| `std_dev`   | `float` | Standard deviation multiplier (default 2.0) | `2.0`                                                                                              |
| `timeframe` | \`str   | None\`                                      | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type                 | Description                                                             |
| -------------------- | ----------------------------------------------------------------------- |
| `BollingerBandsData` | BollingerBandsData with upper, middle, lower bands and position metrics |

Raises:

| Type         | Description                              |
| ------------ | ---------------------------------------- |
| `ValueError` | If Bollinger Bands data is not available |

Example

bb = market.bollinger_bands("WETH") if bb.is_oversold: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))

### stochastic

```
stochastic(
    token: str,
    k_period: int = 14,
    d_period: int = 3,
    timeframe: str | None = None,
) -> StochasticData
```

Get Stochastic Oscillator for a token.

Parameters:

| Name        | Type  | Description            | Default                                                                                            |
| ----------- | ----- | ---------------------- | -------------------------------------------------------------------------------------------------- |
| `token`     | `str` | Token symbol           | *required*                                                                                         |
| `k_period`  | `int` | %K period (default 14) | `14`                                                                                               |
| `d_period`  | `int` | %D period (default 3)  | `3`                                                                                                |
| `timeframe` | \`str | None\`                 | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type             | Description                          |
| ---------------- | ------------------------------------ |
| `StochasticData` | StochasticData with %K and %D values |

Raises:

| Type         | Description                         |
| ------------ | ----------------------------------- |
| `ValueError` | If Stochastic data is not available |

Example

stoch = market.stochastic("WETH") if stoch.is_oversold and stoch.k_value > stoch.d_value: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))

### atr

```
atr(
    token: str,
    period: int = 14,
    timeframe: str | None = None,
) -> ATRData
```

Get ATR (Average True Range) for a token.

Parameters:

| Name        | Type  | Description             | Default                                                                                            |
| ----------- | ----- | ----------------------- | -------------------------------------------------------------------------------------------------- |
| `token`     | `str` | Token symbol            | *required*                                                                                         |
| `period`    | `int` | ATR period (default 14) | `14`                                                                                               |
| `timeframe` | \`str | None\`                  | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type      | Description                                      |
| --------- | ------------------------------------------------ |
| `ATRData` | ATRData with ATR value and volatility assessment |

Raises:

| Type         | Description                  |
| ------------ | ---------------------------- |
| `ValueError` | If ATR data is not available |

Example

atr = market.atr("WETH") if atr.is_low_volatility:

# Safe to trade

return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))

### sma

```
sma(
    token: str,
    period: int = 20,
    timeframe: str | None = None,
) -> MAData
```

Get Simple Moving Average for a token.

Parameters:

| Name        | Type  | Description             | Default                                                                                            |
| ----------- | ----- | ----------------------- | -------------------------------------------------------------------------------------------------- |
| `token`     | `str` | Token symbol            | *required*                                                                                         |
| `period`    | `int` | SMA period (default 20) | `20`                                                                                               |
| `timeframe` | \`str | None\`                  | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type     | Description           |
| -------- | --------------------- |
| `MAData` | MAData with SMA value |

Raises:

| Type         | Description                  |
| ------------ | ---------------------------- |
| `ValueError` | If SMA data is not available |

Example

sma = market.sma("WETH", period=50) if sma.is_price_above: print("Bullish - price above 50 SMA")

### ema

```
ema(
    token: str,
    period: int = 12,
    timeframe: str | None = None,
) -> MAData
```

Get Exponential Moving Average for a token.

Parameters:

| Name        | Type  | Description             | Default                                                                                            |
| ----------- | ----- | ----------------------- | -------------------------------------------------------------------------------------------------- |
| `token`     | `str` | Token symbol            | *required*                                                                                         |
| `period`    | `int` | EMA period (default 12) | `12`                                                                                               |
| `timeframe` | \`str | None\`                  | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type     | Description           |
| -------- | --------------------- |
| `MAData` | MAData with EMA value |

Raises:

| Type         | Description                  |
| ------------ | ---------------------------- |
| `ValueError` | If EMA data is not available |

Example

ema_12 = market.ema("WETH", period=12) ema_26 = market.ema("WETH", period=26) if ema_12.value > ema_26.value: print("Golden cross - bullish")

### adx

```
adx(
    token: str,
    period: int = 14,
    timeframe: str | None = None,
) -> ADXData
```

Get ADX (Average Directional Index) for a token.

Parameters:

| Name        | Type  | Description             | Default                                                                                            |
| ----------- | ----- | ----------------------- | -------------------------------------------------------------------------------------------------- |
| `token`     | `str` | Token symbol            | *required*                                                                                         |
| `period`    | `int` | ADX period (default 14) | `14`                                                                                               |
| `timeframe` | \`str | None\`                  | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type      | Description                           |
| --------- | ------------------------------------- |
| `ADXData` | ADXData with ADX, +DI, and -DI values |

Raises:

| Type         | Description                  |
| ------------ | ---------------------------- |
| `ValueError` | If ADX data is not available |

Example

adx = market.adx("WETH") if adx.is_uptrend: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))

### obv

```
obv(
    token: str,
    signal_period: int = 21,
    timeframe: str | None = None,
) -> OBVData
```

Get OBV (On-Balance Volume) for a token.

Parameters:

| Name            | Type  | Description                         | Default                                                                                            |
| --------------- | ----- | ----------------------------------- | -------------------------------------------------------------------------------------------------- |
| `token`         | `str` | Token symbol                        | *required*                                                                                         |
| `signal_period` | `int` | OBV signal line period (default 21) | `21`                                                                                               |
| `timeframe`     | \`str | None\`                              | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type      | Description                             |
| --------- | --------------------------------------- |
| `OBVData` | OBVData with OBV and signal line values |

Raises:

| Type         | Description                  |
| ------------ | ---------------------------- |
| `ValueError` | If OBV data is not available |

Example

obv = market.obv("WETH") if obv.is_bullish: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))

### cci

```
cci(
    token: str,
    period: int = 20,
    timeframe: str | None = None,
) -> CCIData
```

Get CCI (Commodity Channel Index) for a token.

Parameters:

| Name        | Type  | Description             | Default                                                                                            |
| ----------- | ----- | ----------------------- | -------------------------------------------------------------------------------------------------- |
| `token`     | `str` | Token symbol            | *required*                                                                                         |
| `period`    | `int` | CCI period (default 20) | `20`                                                                                               |
| `timeframe` | \`str | None\`                  | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type      | Description                                           |
| --------- | ----------------------------------------------------- |
| `CCIData` | CCIData with CCI value and overbought/oversold status |

Raises:

| Type         | Description                  |
| ------------ | ---------------------------- |
| `ValueError` | If CCI data is not available |

Example

cci = market.cci("WETH") if cci.is_oversold: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))

### ichimoku

```
ichimoku(
    token: str,
    tenkan_period: int = 9,
    kijun_period: int = 26,
    senkou_b_period: int = 52,
    timeframe: str | None = None,
) -> IchimokuData
```

Get Ichimoku Cloud data for a token.

Parameters:

| Name              | Type  | Description                        | Default                                                                                            |
| ----------------- | ----- | ---------------------------------- | -------------------------------------------------------------------------------------------------- |
| `token`           | `str` | Token symbol                       | *required*                                                                                         |
| `tenkan_period`   | `int` | Conversion line period (default 9) | `9`                                                                                                |
| `kijun_period`    | `int` | Base line period (default 26)      | `26`                                                                                               |
| `senkou_b_period` | `int` | Leading span B period (default 52) | `52`                                                                                               |
| `timeframe`       | \`str | None\`                             | OHLCV candle timeframe. Defaults to strategy's data_granularity config, or "4h" if not configured. |

Returns:

| Type           | Description                               |
| -------------- | ----------------------------------------- |
| `IchimokuData` | IchimokuData with all Ichimoku components |

Raises:

| Type         | Description                       |
| ------------ | --------------------------------- |
| `ValueError` | If Ichimoku data is not available |

Example

ich = market.ichimoku("WETH") if ich.is_bullish_crossover and ich.is_above_cloud: return Intent.swap("USDC", "WETH", amount_usd=Decimal("100"))

### balance

```
balance(token: str) -> TokenBalance
```

Get wallet balance for a token.

Parameters:

| Name    | Type  | Description  | Default    |
| ------- | ----- | ------------ | ---------- |
| `token` | `str` | Token symbol | *required* |

Returns:

| Type           | Description                       |
| -------------- | --------------------------------- |
| `TokenBalance` | TokenBalance with current balance |

Raises:

| Type         | Description                     |
| ------------ | ------------------------------- |
| `ValueError` | If balance cannot be determined |

### balance_usd

```
balance_usd(token: str) -> Decimal
```

Get wallet balance in USD terms.

Parameters:

| Name    | Type  | Description  | Default    |
| ------- | ----- | ------------ | ---------- |
| `token` | `str` | Token symbol | *required* |

Returns:

| Type      | Description    |
| --------- | -------------- |
| `Decimal` | Balance in USD |

### collateral_value_usd

```
collateral_value_usd(
    token: str, amount: Decimal
) -> Decimal
```

Get the USD value of a given amount of collateral.

Convenience helper for perp position sizing. Multiplies the given amount by the token's current price.

Parameters:

| Name     | Type      | Description                                    | Default    |
| -------- | --------- | ---------------------------------------------- | ---------- |
| `token`  | `str`     | Token symbol (e.g., "WETH", "USDC", "WBTC")    | *required* |
| `amount` | `Decimal` | Token amount in human-readable units (not wei) | *required* |

Returns:

| Type      | Description            |
| --------- | ---------------------- |
| `Decimal` | USD value as a Decimal |

### total_portfolio_usd

```
total_portfolio_usd() -> Decimal
```

Calculate total portfolio value in USD across all known balances.

Sums balance_usd for all tokens in pre-populated balances and cached balances (tokens queried via balance() in this snapshot).

Returns:

| Type      | Description                  |
| --------- | ---------------------------- |
| `Decimal` | Total portfolio value in USD |

### set_price

```
set_price(token: str, price_value: Decimal) -> None
```

Pre-populate price for a token.

Parameters:

| Name          | Type      | Description        | Default    |
| ------------- | --------- | ------------------ | ---------- |
| `token`       | `str`     | Token symbol       | *required* |
| `price_value` | `Decimal` | Price value in USD | *required* |

### set_price_data

```
set_price_data(
    token: str, price_data: PriceData, quote: str = "USD"
) -> None
```

Pre-populate enriched price data for a token (useful for testing).

Unlike set_price() which only sets a scalar price, this sets the full PriceData object including change_24h_pct, high_24h, low_24h, etc.

Parameters:

| Name         | Type        | Description                                | Default    |
| ------------ | ----------- | ------------------------------------------ | ---------- |
| `token`      | `str`       | Token symbol                               | *required* |
| `price_data` | `PriceData` | PriceData with price, change_24h_pct, etc. | *required* |
| `quote`      | `str`       | Quote currency (default "USD")             | `'USD'`    |

### set_balance

```
set_balance(token: str, balance_data: TokenBalance) -> None
```

Pre-populate balance for a token.

Parameters:

| Name           | Type           | Description  | Default    |
| -------------- | -------------- | ------------ | ---------- |
| `token`        | `str`          | Token symbol | *required* |
| `balance_data` | `TokenBalance` | Balance data | *required* |

### set_rsi

```
set_rsi(
    token: str,
    rsi_data: RSIData,
    timeframe: str | None = None,
) -> None
```

Pre-populate RSI for a token.

Parameters:

| Name        | Type      | Description  | Default                                                        |
| ----------- | --------- | ------------ | -------------------------------------------------------------- |
| `token`     | `str`     | Token symbol | *required*                                                     |
| `rsi_data`  | `RSIData` | RSI data     | *required*                                                     |
| `timeframe` | \`str     | None\`       | OHLCV timeframe this data was computed from (None matches any) |

### set_macd

```
set_macd(
    token: str,
    macd_data: MACDData,
    timeframe: str | None = None,
) -> None
```

Pre-populate MACD data for a token.

Parameters:

| Name        | Type       | Description       | Default                                                        |
| ----------- | ---------- | ----------------- | -------------------------------------------------------------- |
| `token`     | `str`      | Token symbol      | *required*                                                     |
| `macd_data` | `MACDData` | MACDData instance | *required*                                                     |
| `timeframe` | \`str      | None\`            | OHLCV timeframe this data was computed from (None matches any) |

Example

market.set_macd("WETH", MACDData( macd_line=Decimal("0.5"), signal_line=Decimal("0.3"), histogram=Decimal("0.2"), ))

### set_bollinger_bands

```
set_bollinger_bands(
    token: str,
    bb_data: BollingerBandsData,
    timeframe: str | None = None,
) -> None
```

Pre-populate Bollinger Bands data for a token.

Parameters:

| Name        | Type                 | Description                 | Default                                                        |
| ----------- | -------------------- | --------------------------- | -------------------------------------------------------------- |
| `token`     | `str`                | Token symbol                | *required*                                                     |
| `bb_data`   | `BollingerBandsData` | BollingerBandsData instance | *required*                                                     |
| `timeframe` | \`str                | None\`                      | OHLCV timeframe this data was computed from (None matches any) |

Example

market.set_bollinger_bands("WETH", BollingerBandsData( upper_band=Decimal("3100"), middle_band=Decimal("3000"), lower_band=Decimal("2900"), percent_b=Decimal("0.5"), ))

### set_stochastic

```
set_stochastic(
    token: str,
    stoch_data: StochasticData,
    timeframe: str | None = None,
) -> None
```

Pre-populate Stochastic data for a token.

Parameters:

| Name         | Type             | Description             | Default                                                        |
| ------------ | ---------------- | ----------------------- | -------------------------------------------------------------- |
| `token`      | `str`            | Token symbol            | *required*                                                     |
| `stoch_data` | `StochasticData` | StochasticData instance | *required*                                                     |
| `timeframe`  | \`str            | None\`                  | OHLCV timeframe this data was computed from (None matches any) |

Example

market.set_stochastic("WETH", StochasticData( k_value=Decimal("25"), d_value=Decimal("30"), ))

### set_atr

```
set_atr(
    token: str,
    atr_data: ATRData,
    timeframe: str | None = None,
) -> None
```

Pre-populate ATR data for a token.

Parameters:

| Name        | Type      | Description      | Default                                                        |
| ----------- | --------- | ---------------- | -------------------------------------------------------------- |
| `token`     | `str`     | Token symbol     | *required*                                                     |
| `atr_data`  | `ATRData` | ATRData instance | *required*                                                     |
| `timeframe` | \`str     | None\`           | OHLCV timeframe this data was computed from (None matches any) |

Example

market.set_atr("WETH", ATRData( value=Decimal("50"), value_percent=Decimal("2.5"), ))

### set_ma

```
set_ma(
    token: str,
    ma_data: MAData,
    ma_type: str = "SMA",
    period: int = 20,
    timeframe: str | None = None,
) -> None
```

Pre-populate Moving Average data for a token.

Parameters:

| Name        | Type     | Description                 | Default                                                        |
| ----------- | -------- | --------------------------- | -------------------------------------------------------------- |
| `token`     | `str`    | Token symbol                | *required*                                                     |
| `ma_data`   | `MAData` | MAData instance             | *required*                                                     |
| `ma_type`   | `str`    | Type of MA ("SMA" or "EMA") | `'SMA'`                                                        |
| `period`    | `int`    | MA period                   | `20`                                                           |
| `timeframe` | \`str    | None\`                      | OHLCV timeframe this data was computed from (None matches any) |

Example

market.set_ma("WETH", MAData( value=Decimal("3000"), ma_type="SMA", period=20, current_price=Decimal("3050"), ), ma_type="SMA", period=20)

### set_adx

```
set_adx(
    token: str,
    adx_data: ADXData,
    timeframe: str | None = None,
) -> None
```

Pre-populate ADX data for a token.

Parameters:

| Name        | Type      | Description      | Default                                     |
| ----------- | --------- | ---------------- | ------------------------------------------- |
| `token`     | `str`     | Token symbol     | *required*                                  |
| `adx_data`  | `ADXData` | ADXData instance | *required*                                  |
| `timeframe` | \`str     | None\`           | Optional timeframe (None matches any query) |

Example

market.set_adx("WETH", ADXData( adx=Decimal("30"), plus_di=Decimal("25"), minus_di=Decimal("15"), ))

### set_obv

```
set_obv(
    token: str,
    obv_data: OBVData,
    timeframe: str | None = None,
) -> None
```

Pre-populate OBV data for a token.

Parameters:

| Name        | Type      | Description      | Default                                     |
| ----------- | --------- | ---------------- | ------------------------------------------- |
| `token`     | `str`     | Token symbol     | *required*                                  |
| `obv_data`  | `OBVData` | OBVData instance | *required*                                  |
| `timeframe` | \`str     | None\`           | Optional timeframe (None matches any query) |

Example

market.set_obv("WETH", OBVData( obv=Decimal("1000000"), signal_line=Decimal("950000"), ))

### set_cci

```
set_cci(
    token: str,
    cci_data: CCIData,
    timeframe: str | None = None,
) -> None
```

Pre-populate CCI data for a token.

Parameters:

| Name        | Type      | Description      | Default                                     |
| ----------- | --------- | ---------------- | ------------------------------------------- |
| `token`     | `str`     | Token symbol     | *required*                                  |
| `cci_data`  | `CCIData` | CCIData instance | *required*                                  |
| `timeframe` | \`str     | None\`           | Optional timeframe (None matches any query) |

Example

market.set_cci("WETH", CCIData( value=Decimal("-120"), ))

### set_ichimoku

```
set_ichimoku(
    token: str,
    ichimoku_data: IchimokuData,
    timeframe: str | None = None,
) -> None
```

Pre-populate Ichimoku data for a token.

Parameters:

| Name            | Type           | Description           | Default                                     |
| --------------- | -------------- | --------------------- | ------------------------------------------- |
| `token`         | `str`          | Token symbol          | *required*                                  |
| `ichimoku_data` | `IchimokuData` | IchimokuData instance | *required*                                  |
| `timeframe`     | \`str          | None\`                | Optional timeframe (None matches any query) |

Example

market.set_ichimoku("WETH", IchimokuData( tenkan_sen=Decimal("3050"), kijun_sen=Decimal("3000"), senkou_span_a=Decimal("3025"), senkou_span_b=Decimal("2950"), current_price=Decimal("3100"), ))

### lending_rate

```
lending_rate(
    protocol: str, token: str, side: str = "supply"
) -> Any
```

Get the lending rate for a specific protocol and token.

Fetches the current supply or borrow APY from the specified lending protocol. Rates are cached for efficiency (typically 12s = ~1 block).

Parameters:

| Name       | Type  | Description                                             | Default    |
| ---------- | ----- | ------------------------------------------------------- | ---------- |
| `protocol` | `str` | Protocol identifier (aave_v3, morpho_blue, compound_v3) | *required* |
| `token`    | `str` | Token symbol (e.g., "USDC", "WETH")                     | *required* |
| `side`     | `str` | Rate side - "supply" or "borrow" (default "supply")     | `'supply'` |

Returns:

| Type  | Description                                                                |
| ----- | -------------------------------------------------------------------------- |
| `Any` | LendingRate dataclass with apy_percent, apy_ray, utilization_percent, etc. |

Raises:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `ValueError` | If no rate monitor is configured |

Example

rate = market.lending_rate("aave_v3", "USDC", "supply") print(f"Aave USDC Supply APY: {rate.apy_percent:.2f}%")

### best_lending_rate

```
best_lending_rate(
    token: str,
    side: str = "supply",
    protocols: list[str] | None = None,
) -> Any
```

Get the best lending rate for a token across protocols.

For supply rates, returns highest APY. For borrow rates, returns lowest APY.

Parameters:

| Name        | Type        | Description                                         | Default                                                |
| ----------- | ----------- | --------------------------------------------------- | ------------------------------------------------------ |
| `token`     | `str`       | Token symbol (e.g., "USDC", "WETH")                 | *required*                                             |
| `side`      | `str`       | Rate side - "supply" or "borrow" (default "supply") | `'supply'`                                             |
| `protocols` | \`list[str] | None\`                                              | Protocols to compare (default: all available on chain) |

Returns:

| Type  | Description                                    |
| ----- | ---------------------------------------------- |
| `Any` | BestRateResult with best_rate, all_rates, etc. |

Raises:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `ValueError` | If no rate monitor is configured |

Example

result = market.best_lending_rate("USDC", "supply") if result.best_rate: print(f"Best: {result.best_rate.protocol} at {result.best_rate.apy_percent:.2f}%")

### set_lending_rate

```
set_lending_rate(
    protocol: str, token: str, side: str, rate: Any
) -> None
```

Pre-populate a lending rate for a protocol/token/side.

Useful for backtesting and testing where you want to inject known rates without needing a live RateMonitor.

Parameters:

| Name       | Type  | Description                           | Default    |
| ---------- | ----- | ------------------------------------- | ---------- |
| `protocol` | `str` | Protocol identifier (e.g., "aave_v3") | *required* |
| `token`    | `str` | Token symbol (e.g., "USDC")           | *required* |
| `side`     | `str` | Rate side ("supply" or "borrow")      | *required* |
| `rate`     | `Any` | LendingRate dataclass instance        | *required* |

Example

from almanak.framework.data.rates import LendingRate market.set_lending_rate("aave_v3", "USDC", "supply", LendingRate( protocol="aave_v3", token="USDC", side="supply", apy_ray=Decimal("0"), apy_percent=Decimal("4.25"), ))

### funding_rate

```
funding_rate(venue: str, market: str) -> FundingRate
```

Get the current funding rate for a perpetual market on a specific venue.

Parameters:

| Name     | Type  | Description                                      | Default    |
| -------- | ----- | ------------------------------------------------ | ---------- |
| `venue`  | `str` | Venue identifier (e.g., "gmx_v2", "hyperliquid") | *required* |
| `market` | `str` | Market symbol (e.g., "ETH-USD")                  | *required* |

Returns:

| Type          | Description                                                            |
| ------------- | ---------------------------------------------------------------------- |
| `FundingRate` | FundingRate dataclass with rate_hourly, rate_8h, rate_annualized, etc. |

Raises:

| Type         | Description                                                       |
| ------------ | ----------------------------------------------------------------- |
| `ValueError` | If no funding rate provider is configured or venue is unsupported |

### funding_rate_spread

```
funding_rate_spread(
    market: str, venue_a: str, venue_b: str
) -> FundingRateSpread
```

Get the funding rate spread between two venues.

Parameters:

| Name      | Type  | Description                     | Default    |
| --------- | ----- | ------------------------------- | ---------- |
| `market`  | `str` | Market symbol (e.g., "ETH-USD") | *required* |
| `venue_a` | `str` | First venue identifier          | *required* |
| `venue_b` | `str` | Second venue identifier         | *required* |

Returns:

| Type                | Description                                                                       |
| ------------------- | --------------------------------------------------------------------------------- |
| `FundingRateSpread` | FundingRateSpread dataclass with spread_hourly, spread_annualized, rate_a, rate_b |

Raises:

| Type         | Description                                                       |
| ------------ | ----------------------------------------------------------------- |
| `ValueError` | If no funding rate provider is configured or venue is unsupported |

### wallet_activity

```
wallet_activity(
    leader_address: str | None = None,
    action_types: list[str] | None = None,
    min_usd_value: Decimal | None = None,
    protocols: list[str] | None = None,
) -> list
```

Get leader wallet activity signals for copy trading.

Returns filtered signals from the WalletActivityProvider. If no provider is configured, returns an empty list (graceful degradation).

Parameters:

| Name             | Type        | Description | Default                                         |
| ---------------- | ----------- | ----------- | ----------------------------------------------- |
| `leader_address` | \`str       | None\`      | Filter by specific leader wallet address        |
| `action_types`   | \`list[str] | None\`      | Filter by action types (e.g., ["SWAP"])         |
| `min_usd_value`  | \`Decimal   | None\`      | Minimum USD value filter                        |
| `protocols`      | \`list[str] | None\`      | Filter by protocol names (e.g., ["uniswap_v3"]) |

Returns:

| Type   | Description                                     |
| ------ | ----------------------------------------------- |
| `list` | List of CopySignal objects matching the filters |

### prediction_price

```
prediction_price(
    market_id: str, outcome: str
) -> Decimal | None
```

Get current price for a prediction market outcome.

Convenience method that extracts the YES or NO price from a market.

Parameters:

| Name        | Type  | Description                      | Default    |
| ----------- | ----- | -------------------------------- | ---------- |
| `market_id` | `str` | Prediction market ID or URL slug | *required* |
| `outcome`   | `str` | "YES" or "NO"                    | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

Example

yes_price = market.prediction_price("btc-100k", "YES") if yes_price is not None and yes_price < Decimal("0.3"): return BuyIntent(...)

### get_price_oracle_dict

```
get_price_oracle_dict() -> dict[str, Decimal]
```

Get all prices as a dict suitable for IntentCompiler.

Combines pre-populated prices and cached prices from oracle calls. Keys are normalized to uppercase to match Token.symbol (which is always uppercased by Token.**post_init**). This prevents case-mismatch lookup failures for mixed-case tokens like cbETH, wstETH, crvUSD, sUSDe, etc.

Returns:

| Type                 | Description                                        |
| -------------------- | -------------------------------------------------- |
| `dict[str, Decimal]` | Dict mapping uppercase token symbols to USD prices |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert snapshot to dictionary.

## RiskGuard

Non-bypassable risk validation that runs before every execution.

## almanak.framework.strategies.RiskGuard

```
RiskGuard(config: RiskGuardConfig | None = None)
```

Validates configuration changes against risk limits.

RiskGuard ensures that configuration updates cannot bypass critical risk limits defined by the operator or system. It provides human-readable guidance when validation fails.

Initialize RiskGuard with configuration.

Parameters:

| Name     | Type              | Description | Default                                                  |
| -------- | ----------------- | ----------- | -------------------------------------------------------- |
| `config` | \`RiskGuardConfig | None\`      | Risk guard configuration (uses defaults if not provided) |

### generate_guidance

```
generate_guidance(
    field_name: str,
    requested_value: Decimal,
    limit_value: Decimal,
) -> RiskGuardGuidance
```

Generate human-readable guidance for a failed risk check.

This method creates a RiskGuardGuidance object with:

- What the limit is and what value was requested
- Clear explanation of what the limit protects against
- Actionable suggestion for how to proceed

Parameters:

| Name              | Type      | Description                                    | Default    |
| ----------------- | --------- | ---------------------------------------------- | ---------- |
| `field_name`      | `str`     | The configuration field that failed validation | *required* |
| `requested_value` | `Decimal` | The value the operator tried to set            | *required* |
| `limit_value`     | `Decimal` | The maximum/minimum allowed value              | *required* |

Returns:

| Type                | Description                                                      |
| ------------------- | ---------------------------------------------------------------- |
| `RiskGuardGuidance` | RiskGuardGuidance with human-readable explanation and suggestion |

### validate_config_update

```
validate_config_update(
    current_config: HotReloadableConfig,
    updates: dict[str, Any],
) -> RiskGuardResult
```

Validate proposed configuration updates against risk limits.

This method checks each proposed update against the risk guard's limits to ensure operators cannot accidentally configure dangerous parameters. When validation fails, it includes human-readable guidance explaining what went wrong and how to fix it.

Parameters:

| Name             | Type                  | Description                                 | Default    |
| ---------------- | --------------------- | ------------------------------------------- | ---------- |
| `current_config` | `HotReloadableConfig` | Current configuration                       | *required* |
| `updates`        | `dict[str, Any]`      | Dict of field -> new value proposed updates | *required* |

Returns:

| Type              | Description                                                           |
| ----------------- | --------------------------------------------------------------------- |
| `RiskGuardResult` | RiskGuardResult indicating if validation passed, with guidance if not |

## RiskGuardConfig

## almanak.framework.strategies.RiskGuardConfig

```
RiskGuardConfig(
    max_slippage_limit: Decimal = Decimal("0.1"),
    max_leverage_limit: Decimal = Decimal("10"),
    max_daily_loss_limit_usd: Decimal = Decimal("100000"),
    min_health_factor_floor: Decimal = Decimal("1.05"),
)
```

Configuration for RiskGuard validation.

Defines the maximum allowed values for risk-sensitive parameters. These limits cannot be exceeded through hot-reload updates.

Attributes:

| Name                       | Type      | Description                                |
| -------------------------- | --------- | ------------------------------------------ |
| `max_slippage_limit`       | `Decimal` | Maximum allowed slippage (e.g., 0.05 = 5%) |
| `max_leverage_limit`       | `Decimal` | Maximum allowed leverage (e.g., 10x)       |
| `max_daily_loss_limit_usd` | `Decimal` | Maximum allowed daily loss limit           |
| `min_health_factor_floor`  | `Decimal` | Minimum allowed health factor setting      |

## DecideResult

## almanak.framework.strategies.DecideResult

```
DecideResult = (
    AnyIntent
    | IntentSequence
    | list[AnyIntent | IntentSequence]
    | None
)
```

## IntentSequence

## almanak.framework.strategies.IntentSequence

```
IntentSequence(
    intents: list[AnyIntent],
    sequence_id: str = (lambda: str(uuid.uuid4()))(),
    created_at: datetime = (lambda: datetime.now(UTC))(),
    description: str | None = None,
)
```

A sequence of intents that must execute in order (dependent actions).

IntentSequence wraps a list of intents that have dependencies between them and must execute sequentially. This is used when the output of one intent feeds into the input of the next (e.g., swap output -> bridge input).

Intents that are NOT in a sequence can execute in parallel if they are independent (e.g., two swaps on different chains).

Attributes:

| Name          | Type              | Description                             |
| ------------- | ----------------- | --------------------------------------- |
| `intents`     | `list[AnyIntent]` | List of intents to execute in order     |
| `sequence_id` | `str`             | Unique identifier for this sequence     |
| `created_at`  | `datetime`        | Timestamp when the sequence was created |
| `description` | \`str             | None\`                                  |

Example

### Create a sequence of dependent actions

sequence = Intent.sequence([ Intent.swap("USDC", "ETH", amount=Decimal("1000"), chain="base"), Intent.bridge(token="ETH", amount="all", from_chain="base", to_chain="arbitrum"), Intent.supply(protocol="aave_v3", token="WETH", amount="all", chain="arbitrum"), ])

### Return from decide() - will execute sequentially

return sequence

### first

```
first: AnyIntent
```

Get the first intent in the sequence.

### last

```
last: AnyIntent
```

Get the last intent in the sequence.

### __post_init__

```
__post_init__() -> None
```

Validate the sequence.

### __len__

```
__len__() -> int
```

Return the number of intents in the sequence.

### __iter__

```
__iter__()
```

Iterate over intents in the sequence.

### __getitem__

```
__getitem__(index: int) -> AnyIntent
```

Get intent at index.

### serialize

```
serialize() -> dict[str, Any]
```

Serialize the sequence to a dictionary.

### deserialize

```
deserialize(data: dict[str, Any]) -> IntentSequence
```

Deserialize a dictionary to an IntentSequence.

Note: This requires the Intent.deserialize function to be available, which creates a circular dependency. The actual deserialization is done in the Intent class.

## ExecutionResult

## almanak.framework.strategies.ExecutionResult

```
ExecutionResult(
    intent: AnyIntent | None,
    action_bundle: ActionBundle | None = None,
    state_machine_result: StepResult | None = None,
    success: bool = False,
    error: str | None = None,
    execution_time_ms: float = 0.0,
)
```

Result of strategy execution.

Attributes:

| Name                   | Type           | Description                              |
| ---------------------- | -------------- | ---------------------------------------- |
| `intent`               | \`AnyIntent    | None\`                                   |
| `action_bundle`        | \`ActionBundle | None\`                                   |
| `state_machine_result` | \`StepResult   | None\`                                   |
| `success`              | `bool`         | Whether execution was successful         |
| `error`                | \`str          | None\`                                   |
| `execution_time_ms`    | `float`        | Time taken for execution in milliseconds |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

## Market Data Types

Data types returned by `MarketSnapshot` getters and accepted by `set_*` methods for unit testing.

### TokenBalance

## almanak.framework.strategies.TokenBalance

```
TokenBalance(
    symbol: str,
    balance: Decimal,
    balance_usd: Decimal,
    address: str = "",
)
```

Balance information for a single token.

Supports numeric comparisons so strategy authors can write

if market.balance("ETH") > Decimal("1.0"): ... amount = min(trade_size, market.balance("USDC"))

Comparisons delegate to the `balance` field (native units).

Attributes:

| Name          | Type      | Description                        |
| ------------- | --------- | ---------------------------------- |
| `symbol`      | `str`     | Token symbol (e.g., "ETH", "USDC") |
| `balance`     | `Decimal` | Token balance in native units      |
| `balance_usd` | `Decimal` | Token balance in USD terms         |
| `address`     | `str`     | Token contract address             |

### PriceData

## almanak.framework.strategies.PriceData

```
PriceData(
    price: Decimal,
    price_24h_ago: Decimal = Decimal("0"),
    change_24h_pct: Decimal = Decimal("0"),
    high_24h: Decimal = Decimal("0"),
    low_24h: Decimal = Decimal("0"),
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)
```

Price data for a token.

Attributes:

| Name             | Type       | Description                     |
| ---------------- | ---------- | ------------------------------- |
| `price`          | `Decimal`  | Current price in USD            |
| `price_24h_ago`  | `Decimal`  | Price 24 hours ago in USD       |
| `change_24h_pct` | `Decimal`  | 24-hour price change percentage |
| `high_24h`       | `Decimal`  | 24-hour high                    |
| `low_24h`        | `Decimal`  | 24-hour low                     |
| `timestamp`      | `datetime` | When the price was fetched      |

### RSIData

## almanak.framework.strategies.RSIData

```
RSIData(
    value: Decimal,
    period: int = 14,
    overbought: Decimal = Decimal("70"),
    oversold: Decimal = Decimal("30"),
)
```

RSI (Relative Strength Index) data for a token.

Supports numeric operations so strategy authors can write

rsi = market.rsi("ETH") if rsi > 70: ... # comparison against int/float round(rsi, 2) # rounding f"{rsi:.2f}" # f-string formatting float(rsi) # explicit float conversion

Attributes:

| Name         | Type      | Description                              |
| ------------ | --------- | ---------------------------------------- |
| `value`      | `Decimal` | Current RSI value (0-100)                |
| `period`     | `int`     | RSI period used (e.g., 14)               |
| `overbought` | `Decimal` | Overbought threshold (default 70)        |
| `oversold`   | `Decimal` | Oversold threshold (default 30)          |
| `signal`     | `str`     | Signal based on RSI (BUY, SELL, or HOLD) |

### signal

```
signal: str
```

Get signal based on RSI value.

### is_oversold

```
is_oversold: bool
```

Check if RSI indicates oversold condition.

### is_overbought

```
is_overbought: bool
```

Check if RSI indicates overbought condition.

### MACDData

## almanak.framework.strategies.MACDData

```
MACDData(
    macd_line: Decimal,
    signal_line: Decimal,
    histogram: Decimal,
    fast_period: int = 12,
    slow_period: int = 26,
    signal_period: int = 9,
)
```

MACD (Moving Average Convergence Divergence) data for a token.

Attributes:

| Name            | Type      | Description                              |
| --------------- | --------- | ---------------------------------------- |
| `macd_line`     | `Decimal` | MACD line value (fast EMA - slow EMA)    |
| `signal_line`   | `Decimal` | Signal line value (EMA of MACD line)     |
| `histogram`     | `Decimal` | MACD histogram (MACD line - signal line) |
| `fast_period`   | `int`     | Fast EMA period (default 12)             |
| `slow_period`   | `int`     | Slow EMA period (default 26)             |
| `signal_period` | `int`     | Signal EMA period (default 9)            |

### is_bullish_crossover

```
is_bullish_crossover: bool
```

Check if MACD line is above signal line (bullish).

### is_bearish_crossover

```
is_bearish_crossover: bool
```

Check if MACD line is below signal line (bearish).

### signal

```
signal: str
```

Get signal based on MACD histogram.

### BollingerBandsData

## almanak.framework.strategies.BollingerBandsData

```
BollingerBandsData(
    upper_band: Decimal,
    middle_band: Decimal,
    lower_band: Decimal,
    bandwidth: Decimal = Decimal("0"),
    percent_b: Decimal = Decimal("0.5"),
    period: int = 20,
    std_dev: float = 2.0,
)
```

Bollinger Bands data for a token.

Numeric operations use percent_b (price position within bands, 0-1): bb = market.bollinger_bands("ETH") f"{bb:.2f}" # formats percent_b float(bb) # returns percent_b as float

Attributes:

| Name          | Type      | Description                                             |
| ------------- | --------- | ------------------------------------------------------- |
| `upper_band`  | `Decimal` | Upper band value (middle + std_dev * multiplier)        |
| `middle_band` | `Decimal` | Middle band value (SMA)                                 |
| `lower_band`  | `Decimal` | Lower band value (middle - std_dev * multiplier)        |
| `bandwidth`   | `Decimal` | Band width as percentage ((upper - lower) / middle)     |
| `percent_b`   | `Decimal` | Price position relative to bands (0 = lower, 1 = upper) |
| `period`      | `int`     | SMA period (default 20)                                 |
| `std_dev`     | `float`   | Standard deviation multiplier (default 2.0)             |

### is_oversold

```
is_oversold: bool
```

Check if price is below lower band (oversold).

### is_overbought

```
is_overbought: bool
```

Check if price is above upper band (overbought).

### is_squeeze

```
is_squeeze: bool
```

Check if bands are tight (low volatility squeeze).

### signal

```
signal: str
```

Get signal based on Bollinger Bands position.

### StochasticData

## almanak.framework.strategies.StochasticData

```
StochasticData(
    k_value: Decimal,
    d_value: Decimal,
    k_period: int = 14,
    d_period: int = 3,
    overbought: Decimal = Decimal("80"),
    oversold: Decimal = Decimal("20"),
)
```

Stochastic Oscillator data for a token.

Attributes:

| Name         | Type      | Description                                  |
| ------------ | --------- | -------------------------------------------- |
| `k_value`    | `Decimal` | %K value (fast stochastic, 0-100)            |
| `d_value`    | `Decimal` | %D value (slow stochastic, SMA of %K, 0-100) |
| `k_period`   | `int`     | %K period (default 14)                       |
| `d_period`   | `int`     | %D period (default 3)                        |
| `overbought` | `Decimal` | Overbought threshold (default 80)            |
| `oversold`   | `Decimal` | Oversold threshold (default 20)              |

### is_oversold

```
is_oversold: bool
```

Check if stochastic indicates oversold condition.

### is_overbought

```
is_overbought: bool
```

Check if stochastic indicates overbought condition.

### is_bullish_crossover

```
is_bullish_crossover: bool
```

Check if %K crossed above %D (bullish signal).

### is_bearish_crossover

```
is_bearish_crossover: bool
```

Check if %K crossed below %D (bearish signal).

### signal

```
signal: str
```

Get signal based on Stochastic values.

### ATRData

## almanak.framework.strategies.ATRData

```
ATRData(
    value: Decimal,
    value_percent: Decimal = Decimal("0"),
    period: int = 14,
    volatility_threshold: Decimal = Decimal("5.0"),
)
```

ATR (Average True Range) data for a token.

Numeric operations use value (ATR in price units): atr = market.atr("ETH") f"{atr:.2f}" # formats ATR value float(atr) # returns ATR value as float

Attributes:

| Name                   | Type      | Description                                                                    |
| ---------------------- | --------- | ------------------------------------------------------------------------------ |
| `value`                | `Decimal` | ATR value in price units                                                       |
| `value_percent`        | `Decimal` | ATR as percentage points of current price (e.g., 2.62 means 2.62%, not 0.0262) |
| `period`               | `int`     | ATR period (default 14)                                                        |
| `volatility_threshold` | `Decimal` | Max volatility threshold in percentage points (e.g., 5.0 means 5.0%)           |

### is_high_volatility

```
is_high_volatility: bool
```

Check if volatility is above threshold (risky to trade).

### is_low_volatility

```
is_low_volatility: bool
```

Check if volatility is below threshold (safe to trade).

### signal

```
signal: str
```

Get signal based on ATR volatility gate.

### MAData

## almanak.framework.strategies.MAData

```
MAData(
    value: Decimal,
    ma_type: str = "SMA",
    period: int = 20,
    current_price: Decimal = Decimal("0"),
)
```

Moving Average data for a token.

Attributes:

| Name            | Type      | Description                                  |
| --------------- | --------- | -------------------------------------------- |
| `value`         | `Decimal` | Moving average value                         |
| `ma_type`       | `str`     | Type of moving average ("SMA", "EMA", "WMA") |
| `period`        | `int`     | MA period                                    |
| `current_price` | `Decimal` | Current price for comparison                 |

### is_price_above

```
is_price_above: bool
```

Check if current price is above the MA.

### is_price_below

```
is_price_below: bool
```

Check if current price is below the MA.

### signal

```
signal: str
```

Get signal based on price vs MA.

### ADXData

## almanak.framework.strategies.ADXData

```
ADXData(
    adx: Decimal,
    plus_di: Decimal,
    minus_di: Decimal,
    period: int = 14,
    trend_threshold: Decimal = Decimal("25"),
)
```

ADX (Average Directional Index) data for a token.

Attributes:

| Name              | Type      | Description                                |
| ----------------- | --------- | ------------------------------------------ |
| `adx`             | `Decimal` | ADX value (0-100, measures trend strength) |
| `plus_di`         | `Decimal` | +DI value (positive directional indicator) |
| `minus_di`        | `Decimal` | -DI value (negative directional indicator) |
| `period`          | `int`     | ADX period (default 14)                    |
| `trend_threshold` | `Decimal` | Threshold for strong trend (default 25)    |

### is_strong_trend

```
is_strong_trend: bool
```

Check if ADX indicates a strong trend.

### is_uptrend

```
is_uptrend: bool
```

Check if in uptrend (+DI > -DI with strong trend).

### is_downtrend

```
is_downtrend: bool
```

Check if in downtrend (-DI > +DI with strong trend).

### signal

```
signal: str
```

Get signal based on ADX trend analysis.

### OBVData

## almanak.framework.strategies.OBVData

```
OBVData(
    obv: Decimal,
    signal_line: Decimal,
    signal_period: int = 21,
)
```

OBV (On-Balance Volume) data for a token.

Attributes:

| Name            | Type      | Description                |
| --------------- | --------- | -------------------------- |
| `obv`           | `Decimal` | Current OBV value          |
| `signal_line`   | `Decimal` | Signal line (SMA of OBV)   |
| `signal_period` | `int`     | Signal period (default 21) |

### is_bullish

```
is_bullish: bool
```

Check if OBV is above signal (buying pressure).

### is_bearish

```
is_bearish: bool
```

Check if OBV is below signal (selling pressure).

### signal

```
signal: str
```

Get signal based on OBV analysis.

### CCIData

## almanak.framework.strategies.CCIData

```
CCIData(
    value: Decimal,
    period: int = 20,
    upper_level: Decimal = Decimal("100"),
    lower_level: Decimal = Decimal("-100"),
)
```

CCI (Commodity Channel Index) data for a token.

Attributes:

| Name          | Type      | Description                    |
| ------------- | --------- | ------------------------------ |
| `value`       | `Decimal` | CCI value                      |
| `period`      | `int`     | CCI period (default 20)        |
| `upper_level` | `Decimal` | Overbought level (default 100) |
| `lower_level` | `Decimal` | Oversold level (default -100)  |

### is_oversold

```
is_oversold: bool
```

Check if CCI indicates oversold condition.

### is_overbought

```
is_overbought: bool
```

Check if CCI indicates overbought condition.

### signal

```
signal: str
```

Get signal based on CCI.

### IchimokuData

## almanak.framework.strategies.IchimokuData

```
IchimokuData(
    tenkan_sen: Decimal,
    kijun_sen: Decimal,
    senkou_span_a: Decimal,
    senkou_span_b: Decimal,
    current_price: Decimal = Decimal("0"),
    tenkan_period: int = 9,
    kijun_period: int = 26,
    senkou_b_period: int = 52,
)
```

Ichimoku Cloud data for a token.

Attributes:

| Name              | Type      | Description                            |
| ----------------- | --------- | -------------------------------------- |
| `tenkan_sen`      | `Decimal` | Conversion line (9-period midpoint)    |
| `kijun_sen`       | `Decimal` | Base line (26-period midpoint)         |
| `senkou_span_a`   | `Decimal` | Leading span A                         |
| `senkou_span_b`   | `Decimal` | Leading span B                         |
| `current_price`   | `Decimal` | Current price for cloud position check |
| `tenkan_period`   | `int`     | Tenkan-sen period (default 9)          |
| `kijun_period`    | `int`     | Kijun-sen period (default 26)          |
| `senkou_b_period` | `int`     | Senkou Span B period (default 52)      |

### cloud_top

```
cloud_top: Decimal
```

Get the top of the cloud.

### cloud_bottom

```
cloud_bottom: Decimal
```

Get the bottom of the cloud.

### is_bullish_crossover

```
is_bullish_crossover: bool
```

Check if Tenkan crossed above Kijun (bullish).

### is_bearish_crossover

```
is_bearish_crossover: bool
```

Check if Tenkan crossed below Kijun (bearish).

### is_above_cloud

```
is_above_cloud: bool
```

Check if price is above the cloud.

### is_below_cloud

```
is_below_cloud: bool
```

Check if price is below the cloud.

### signal

```
signal: str
```

Get signal based on Ichimoku analysis.

### ChainHealthStatus

## almanak.framework.strategies.ChainHealthStatus

Bases: `Enum`

Status of a chain's data health.

Attributes:

| Name          | Type | Description                                                               |
| ------------- | ---- | ------------------------------------------------------------------------- |
| `HEALTHY`     |      | Chain data is fresh and available                                         |
| `DEGRADED`    |      | Chain data is stale but still usable (between threshold and 2x threshold) |
| `UNAVAILABLE` |      | Chain data could not be fetched                                           |
| `STALE`       |      | Chain data is too old to be trusted (beyond staleness threshold)          |

### ChainHealth

## almanak.framework.strategies.ChainHealth

```
ChainHealth(
    chain: str,
    status: ChainHealthStatus,
    last_updated: datetime | None = None,
    staleness_seconds: float | None = None,
    stale_threshold_seconds: float = 30.0,
    error: str | None = None,
)
```

Health status and staleness information for a single chain.

This dataclass provides detailed information about the health of market data for a specific chain, including when data was last fetched, staleness metrics, and any error information.

Attributes:

| Name                      | Type                | Description                               |
| ------------------------- | ------------------- | ----------------------------------------- |
| `chain`                   | `str`               | Chain name (e.g., "arbitrum", "optimism") |
| `status`                  | `ChainHealthStatus` | Current health status of the chain        |
| `last_updated`            | \`datetime          | None\`                                    |
| `staleness_seconds`       | \`float             | None\`                                    |
| `stale_threshold_seconds` | `float`             | The threshold used to determine staleness |
| `error`                   | \`str               | None\`                                    |
| `is_stale`                | `bool`              | Whether the data is considered stale      |
| `is_available`            | `bool`              | Whether the data is available for use     |

Example

health = ChainHealth( chain="arbitrum", status=ChainHealthStatus.HEALTHY, last_updated=datetime.now(timezone.utc), staleness_seconds=5.2, stale_threshold_seconds=30.0, )

if health.is_stale: logger.warning(f"Chain {health.chain} data is stale")

### is_stale

```
is_stale: bool
```

Check if the chain data is stale.

Returns:

| Type   | Description                                                |
| ------ | ---------------------------------------------------------- |
| `bool` | True if staleness exceeds threshold or data is unavailable |

### is_available

```
is_available: bool
```

Check if the chain data is available for use.

Returns:

| Type   | Description                                            |
| ------ | ------------------------------------------------------ |
| `bool` | True if data is healthy or degraded (but still usable) |

### is_healthy

```
is_healthy: bool
```

Check if the chain data is fully healthy.

Returns:

| Type   | Description               |
| ------ | ------------------------- |
| `bool` | True if status is HEALTHY |

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary representation.

Returns:

| Type             | Description                        |
| ---------------- | ---------------------------------- |
| `dict[str, Any]` | Dictionary with health information |

### from_dict

```
from_dict(data: dict[str, Any]) -> ChainHealth
```

Create ChainHealth from dictionary.

Parameters:

| Name   | Type             | Description                        | Default    |
| ------ | ---------------- | ---------------------------------- | ---------- |
| `data` | `dict[str, Any]` | Dictionary with health information | *required* |

Returns:

| Type          | Description          |
| ------------- | -------------------- |
| `ChainHealth` | ChainHealth instance |

# Token Resolution

Unified token resolution for addresses, decimals, and symbol lookups across all chains.

## Usage

```
from almanak.framework.data.tokens import get_token_resolver

resolver = get_token_resolver()

# Resolve by symbol
token = resolver.resolve("USDC", "arbitrum")
print(token.address, token.decimals)  # 0xaf88... 6

# Resolve by address
token = resolver.resolve("0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "arbitrum")

# Convenience methods
decimals = resolver.get_decimals("arbitrum", "USDC")
address = resolver.get_address("arbitrum", "USDC")

# For DEX swaps (auto-wraps native tokens: ETH->WETH, etc.)
token = resolver.resolve_for_swap("ETH", "arbitrum")
```

## get_token_resolver

## almanak.framework.data.tokens.get_token_resolver

```
get_token_resolver(
    gateway_client: Any | None = None,
    cache_file: str | None = None,
    gateway_channel: Channel | None = None,
) -> TokenResolver
```

Get the singleton TokenResolver instance.

This is the recommended entry point for token resolution.

Parameters:

| Name              | Type      | Description | Default                                                                                                                                                                                                |
| ----------------- | --------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `gateway_client`  | \`Any     | None\`      | DEPRECATED - Use gateway_channel instead.                                                                                                                                                              |
| `cache_file`      | \`str     | None\`      | Optional path to cache file. Only used on first call.                                                                                                                                                  |
| `gateway_channel` | \`Channel | None\`      | Optional gRPC channel to gateway for on-chain lookups. If None, only static resolution is available. On-chain discovery gracefully falls back to static resolution if the gateway becomes unavailable. |

Returns:

| Type            | Description                          |
| --------------- | ------------------------------------ |
| `TokenResolver` | The singleton TokenResolver instance |

Example

from almanak.framework.data.tokens import get_token_resolver

### Static resolution only

resolver = get_token_resolver() usdc = resolver.resolve("USDC", "arbitrum")

### With gateway for on-chain discovery

import grpc channel = grpc.insecure_channel("localhost:50051") resolver = get_token_resolver(gateway_channel=channel)

## TokenResolver

## almanak.framework.data.tokens.TokenResolver

```
TokenResolver(
    gateway_client: Any | None = None,
    cache_file: str | None = None,
    gateway_channel: Channel | None = None,
)
```

Unified token resolver with multi-layer caching.

This class provides the main API for token resolution in the Almanak framework. It implements a singleton pattern for thread-safe global access.

Resolution Order

1. Memory cache - fastest, O(1)
1. Disk cache - loads from JSON, promotes to memory
1. Static registry - DEFAULT_TOKENS from defaults.py
1. Gateway on-chain lookup - queries ERC20 contracts (if gateway_client provided)

Thread Safety

Uses threading.RLock for all operations. Safe for concurrent access.

Attributes:

| Name             | Type | Description                                  |
| ---------------- | ---- | -------------------------------------------- |
| `gateway_client` |      | Optional gateway client for on-chain lookups |

Example

resolver = TokenResolver.get_instance()

### Resolve by symbol

token = resolver.resolve("USDC", "arbitrum")

### Resolve by address

token = resolver.resolve("0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "arbitrum")

### Register a custom token

resolver.register(my_custom_token)

Initialize the TokenResolver.

NOTE: Prefer using get_instance() for singleton access.

Parameters:

| Name              | Type      | Description | Default                                                                                                                                                                                                    |
| ----------------- | --------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gateway_client`  | \`Any     | None\`      | DEPRECATED - Use gateway_channel instead. Kept for backward compatibility.                                                                                                                                 |
| `cache_file`      | \`str     | None\`      | Optional path to cache file. Defaults to ~/.almanak/token_cache.json                                                                                                                                       |
| `gateway_channel` | \`Channel | None\`      | Optional gRPC channel to gateway for on-chain lookups. If None, only static resolution is available. On-chain discovery will gracefully fall back to static resolution if the gateway becomes unavailable. |

### get_instance

```
get_instance(
    gateway_client: Any | None = None,
    cache_file: str | None = None,
    gateway_channel: Channel | None = None,
) -> TokenResolver
```

Get the singleton TokenResolver instance.

This is the recommended way to get a TokenResolver. The first call creates the instance, subsequent calls return the same instance.

Parameters:

| Name              | Type      | Description | Default                                                                                                                                                     |
| ----------------- | --------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `gateway_client`  | \`Any     | None\`      | DEPRECATED - Use gateway_channel instead.                                                                                                                   |
| `cache_file`      | \`str     | None\`      | Optional path to cache file. Only used on first call.                                                                                                       |
| `gateway_channel` | \`Channel | None\`      | Optional gRPC channel to gateway for on-chain lookups. Only used on first call when creating instance. Pass a grpc.Channel connected to the gateway server. |

Returns:

| Type            | Description                          |
| --------------- | ------------------------------------ |
| `TokenResolver` | The singleton TokenResolver instance |

Example

#### Without gateway (static resolution only)

resolver = TokenResolver.get_instance() token = resolver.resolve("USDC", "arbitrum")

#### With gateway (enables on-chain discovery)

import grpc channel = grpc.insecure_channel("localhost:50051") resolver = TokenResolver.get_instance(gateway_channel=channel)

### reset_instance

```
reset_instance() -> None
```

Reset the singleton instance. Primarily for testing.

### resolve

```
resolve(
    token: str,
    chain: str | Chain,
    *,
    log_errors: bool = True,
    skip_gateway: bool = False,
) -> ResolvedToken
```

Resolve a token by symbol or address on a specific chain.

This is the main resolution method. It checks:

1. Memory cache
1. Disk cache
1. Static registry
1. Gateway on-chain lookup (if token is an address and gateway available)

Parameters:

| Name           | Type   | Description                                                                                                                                                                   | Default                  |
| -------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| `token`        | `str`  | Token symbol (e.g., "USDC") or address (e.g., "0x...")                                                                                                                        | *required*               |
| `chain`        | \`str  | Chain\`                                                                                                                                                                       | Chain name or Chain enum |
| `log_errors`   | `bool` | If False, suppress warning logs on resolution failure (default True). Use False for best-effort lookups where failures are expected and handled.                              | `True`                   |
| `skip_gateway` | `bool` | If True, skip the slow gateway on-chain lookup and fail fast after cache + static registry. Use for cosmetic/best-effort lookups where a 30s gateway timeout is unacceptable. | `False`                  |

Returns:

| Type            | Description                      |
| --------------- | -------------------------------- |
| `ResolvedToken` | ResolvedToken with full metadata |

Raises:

| Type                       | Description                  |
| -------------------------- | ---------------------------- |
| `TokenNotFoundError`       | If token cannot be resolved  |
| `InvalidTokenAddressError` | If address format is invalid |
| `TokenResolutionError`     | For other resolution errors  |

Example

#### By symbol

usdc = resolver.resolve("USDC", "arbitrum")

#### By address

token = resolver.resolve("0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "arbitrum")

### is_gateway_connected

```
is_gateway_connected() -> bool
```

Check if gateway is connected and available for on-chain lookups.

This method checks if a gateway channel is configured and appears to be connected. Note that the actual availability is verified lazily - the gateway might become unavailable between this check and actual use.

Returns:

| Type   | Description                                                  |
| ------ | ------------------------------------------------------------ |
| `bool` | True if gateway channel is configured and appears available, |
| `bool` | False otherwise.                                             |

Example

resolver = get_token_resolver(gateway_channel=channel) if resolver.is_gateway_connected(): print("Gateway available for on-chain token discovery") else: print("Static resolution only")

### set_gateway_channel

```
set_gateway_channel(channel: Channel | None) -> None
```

Set or update the gateway channel.

This allows changing the gateway connection after initialization. Useful for reconnection scenarios or testing.

Parameters:

| Name      | Type      | Description | Default                                             |
| --------- | --------- | ----------- | --------------------------------------------------- |
| `channel` | \`Channel | None\`      | gRPC channel to gateway, or None to disable gateway |

Example

import grpc resolver = get_token_resolver()

#### Connect to gateway later

channel = grpc.insecure_channel("localhost:50051") resolver.set_gateway_channel(channel)

#### Disconnect from gateway

resolver.set_gateway_channel(None)

### resolve_pair

```
resolve_pair(
    token_in: str, token_out: str, chain: str | Chain
) -> tuple[ResolvedToken, ResolvedToken]
```

Resolve a pair of tokens for a swap operation.

Convenience method for resolving both tokens in a trading pair.

Parameters:

| Name        | Type  | Description                    | Default                  |
| ----------- | ----- | ------------------------------ | ------------------------ |
| `token_in`  | `str` | Input token symbol or address  | *required*               |
| `token_out` | `str` | Output token symbol or address | *required*               |
| `chain`     | \`str | Chain\`                        | Chain name or Chain enum |

Returns:

| Type                                  | Description                                      |
| ------------------------------------- | ------------------------------------------------ |
| `tuple[ResolvedToken, ResolvedToken]` | Tuple of (resolved_token_in, resolved_token_out) |

Raises:

| Type                   | Description                        |
| ---------------------- | ---------------------------------- |
| `TokenNotFoundError`   | If either token cannot be resolved |
| `TokenResolutionError` | For other resolution errors        |

Example

usdc, weth = resolver.resolve_pair("USDC", "WETH", "arbitrum")

### get_decimals

```
get_decimals(chain: str | Chain, token: str) -> int
```

Get the decimals for a token on a specific chain.

Convenience method that extracts just the decimals from resolution. NEVER defaults to 18 - always raises TokenNotFoundError if unknown.

Parameters:

| Name    | Type  | Description             | Default                  |
| ------- | ----- | ----------------------- | ------------------------ |
| `chain` | \`str | Chain\`                 | Chain name or Chain enum |
| `token` | `str` | Token symbol or address | *required*               |

Returns:

| Type  | Description              |
| ----- | ------------------------ |
| `int` | Number of decimal places |

Raises:

| Type                 | Description                 |
| -------------------- | --------------------------- |
| `TokenNotFoundError` | If token cannot be resolved |

Example

decimals = resolver.get_decimals("arbitrum", "USDC")

#### Returns 6

### get_address

```
get_address(chain: str | Chain, symbol: str) -> str
```

Get the address for a token symbol on a specific chain.

Convenience method that extracts just the address from resolution.

Parameters:

| Name     | Type  | Description                 | Default                  |
| -------- | ----- | --------------------------- | ------------------------ |
| `chain`  | \`str | Chain\`                     | Chain name or Chain enum |
| `symbol` | `str` | Token symbol (e.g., "USDC") | *required*               |

Returns:

| Type  | Description      |
| ----- | ---------------- |
| `str` | Contract address |

Raises:

| Type                 | Description                 |
| -------------------- | --------------------------- |
| `TokenNotFoundError` | If token cannot be resolved |

Example

address = resolver.get_address("arbitrum", "USDC")

#### Returns "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"

### resolve_for_swap

```
resolve_for_swap(
    token: str, chain: str | Chain
) -> ResolvedToken
```

Resolve a token for swap operations, auto-wrapping native tokens.

This method resolves a token and if it's a native token (ETH, MATIC, AVAX, BNB), automatically returns the wrapped version instead (WETH, WMATIC, WAVAX, WBNB). This is because most DEX protocols cannot swap native tokens directly.

For non-native tokens, this behaves identically to resolve().

Parameters:

| Name    | Type  | Description                                   | Default                  |
| ------- | ----- | --------------------------------------------- | ------------------------ |
| `token` | `str` | Token symbol (e.g., "ETH", "USDC") or address | *required*               |
| `chain` | \`str | Chain\`                                       | Chain name or Chain enum |

Returns:

| Type            | Description                                                   |
| --------------- | ------------------------------------------------------------- |
| `ResolvedToken` | ResolvedToken - wrapped version if native, original otherwise |

Raises:

| Type                       | Description                                    |
| -------------------------- | ---------------------------------------------- |
| `TokenNotFoundError`       | If token or wrapped version cannot be resolved |
| `InvalidTokenAddressError` | If address format is invalid                   |
| `TokenResolutionError`     | For other resolution errors                    |

Example

#### ETH on Arbitrum returns WETH

token = resolver.resolve_for_swap("ETH", "arbitrum") assert token.symbol == "WETH"

#### USDC returns USDC (not native)

token = resolver.resolve_for_swap("USDC", "arbitrum") assert token.symbol == "USDC"

### resolve_for_protocol

```
resolve_for_protocol(
    token: str, chain: str | Chain, protocol: str
) -> ResolvedToken
```

Resolve a token with protocol-specific handling.

This method provides a hook for future protocol-specific token resolution. Currently, it simply delegates to resolve_for_swap() for DEX protocols and to resolve() for other protocols.

This allows for future expansion where specific protocols might have unique token requirements (e.g., protocol-specific wrapped tokens, canonical bridge tokens, etc.).

Parameters:

| Name       | Type  | Description                                         | Default                  |
| ---------- | ----- | --------------------------------------------------- | ------------------------ |
| `token`    | `str` | Token symbol or address                             | *required*               |
| `chain`    | \`str | Chain\`                                             | Chain name or Chain enum |
| `protocol` | `str` | Protocol identifier (e.g., "uniswap_v3", "aave_v3") | *required*               |

Returns:

| Type            | Description                                      |
| --------------- | ------------------------------------------------ |
| `ResolvedToken` | ResolvedToken with appropriate protocol handling |

Raises:

| Type                   | Description                 |
| ---------------------- | --------------------------- |
| `TokenNotFoundError`   | If token cannot be resolved |
| `TokenResolutionError` | For other resolution errors |

Example

#### DEX protocols get auto-wrapped native tokens

token = resolver.resolve_for_protocol("ETH", "arbitrum", "uniswap_v3") assert token.symbol == "WETH"

#### Lending protocols get the original token

token = resolver.resolve_for_protocol("ETH", "ethereum", "aave_v3") assert token.symbol == "ETH"

### register

```
register(token: ResolvedToken) -> None
```

Register a token explicitly at runtime.

This allows adding custom tokens that aren't in the static registry. Registered tokens are stored in the cache.

Parameters:

| Name    | Type            | Description               | Default    |
| ------- | --------------- | ------------------------- | ---------- |
| `token` | `ResolvedToken` | ResolvedToken to register | *required* |

Example

custom_token = ResolvedToken( symbol="CUSTOM", address="0x...", decimals=18, chain=Chain.ARBITRUM, chain_id=42161, name="Custom Token", ) resolver.register(custom_token)

### register_token

```
register_token(
    symbol: str,
    chain: str | Chain,
    address: str,
    decimals: int,
    *,
    name: str | None = None,
    coingecko_id: str | None = None,
    is_stablecoin: bool = False,
) -> ResolvedToken
```

Register a custom token by its basic properties.

Convenience wrapper around register() for strategy authors who need to register protocol-specific tokens (e.g., Pendle PT/YT, LP tokens) that aren't in the static registry.

After registration, the token is resolvable via resolve(), get_address(), and get_decimals() within the same process.

Note: This registers tokens in the local resolver only. Gateway-backed lookups (e.g., MarketSnapshot.balance() by symbol) require the gateway to also know the token. For balance queries on custom tokens, use the token address directly: market.balance("0x...").

Parameters:

| Name            | Type   | Description                                  | Default                                  |
| --------------- | ------ | -------------------------------------------- | ---------------------------------------- |
| `symbol`        | `str`  | Token symbol (e.g., "PT-wstETH-25JUN2026")   | *required*                               |
| `chain`         | \`str  | Chain\`                                      | Chain name or Chain enum                 |
| `address`       | `str`  | Token contract address                       | *required*                               |
| `decimals`      | `int`  | Token decimal places                         | *required*                               |
| `name`          | \`str  | None\`                                       | Optional human-readable name             |
| `coingecko_id`  | \`str  | None\`                                       | Optional CoinGecko ID for price fetching |
| `is_stablecoin` | `bool` | Whether this is a stablecoin (default False) | `False`                                  |

Returns:

| Type            | Description                                            |
| --------------- | ------------------------------------------------------ |
| `ResolvedToken` | The registered ResolvedToken (can be used immediately) |

Raises:

| Type                       | Description                  |
| -------------------------- | ---------------------------- |
| `InvalidTokenAddressError` | If address format is invalid |
| `TokenResolutionError`     | If chain is not recognized   |

Example

resolver = get_token_resolver() resolver.register_token( symbol="PT-wstETH-25JUN2026", chain="arbitrum", address="0x71FBF40651E9d4bC027876E5aA4a3806d8E0B243", decimals=18, )

#### Now works:

token = resolver.resolve("PT-wstETH-25JUN2026", "arbitrum")

### stats

```
stats() -> dict[str, int]
```

Get resolver performance statistics.

Returns:

| Type             | Description                                                |
| ---------------- | ---------------------------------------------------------- |
| `dict[str, int]` | Dict with cache_hits, static_hits, gateway_lookups, errors |

### cache_stats

```
cache_stats() -> dict[str, int]
```

Get cache performance statistics.

Returns:

| Type             | Description                                         |
| ---------------- | --------------------------------------------------- |
| `dict[str, int]` | Dict with memory_hits, disk_hits, misses, evictions |

## ResolvedToken

## almanak.framework.data.tokens.ResolvedToken

```
ResolvedToken(
    symbol: str,
    address: str,
    decimals: int,
    chain: Chain,
    chain_id: int,
    name: str | None = None,
    coingecko_id: str | None = None,
    is_stablecoin: bool = False,
    is_native: bool = False,
    is_wrapped_native: bool = False,
    canonical_symbol: str | None = None,
    bridge_type: BridgeType = BridgeType.NATIVE,
    source: str = "static",
    is_verified: bool = True,
    resolved_at: datetime | None = None,
)
```

Fully resolved token with all metadata for a specific chain.

This is a frozen (immutable) dataclass representing a token that has been fully resolved with all its metadata. It's designed for caching and thread-safe access.

Attributes:

| Name                | Type         | Description                                                        |
| ------------------- | ------------ | ------------------------------------------------------------------ |
| `symbol`            | `str`        | Token symbol (e.g., "ETH", "USDC", "WBTC")                         |
| `address`           | `str`        | Contract address on the resolved chain                             |
| `decimals`          | `int`        | Token decimal places                                               |
| `chain`             | `Chain`      | Chain enum value where this token is resolved                      |
| `chain_id`          | `int`        | Numeric chain ID for the resolved chain                            |
| `name`              | \`str        | None\`                                                             |
| `coingecko_id`      | \`str        | None\`                                                             |
| `is_stablecoin`     | `bool`       | Whether this token is a stablecoin                                 |
| `is_native`         | `bool`       | Whether this is the native gas token (ETH, MATIC, AVAX, etc.)      |
| `is_wrapped_native` | `bool`       | Whether this is wrapped native (WETH, WMATIC, WAVAX, etc.)         |
| `canonical_symbol`  | \`str        | None\`                                                             |
| `bridge_type`       | `BridgeType` | Bridge status of the token                                         |
| `source`            | `str`        | Where the token metadata came from ("static", "on_chain", "cache") |
| `is_verified`       | `bool`       | Whether the token metadata has been verified                       |
| `resolved_at`       | \`datetime   | None\`                                                             |

Example

resolved_usdc = ResolvedToken( symbol="USDC", address="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", decimals=6, chain=Chain.ARBITRUM, chain_id=42161, name="USD Coin", coingecko_id="usd-coin", is_stablecoin=True, is_native=False, is_wrapped_native=False, canonical_symbol="USDC", bridge_type=BridgeType.NATIVE, source="static", is_verified=True, resolved_at=datetime.now(), )

### __post_init__

```
__post_init__() -> None
```

Validate resolved token data.

### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary for serialization.

### from_dict

```
from_dict(data: dict[str, Any]) -> ResolvedToken
```

Create ResolvedToken from dictionary.

## BridgeType

## almanak.framework.data.tokens.BridgeType

Bases: `Enum`

Token bridge status indicating origin of the token on a chain.

Attributes:

| Name        | Type | Description                                                                     |
| ----------- | ---- | ------------------------------------------------------------------------------- |
| `NATIVE`    |      | Token is native to this chain (e.g., ETH on Ethereum, USDC native on Arbitrum)  |
| `BRIDGED`   |      | Token was bridged from another chain (e.g., USDC.e on Arbitrum)                 |
| `CANONICAL` |      | Token is the canonical/official bridge representation for cross-chain transfers |

Example

### Native USDC on Arbitrum (issued by Circle directly)

native_usdc = ResolvedToken(..., bridge_type=BridgeType.NATIVE)

### Bridged USDC.e on Arbitrum (bridged from Ethereum)

bridged_usdc = ResolvedToken(..., bridge_type=BridgeType.BRIDGED)

## Exceptions

## almanak.framework.data.tokens.TokenResolutionError

```
TokenResolutionError(
    token: str,
    chain: str,
    reason: str,
    suggestions: list[str] | None = None,
)
```

Bases: `Exception`

Base exception for token resolution errors.

This is the base class for all token resolution-related exceptions. It provides structured error information including the token identifier, chain, reason for failure, and actionable suggestions.

Attributes:

| Name          | Type | Description                                                     |
| ------------- | ---- | --------------------------------------------------------------- |
| `token`       |      | The token identifier that failed to resolve (symbol or address) |
| `chain`       |      | The chain where resolution was attempted                        |
| `reason`      |      | Explanation of why resolution failed                            |
| `suggestions` |      | List of actionable suggestions to fix the issue                 |

Example

raise TokenResolutionError( token="USDC", chain="unknown_chain", reason="Chain 'unknown_chain' is not supported", suggestions=["Use a supported chain: ethereum, arbitrum, base, optimism"], )

Initialize the exception.

Parameters:

| Name          | Type        | Description                                 | Default                                         |
| ------------- | ----------- | ------------------------------------------- | ----------------------------------------------- |
| `token`       | `str`       | The token identifier that failed to resolve | *required*                                      |
| `chain`       | `str`       | The chain where resolution was attempted    | *required*                                      |
| `reason`      | `str`       | Explanation of why resolution failed        | *required*                                      |
| `suggestions` | \`list[str] | None\`                                      | List of actionable suggestions to fix the issue |

### __repr__

```
__repr__() -> str
```

Return a detailed representation of the exception.

## almanak.framework.data.tokens.TokenNotFoundError

```
TokenNotFoundError(
    token: str,
    chain: str,
    reason: str = "Token not found in any registry",
    suggestions: list[str] | None = None,
)
```

Bases: `TokenResolutionError`

Raised when a token is not found in any registry.

This exception is raised when:

- Token symbol is not in the static registry
- Token symbol is not in the cache
- Token address (if provided) doesn't match any known token
- Gateway on-chain lookup (if enabled) also fails

Example

raise TokenNotFoundError( token="UNKNOWNTOKEN", chain="arbitrum", reason="Token not in static registry or cache", suggestions=[ "Check spelling - did you mean 'UNI' or 'LINK'?", "If using an address, ensure it's a valid ERC20 contract", "Use register() to add custom tokens to the resolver", ], )

Initialize the exception.

Parameters:

| Name          | Type        | Description                               | Default                             |
| ------------- | ----------- | ----------------------------------------- | ----------------------------------- |
| `token`       | `str`       | The token identifier that was not found   | *required*                          |
| `chain`       | `str`       | The chain where the token was searched    | *required*                          |
| `reason`      | `str`       | Explanation of why the token wasn't found | `'Token not found in any registry'` |
| `suggestions` | \`list[str] | None\`                                    | List of actionable suggestions      |

## almanak.framework.data.tokens.AmbiguousTokenError

```
AmbiguousTokenError(
    token: str,
    chain: str,
    reason: str = "Multiple tokens match the identifier",
    matching_addresses: list[str] | None = None,
    suggestions: list[str] | None = None,
)
```

Bases: `TokenResolutionError`

Raised when multiple tokens match the given identifier.

This exception is raised when:

- A symbol matches multiple tokens on the same chain (e.g., multiple USDC variants)
- Bridged tokens create ambiguity (USDC vs USDC.e)
- Multiple protocols have deployed tokens with the same symbol

Attributes:

| Name                 | Type | Description                                       |
| -------------------- | ---- | ------------------------------------------------- |
| `matching_addresses` |      | List of addresses that match the token identifier |

Example

raise AmbiguousTokenError( token="USDC", chain="arbitrum", reason="Multiple USDC variants found on Arbitrum", matching_addresses=[ "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # Native USDC "0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", # USDC.e (bridged) ], suggestions=[ "Use 'USDC' for native USDC: 0xaf88d065e77c8cC2239327C5EDb3A432268e5831", "Use 'USDC.e' for bridged USDC: 0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8", "Or specify the full contract address", ], )

Initialize the exception.

Parameters:

| Name                 | Type        | Description                        | Default                                  |
| -------------------- | ----------- | ---------------------------------- | ---------------------------------------- |
| `token`              | `str`       | The ambiguous token identifier     | *required*                               |
| `chain`              | `str`       | The chain where ambiguity occurred | *required*                               |
| `reason`             | `str`       | Explanation of the ambiguity       | `'Multiple tokens match the identifier'` |
| `matching_addresses` | \`list[str] | None\`                             | List of addresses that match             |
| `suggestions`        | \`list[str] | None\`                             | List of actionable suggestions           |

# Connectors

Protocol connectors provide adapters for interacting with DeFi protocols. Each connector includes an SDK (low-level interactions), an adapter (standard interface), and a receipt parser.

## Supported Protocols

| Protocol                                                                         | Type           | Chains                                        | Module           |
| -------------------------------------------------------------------------------- | -------------- | --------------------------------------------- | ---------------- |
| [Uniswap V3](https://sdk.docs.almanak.co/api/connectors/uniswap_v3.html)         | DEX            | Ethereum, Arbitrum, Optimism, Base, Polygon   | `uniswap_v3`     |
| [Uniswap V4](https://sdk.docs.almanak.co/api/connectors/uniswap_v4.html)         | DEX            | Ethereum, Arbitrum, Base                      | `uniswap_v4`     |
| [Aave V3](https://sdk.docs.almanak.co/api/connectors/aave_v3.html)               | Lending        | Ethereum, Arbitrum, Optimism, Base, Avalanche | `aave_v3`        |
| [Morpho Blue](https://sdk.docs.almanak.co/api/connectors/morpho_blue.html)       | Lending        | Ethereum, Base                                | `morpho_blue`    |
| [GMX V2](https://sdk.docs.almanak.co/api/connectors/gmx_v2.html)                 | Perpetuals     | Arbitrum, Avalanche                           | `gmx_v2`         |
| [Aerodrome](https://sdk.docs.almanak.co/api/connectors/aerodrome.html)           | DEX            | Base                                          | `aerodrome`      |
| [TraderJoe V2](https://sdk.docs.almanak.co/api/connectors/traderjoe_v2.html)     | DEX            | Avalanche, Arbitrum                           | `traderjoe_v2`   |
| [PancakeSwap V3](https://sdk.docs.almanak.co/api/connectors/pancakeswap_v3.html) | DEX            | BSC, Ethereum, Arbitrum                       | `pancakeswap_v3` |
| [SushiSwap V3](https://sdk.docs.almanak.co/api/connectors/sushiswap_v3.html)     | DEX            | Ethereum, Arbitrum                            | `sushiswap_v3`   |
| [Curve](https://sdk.docs.almanak.co/api/connectors/curve.html)                   | DEX            | Ethereum, Base, Optimism                      | `curve`          |
| [Balancer](https://sdk.docs.almanak.co/api/connectors/balancer.html)             | DEX            | Ethereum, Arbitrum                            | `balancer`       |
| [Compound V3](https://sdk.docs.almanak.co/api/connectors/compound_v3.html)       | Lending        | Ethereum, Arbitrum, Base                      | `compound_v3`    |
| [Enso](https://sdk.docs.almanak.co/api/connectors/enso.html)                     | Aggregator     | Multi-chain                                   | `enso`           |
| [Polymarket](https://sdk.docs.almanak.co/api/connectors/polymarket.html)         | Prediction     | Polygon                                       | `polymarket`     |
| [Hyperliquid](https://sdk.docs.almanak.co/api/connectors/hyperliquid.html)       | Perpetuals     | HyperEVM                                      | `hyperliquid`    |
| [Lido](https://sdk.docs.almanak.co/api/connectors/lido.html)                     | Liquid Staking | Ethereum                                      | `lido`           |
| [Ethena](https://sdk.docs.almanak.co/api/connectors/ethena.html)                 | Yield          | Ethereum                                      | `ethena`         |
| [Spark](https://sdk.docs.almanak.co/api/connectors/spark.html)                   | Lending        | Ethereum                                      | `spark`          |
| [Pendle](https://sdk.docs.almanak.co/api/connectors/pendle.html)                 | Yield          | Ethereum, Arbitrum                            | `pendle`         |
| [Kraken](https://sdk.docs.almanak.co/api/connectors/kraken.html)                 | CEX            | N/A                                           | `kraken`         |
| [Bridges](https://sdk.docs.almanak.co/api/connectors/bridges.html)               | Bridge         | Multi-chain                                   | `bridges`        |
| [Flash Loan](https://sdk.docs.almanak.co/api/connectors/flash_loan.html)         | Utility        | Multi-chain                                   | `flash_loan`     |
| Agni Finance                                                                     | DEX            | Mantle                                        | `agni_finance`   |
| BenQi                                                                            | Lending        | Avalanche                                     | `benqi`          |
| [Drift](https://sdk.docs.almanak.co/api/connectors/drift.html)                   | Perpetuals     | Solana                                        | `drift`          |
| [Fluid DEX](https://sdk.docs.almanak.co/api/connectors/fluid.html)               | DEX            | Ethereum, Arbitrum                            | `fluid`          |
| Jupiter                                                                          | DEX Aggregator | Solana                                        | `jupiter`        |
| Jupiter Lend                                                                     | Lending        | Solana                                        | `jupiter_lend`   |
| Kamino                                                                           | Lending        | Solana                                        | `kamino`         |
| [Lagoon](https://sdk.docs.almanak.co/api/connectors/lagoon.html)                 | Vault          | Multi-chain                                   | `lagoon`         |
| [Meteora](https://sdk.docs.almanak.co/api/connectors/meteora.html)               | DEX / LP       | Solana                                        | `meteora`        |
| [Morpho Vault](https://sdk.docs.almanak.co/api/connectors/morpho_vault.html)     | Vault          | Ethereum, Base                                | `morpho_vault`   |
| [Orca](https://sdk.docs.almanak.co/api/connectors/orca.html)                     | DEX / LP       | Solana                                        | `orca`           |
| [Raydium](https://sdk.docs.almanak.co/api/connectors/raydium.html)               | DEX / LP       | Solana                                        | `raydium`        |
| [Base Infrastructure](https://sdk.docs.almanak.co/api/connectors/base.html)      | Shared         | N/A                                           | `base`           |

# Aave V3

Connector for Aave V3 lending protocol.

## almanak.framework.connectors.aave_v3

Aave V3 Connector.

This module provides an adapter for interacting with Aave V3 lending protocol, supporting supply, borrow, repay, withdraw, flash loans, E-Mode, isolation mode, and comprehensive event parsing.

Aave V3 is a decentralized lending protocol supporting:

- Supply assets to earn yield
- Borrow against collateral
- Flash loans for atomic arbitrage
- Efficiency Mode (E-Mode) for correlated assets
- Isolation Mode for new assets with limited debt ceiling
- Variable and stable interest rates

Supported chains:

- Ethereum
- Arbitrum
- Optimism
- Polygon
- Base
- Avalanche

Example

from almanak.framework.connectors.aave_v3 import AaveV3Adapter, AaveV3Config

config = AaveV3Config( chain="arbitrum", wallet_address="0x...", ) adapter = AaveV3Adapter(config)

### Supply collateral

result = adapter.supply( asset="USDC", amount=Decimal("1000"), )

### Borrow against collateral

result = adapter.borrow( asset="WETH", amount=Decimal("0.5"), )

### Execute flash loan

result = adapter.flash_loan_simple( receiver_address="0x...", asset="USDC", amount=Decimal("100000"), )

### Calculate health factor

hf_calc = adapter.calculate_health_factor( positions=positions, reserve_data=reserve_data, )

### AaveV3Adapter

```
AaveV3Adapter(
    config: AaveV3Config,
    price_oracle: PriceOracle | None = None,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Aave V3 lending protocol.

This adapter provides methods for interacting with Aave V3:

- Supply/withdraw collateral
- Borrow/repay assets
- Flash loans
- E-Mode configuration
- Health factor calculations
- Liquidation price calculations

Example

config = AaveV3Config( chain="arbitrum", wallet_address="0x...", ) adapter = AaveV3Adapter(config)

#### Supply USDC as collateral

result = adapter.supply("USDC", Decimal("1000"))

#### Borrow ETH

result = adapter.borrow("WETH", Decimal("0.5"))

#### Check health factor

hf = adapter.calculate_health_factor(positions, prices)

Initialize the adapter.

Parameters:

| Name             | Type            | Description           | Default                                                                                                                                                                                                                       |
| ---------------- | --------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `config`         | `AaveV3Config`  | Adapter configuration | *required*                                                                                                                                                                                                                    |
| `price_oracle`   | \`PriceOracle   | None\`                | Price oracle callback. REQUIRED for production use. If not provided and allow_placeholder_prices=False, health factor calculations will raise an error. Pass a real price oracle from your MarketSnapshot or PriceAggregator. |
| `token_resolver` | \`TokenResolver | None\`                | Optional TokenResolver instance. If None, uses singleton.                                                                                                                                                                     |

Raises:

| Type         | Description                                                       |
| ------------ | ----------------------------------------------------------------- |
| `ValueError` | If no price_oracle is provided and allow_placeholder_prices=False |

#### supply

```
supply(
    asset: str,
    amount: Decimal,
    on_behalf_of: str | None = None,
) -> TransactionResult
```

Build a supply transaction.

Parameters:

| Name           | Type      | Description                       | Default                                        |
| -------------- | --------- | --------------------------------- | ---------------------------------------------- |
| `asset`        | `str`     | Asset symbol or address           | *required*                                     |
| `amount`       | `Decimal` | Amount to supply (in token units) | *required*                                     |
| `on_behalf_of` | \`str     | None\`                            | Address to credit (defaults to wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### withdraw

```
withdraw(
    asset: str,
    amount: Decimal,
    to: str | None = None,
    withdraw_all: bool = False,
) -> TransactionResult
```

Build a withdraw transaction.

Parameters:

| Name           | Type      | Description                              | Default                                                |
| -------------- | --------- | ---------------------------------------- | ------------------------------------------------------ |
| `asset`        | `str`     | Asset symbol or address                  | *required*                                             |
| `amount`       | `Decimal` | Amount to withdraw (in token units)      | *required*                                             |
| `to`           | \`str     | None\`                                   | Address to receive tokens (defaults to wallet_address) |
| `withdraw_all` | `bool`    | If True, use MAX_UINT256 to withdraw all | `False`                                                |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### borrow

```
borrow(
    asset: str,
    amount: Decimal,
    interest_rate_mode: AaveV3InterestRateMode = AaveV3InterestRateMode.VARIABLE,
    on_behalf_of: str | None = None,
) -> TransactionResult
```

Build a borrow transaction.

Parameters:

| Name                 | Type                     | Description                             | Default                                       |
| -------------------- | ------------------------ | --------------------------------------- | --------------------------------------------- |
| `asset`              | `str`                    | Asset symbol or address                 | *required*                                    |
| `amount`             | `Decimal`                | Amount to borrow (in token units)       | *required*                                    |
| `interest_rate_mode` | `AaveV3InterestRateMode` | Interest rate mode (STABLE or VARIABLE) | `VARIABLE`                                    |
| `on_behalf_of`       | \`str                    | None\`                                  | Address to debit (defaults to wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### repay

```
repay(
    asset: str,
    amount: Decimal,
    interest_rate_mode: AaveV3InterestRateMode = AaveV3InterestRateMode.VARIABLE,
    on_behalf_of: str | None = None,
    repay_all: bool = False,
) -> TransactionResult
```

Build a repay transaction.

Parameters:

| Name                 | Type                     | Description                                 | Default                                        |
| -------------------- | ------------------------ | ------------------------------------------- | ---------------------------------------------- |
| `asset`              | `str`                    | Asset symbol or address                     | *required*                                     |
| `amount`             | `Decimal`                | Amount to repay (in token units)            | *required*                                     |
| `interest_rate_mode` | `AaveV3InterestRateMode` | Interest rate mode of debt to repay         | `VARIABLE`                                     |
| `on_behalf_of`       | \`str                    | None\`                                      | Address with debt (defaults to wallet_address) |
| `repay_all`          | `bool`                   | If True, use MAX_UINT256 to repay full debt | `False`                                        |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### set_user_use_reserve_as_collateral

```
set_user_use_reserve_as_collateral(
    asset: str, use_as_collateral: bool
) -> TransactionResult
```

Build a transaction to enable/disable asset as collateral.

Parameters:

| Name                | Type   | Description                  | Default    |
| ------------------- | ------ | ---------------------------- | ---------- |
| `asset`             | `str`  | Asset symbol or address      | *required* |
| `use_as_collateral` | `bool` | Whether to use as collateral | *required* |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### set_user_emode

```
set_user_emode(category_id: int) -> TransactionResult
```

Build a transaction to set user E-Mode category.

E-Mode (Efficiency Mode) allows higher LTV for correlated assets.

Categories:

- 0: None (normal mode)
- 1: ETH correlated (ETH, wstETH, cbETH, rETH)
- 2: Stablecoins (USDC, USDT, DAI)

Parameters:

| Name          | Type  | Description        | Default    |
| ------------- | ----- | ------------------ | ---------- |
| `category_id` | `int` | E-Mode category ID | *required* |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### get_emode_category_data

```
get_emode_category_data(category_id: int) -> dict[str, Any]
```

Get E-Mode category configuration.

Parameters:

| Name          | Type  | Description        | Default    |
| ------------- | ----- | ------------------ | ---------- |
| `category_id` | `int` | E-Mode category ID | *required* |

Returns:

| Type             | Description                          |
| ---------------- | ------------------------------------ |
| `dict[str, Any]` | Dictionary with E-Mode category data |

#### flash_loan

```
flash_loan(
    receiver_address: str,
    assets: list[str],
    amounts: list[Decimal],
    modes: list[int],
    on_behalf_of: str | None = None,
    params: bytes = b"",
) -> TransactionResult
```

Build a flash loan transaction.

Flash loans allow borrowing assets without collateral, provided they are returned (plus premium) within the same transaction.

Modes:

- 0: No open debt (must repay within same transaction)
- 1: Open stable rate debt
- 2: Open variable rate debt

Parameters:

| Name               | Type            | Description                               | Default                              |
| ------------------ | --------------- | ----------------------------------------- | ------------------------------------ |
| `receiver_address` | `str`           | Contract that will receive the flash loan | *required*                           |
| `assets`           | `list[str]`     | List of asset symbols or addresses        | *required*                           |
| `amounts`          | `list[Decimal]` | List of amounts to borrow                 | *required*                           |
| `modes`            | `list[int]`     | List of debt modes (0, 1, or 2)           | *required*                           |
| `on_behalf_of`     | \`str           | None\`                                    | Address to receive debt if mode != 0 |
| `params`           | `bytes`         | Extra data to pass to receiver            | `b''`                                |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### flash_loan_simple

```
flash_loan_simple(
    receiver_address: str,
    asset: str,
    amount: Decimal,
    params: bytes = b"",
) -> TransactionResult
```

Build a simple flash loan transaction (single asset, no debt).

This is a simplified flash loan for a single asset that must be repaid within the same transaction.

Parameters:

| Name               | Type      | Description                               | Default    |
| ------------------ | --------- | ----------------------------------------- | ---------- |
| `receiver_address` | `str`     | Contract that will receive the flash loan | *required* |
| `asset`            | `str`     | Asset symbol or address                   | *required* |
| `amount`           | `Decimal` | Amount to borrow                          | *required* |
| `params`           | `bytes`   | Extra data to pass to receiver            | `b''`      |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### liquidation_call

```
liquidation_call(
    collateral_asset: str,
    debt_asset: str,
    user: str,
    debt_to_cover: Decimal,
    receive_atoken: bool = False,
) -> TransactionResult
```

Build a liquidation transaction.

Liquidation allows repaying another user's debt in exchange for their collateral at a discount (liquidation bonus).

Parameters:

| Name               | Type      | Description                                    | Default    |
| ------------------ | --------- | ---------------------------------------------- | ---------- |
| `collateral_asset` | `str`     | Asset to receive as collateral                 | *required* |
| `debt_asset`       | `str`     | Asset to repay                                 | *required* |
| `user`             | `str`     | Address of user to liquidate                   | *required* |
| `debt_to_cover`    | `Decimal` | Amount of debt to repay                        | *required* |
| `receive_atoken`   | `bool`    | If True, receive aTokens instead of underlying | `False`    |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### calculate_health_factor

```
calculate_health_factor(
    positions: list[AaveV3Position],
    reserve_data: dict[str, AaveV3ReserveData],
    prices: dict[str, Decimal] | None = None,
    emode_category: int = 0,
) -> AaveV3HealthFactorCalculation
```

Calculate health factor from positions.

Health Factor = (Sum of Collateral * Liquidation Threshold) / Total Debt

A health factor < 1.0 means the position can be liquidated.

Parameters:

| Name             | Type                           | Description                                      | Default                                           |
| ---------------- | ------------------------------ | ------------------------------------------------ | ------------------------------------------------- |
| `positions`      | `list[AaveV3Position]`         | List of user positions                           | *required*                                        |
| `reserve_data`   | `dict[str, AaveV3ReserveData]` | Reserve data for each asset                      | *required*                                        |
| `prices`         | \`dict[str, Decimal]           | None\`                                           | Asset prices in USD (uses oracle if not provided) |
| `emode_category` | `int`                          | User's E-Mode category (affects LT calculations) | `0`                                               |

Returns:

| Type                            | Description                                           |
| ------------------------------- | ----------------------------------------------------- |
| `AaveV3HealthFactorCalculation` | AaveV3HealthFactorCalculation with detailed breakdown |

#### calculate_liquidation_price

```
calculate_liquidation_price(
    collateral_asset: str,
    collateral_amount: Decimal,
    debt_usd: Decimal,
    liquidation_threshold_bps: int,
) -> Decimal
```

Calculate the price at which a position becomes liquidatable.

Parameters:

| Name                        | Type      | Description                           | Default    |
| --------------------------- | --------- | ------------------------------------- | ---------- |
| `collateral_asset`          | `str`     | Collateral asset symbol               | *required* |
| `collateral_amount`         | `Decimal` | Amount of collateral (in token units) | *required* |
| `debt_usd`                  | `Decimal` | Total debt in USD                     | *required* |
| `liquidation_threshold_bps` | `int`     | Liquidation threshold in basis points | *required* |

Returns:

| Type      | Description                                  |
| --------- | -------------------------------------------- |
| `Decimal` | Price at which position becomes liquidatable |

#### calculate_max_borrow

```
calculate_max_borrow(
    collateral_value_usd: Decimal,
    current_debt_usd: Decimal,
    ltv_bps: int,
) -> Decimal
```

Calculate maximum additional borrow amount.

Parameters:

| Name                   | Type      | Description                         | Default    |
| ---------------------- | --------- | ----------------------------------- | ---------- |
| `collateral_value_usd` | `Decimal` | Total collateral value in USD       | *required* |
| `current_debt_usd`     | `Decimal` | Current debt in USD                 | *required* |
| `ltv_bps`              | `int`     | Loan-to-Value ratio in basis points | *required* |

Returns:

| Type      | Description                             |
| --------- | --------------------------------------- |
| `Decimal` | Maximum additional borrow amount in USD |

#### calculate_health_factor_after_borrow

```
calculate_health_factor_after_borrow(
    current_hf_calc: AaveV3HealthFactorCalculation,
    borrow_amount_usd: Decimal,
) -> Decimal
```

Calculate health factor after a hypothetical borrow.

Parameters:

| Name                | Type                            | Description                       | Default    |
| ------------------- | ------------------------------- | --------------------------------- | ---------- |
| `current_hf_calc`   | `AaveV3HealthFactorCalculation` | Current health factor calculation | *required* |
| `borrow_amount_usd` | `Decimal`                       | Amount to borrow in USD           | *required* |

Returns:

| Type      | Description                          |
| --------- | ------------------------------------ |
| `Decimal` | Projected health factor after borrow |

#### get_isolation_mode_debt_ceiling

```
get_isolation_mode_debt_ceiling(asset: str) -> Decimal
```

Get debt ceiling for an isolated asset.

Assets in isolation mode have a debt ceiling limiting total borrows.

Parameters:

| Name    | Type  | Description  | Default    |
| ------- | ----- | ------------ | ---------- |
| `asset` | `str` | Asset symbol | *required* |

Returns:

| Type      | Description                             |
| --------- | --------------------------------------- |
| `Decimal` | Debt ceiling in USD (0 if not isolated) |

#### is_asset_isolated

```
is_asset_isolated(asset: str) -> bool
```

Check if asset is in isolation mode.

Parameters:

| Name    | Type  | Description  | Default    |
| ------- | ----- | ------------ | ---------- |
| `asset` | `str` | Asset symbol | *required* |

Returns:

| Type   | Description               |
| ------ | ------------------------- |
| `bool` | True if asset is isolated |

#### build_approve_tx

```
build_approve_tx(
    asset: str, amount: Decimal | None = None
) -> TransactionResult
```

Build an ERC20 approve transaction for Aave Pool.

Parameters:

| Name     | Type      | Description             | Default                                     |
| -------- | --------- | ----------------------- | ------------------------------------------- |
| `asset`  | `str`     | Asset symbol or address | *required*                                  |
| `amount` | \`Decimal | None\`                  | Amount to approve (defaults to MAX_UINT256) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### get_reserve_data

```
get_reserve_data(asset: str) -> AaveV3ReserveData | None
```

Get reserve data for an asset.

Parameters:

| Name    | Type  | Description  | Default    |
| ------- | ----- | ------------ | ---------- |
| `asset` | `str` | Asset symbol | *required* |

Returns:

| Type                | Description |
| ------------------- | ----------- |
| \`AaveV3ReserveData | None\`      |

#### set_reserve_data

```
set_reserve_data(
    asset: str, data: AaveV3ReserveData
) -> None
```

Set reserve data for an asset (for testing/mocking).

Parameters:

| Name    | Type                | Description  | Default    |
| ------- | ------------------- | ------------ | ---------- |
| `asset` | `str`               | Asset symbol | *required* |
| `data`  | `AaveV3ReserveData` | Reserve data | *required* |

### AaveV3Config

```
AaveV3Config(
    chain: str,
    wallet_address: str,
    default_slippage_bps: int = 50,
    allow_placeholder_prices: bool = False,
)
```

Configuration for Aave V3 adapter.

Attributes:

| Name                       | Type   | Description                                                                                                                                                                                                                                                                  |
| -------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `chain`                    | `str`  | Blockchain network (ethereum, arbitrum, optimism, polygon, base, avalanche)                                                                                                                                                                                                  |
| `wallet_address`           | `str`  | User wallet address                                                                                                                                                                                                                                                          |
| `default_slippage_bps`     | `int`  | Default slippage tolerance in basis points                                                                                                                                                                                                                                   |
| `allow_placeholder_prices` | `bool` | If True, allows fallback to hardcoded placeholder prices. WARNING: NEVER set to True in production! Placeholder prices cause incorrect health factor calculations which can lead to unexpected liquidations. Only use for local testing/development without real price data. |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

### AaveV3EModeCategory

Bases: `IntEnum`

Aave V3 E-Mode categories.

### AaveV3FlashLoanParams

```
AaveV3FlashLoanParams(
    assets: list[str],
    amounts: list[Decimal],
    modes: list[int],
    on_behalf_of: str,
    params: bytes = bytes(),
    referral_code: int = 0,
)
```

Parameters for an Aave V3 flash loan.

Attributes:

| Name            | Type            | Description                                                 |
| --------------- | --------------- | ----------------------------------------------------------- |
| `assets`        | `list[str]`     | List of asset addresses to borrow                           |
| `amounts`       | `list[Decimal]` | List of amounts to borrow (in token units)                  |
| `modes`         | `list[int]`     | Interest rate modes (0 = no debt, 1 = stable, 2 = variable) |
| `on_behalf_of`  | `str`           | Address to receive debt (if modes != 0)                     |
| `params`        | `bytes`         | Extra params to pass to receiver contract                   |
| `referral_code` | `int`           | Referral code (usually 0)                                   |

#### __post_init__

```
__post_init__() -> None
```

Validate flash loan parameters.

### AaveV3HealthFactorCalculation

```
AaveV3HealthFactorCalculation(
    total_collateral_usd: Decimal,
    total_debt_usd: Decimal,
    weighted_liquidation_threshold: Decimal,
    health_factor: Decimal,
    liquidation_price: Decimal | None = None,
    assets_breakdown: dict[str, dict[str, Any]] = dict(),
)
```

Health factor calculation details.

Attributes:

| Name                             | Type                        | Description                            |
| -------------------------------- | --------------------------- | -------------------------------------- |
| `total_collateral_usd`           | `Decimal`                   | Total collateral value in USD          |
| `total_debt_usd`                 | `Decimal`                   | Total debt value in USD                |
| `weighted_liquidation_threshold` | `Decimal`                   | Weighted average liquidation threshold |
| `health_factor`                  | `Decimal`                   | Calculated health factor               |
| `liquidation_price`              | \`Decimal                   | None\`                                 |
| `assets_breakdown`               | `dict[str, dict[str, Any]]` | Per-asset breakdown                    |

#### is_healthy

```
is_healthy: bool
```

Check if position is healthy (HF >= 1).

#### buffer_to_liquidation

```
buffer_to_liquidation: Decimal
```

Get buffer to liquidation as percentage.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### AaveV3InterestRateMode

Bases: `IntEnum`

Aave V3 interest rate modes.

### AaveV3Position

```
AaveV3Position(
    asset: str,
    asset_address: str,
    current_atoken_balance: Decimal = Decimal("0"),
    current_stable_debt: Decimal = Decimal("0"),
    current_variable_debt: Decimal = Decimal("0"),
    principal_stable_debt: Decimal = Decimal("0"),
    scaled_variable_debt: Decimal = Decimal("0"),
    stable_borrow_rate: Decimal = Decimal("0"),
    liquidity_rate: Decimal = Decimal("0"),
    usage_as_collateral_enabled: bool = False,
)
```

User position in a specific Aave V3 reserve.

Attributes:

| Name                          | Type      | Description                                         |
| ----------------------------- | --------- | --------------------------------------------------- |
| `asset`                       | `str`     | Asset symbol                                        |
| `asset_address`               | `str`     | Asset contract address                              |
| `current_atoken_balance`      | `Decimal` | Current aToken balance (supplied amount + interest) |
| `current_stable_debt`         | `Decimal` | Current stable debt                                 |
| `current_variable_debt`       | `Decimal` | Current variable debt                               |
| `principal_stable_debt`       | `Decimal` | Principal stable debt                               |
| `scaled_variable_debt`        | `Decimal` | Scaled variable debt                                |
| `stable_borrow_rate`          | `Decimal` | Current stable borrow rate                          |
| `liquidity_rate`              | `Decimal` | Current supply/liquidity rate                       |
| `usage_as_collateral_enabled` | `bool`    | Whether using as collateral                         |
| `is_collateral`               | `bool`    | Alias for usage_as_collateral_enabled               |

#### is_collateral

```
is_collateral: bool
```

Check if position is being used as collateral.

#### total_debt

```
total_debt: Decimal
```

Get total debt (stable + variable).

#### has_supply

```
has_supply: bool
```

Check if user has supply in this reserve.

#### has_debt

```
has_debt: bool
```

Check if user has debt in this reserve.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### AaveV3ReserveData

```
AaveV3ReserveData(
    asset: str,
    asset_address: str,
    atoken_address: str = "",
    stable_debt_token_address: str = "",
    variable_debt_token_address: str = "",
    ltv: int = 0,
    liquidation_threshold: int = 0,
    liquidation_bonus: int = 0,
    reserve_factor: int = 0,
    usage_as_collateral_enabled: bool = True,
    borrowing_enabled: bool = True,
    stable_borrow_rate_enabled: bool = False,
    is_active: bool = True,
    is_frozen: bool = False,
    is_paused: bool = False,
    supply_cap: Decimal = Decimal("0"),
    borrow_cap: Decimal = Decimal("0"),
    debt_ceiling: Decimal = Decimal("0"),
    emode_ltv: int = 0,
    emode_liquidation_threshold: int = 0,
    emode_liquidation_bonus: int = 0,
    emode_category: int = 0,
)
```

Reserve data for an Aave V3 asset.

Attributes:

| Name                          | Type      | Description                                                 |
| ----------------------------- | --------- | ----------------------------------------------------------- |
| `asset`                       | `str`     | Asset symbol                                                |
| `asset_address`               | `str`     | Asset contract address                                      |
| `atoken_address`              | `str`     | aToken contract address                                     |
| `stable_debt_token_address`   | `str`     | Stable debt token address                                   |
| `variable_debt_token_address` | `str`     | Variable debt token address                                 |
| `ltv`                         | `int`     | Loan-to-Value ratio (in basis points, e.g., 8000 = 80%)     |
| `liquidation_threshold`       | `int`     | Liquidation threshold (in basis points)                     |
| `liquidation_bonus`           | `int`     | Liquidation bonus (in basis points, e.g., 10500 = 5% bonus) |
| `reserve_factor`              | `int`     | Reserve factor (in basis points)                            |
| `usage_as_collateral_enabled` | `bool`    | Whether asset can be used as collateral                     |
| `borrowing_enabled`           | `bool`    | Whether asset can be borrowed                               |
| `stable_borrow_rate_enabled`  | `bool`    | Whether stable borrow rate is enabled                       |
| `is_active`                   | `bool`    | Whether reserve is active                                   |
| `is_frozen`                   | `bool`    | Whether reserve is frozen                                   |
| `is_paused`                   | `bool`    | Whether reserve is paused                                   |
| `supply_cap`                  | `Decimal` | Maximum supply (0 = unlimited)                              |
| `borrow_cap`                  | `Decimal` | Maximum borrow (0 = unlimited)                              |
| `debt_ceiling`                | `Decimal` | Debt ceiling for isolation mode (0 = not isolated)          |
| `emode_ltv`                   | `int`     | LTV when in E-Mode                                          |
| `emode_liquidation_threshold` | `int`     | Liquidation threshold in E-Mode                             |
| `emode_liquidation_bonus`     | `int`     | Liquidation bonus in E-Mode                                 |
| `emode_category`              | `int`     | E-Mode category ID                                          |

#### is_isolated

```
is_isolated: bool
```

Check if asset is in isolation mode.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### AaveV3UserAccountData

```
AaveV3UserAccountData(
    total_collateral_base: Decimal,
    total_debt_base: Decimal,
    available_borrows_base: Decimal,
    current_liquidation_threshold: int,
    ltv: int,
    health_factor: Decimal,
    emode_category: int = 0,
)
```

User account data from Aave V3.

Attributes:

| Name                            | Type      | Description                                      |
| ------------------------------- | --------- | ------------------------------------------------ |
| `total_collateral_base`         | `Decimal` | Total collateral in base currency (USD)          |
| `total_debt_base`               | `Decimal` | Total debt in base currency (USD)                |
| `available_borrows_base`        | `Decimal` | Available borrows in base currency               |
| `current_liquidation_threshold` | `int`     | Current weighted liquidation threshold           |
| `ltv`                           | `int`     | Current weighted LTV                             |
| `health_factor`                 | `Decimal` | Health factor (1e18 = 1.0, < 1.0 = liquidatable) |
| `emode_category`                | `int`     | Current E-Mode category                          |

#### health_factor_normalized

```
health_factor_normalized: Decimal
```

Get health factor as a normalized value (1.0 = healthy threshold).

#### is_liquidatable

```
is_liquidatable: bool
```

Check if position is liquidatable.

#### distance_to_liquidation

```
distance_to_liquidation: Decimal
```

Get distance to liquidation (0 = liquidatable, 1 = 2x health factor).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### TransactionResult

```
TransactionResult(
    success: bool,
    tx_data: dict[str, Any] | None = None,
    gas_estimate: int = 0,
    description: str = "",
    error: str | None = None,
)
```

Result of a transaction build operation.

Attributes:

| Name           | Type             | Description                 |
| -------------- | ---------------- | --------------------------- |
| `success`      | `bool`           | Whether operation succeeded |
| `tx_data`      | \`dict[str, Any] | None\`                      |
| `gas_estimate` | `int`            | Estimated gas               |
| `description`  | `str`            | Human-readable description  |
| `error`        | \`str            | None\`                      |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### AaveV3Event

```
AaveV3Event(
    event_type: AaveV3EventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(tz=None))(),
)
```

Parsed Aave V3 event.

Attributes:

| Name               | Type              | Description                    |
| ------------------ | ----------------- | ------------------------------ |
| `event_type`       | `AaveV3EventType` | Type of event                  |
| `event_name`       | `str`             | Name of event (e.g., "Supply") |
| `log_index`        | `int`             | Index of log in transaction    |
| `transaction_hash` | `str`             | Transaction hash               |
| `block_number`     | `int`             | Block number                   |
| `contract_address` | `str`             | Contract that emitted event    |
| `data`             | `dict[str, Any]`  | Parsed event data              |
| `raw_topics`       | `list[str]`       | Raw event topics               |
| `raw_data`         | `str`             | Raw event data                 |
| `timestamp`        | `datetime`        | Event timestamp                |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> AaveV3Event
```

Create from dictionary.

### AaveV3EventType

Bases: `Enum`

Aave V3 event types.

### AaveV3ReceiptParser

```
AaveV3ReceiptParser(**kwargs: Any)
```

Parser for Aave V3 transaction receipts.

This parser extracts and decodes Aave V3 events from transaction receipts, providing structured data for supplies, borrows, repays, flash loans, liquidations, and other protocol events.

Example

parser = AaveV3ReceiptParser()

#### Parse a receipt dict (from web3.py)

result = parser.parse_receipt(receipt)

if result.success: for event in result.events: print(f"Event: {event.event_name}")

```
for supply in result.supplies:
    print(f"Supply: {supply.amount} to {supply.reserve}")
```

Initialize the parser.

Parameters:

| Name       | Type  | Description                                                     | Default |
| ---------- | ----- | --------------------------------------------------------------- | ------- |
| `**kwargs` | `Any` | Additional arguments. Accepts 'chain' for token decimal lookup. | `{}`    |

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name      | Type             | Description                                                                        | Default    |
| --------- | ---------------- | ---------------------------------------------------------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict containing 'logs', 'transactionHash', 'blockNumber', etc. | *required* |

Returns:

| Type          | Description                                |
| ------------- | ------------------------------------------ |
| `ParseResult` | ParseResult with extracted events and data |

#### parse_logs

```
parse_logs(logs: list[dict[str, Any]]) -> list[AaveV3Event]
```

Parse a list of logs.

Parameters:

| Name   | Type                   | Description       | Default    |
| ------ | ---------------------- | ----------------- | ---------- |
| `logs` | `list[dict[str, Any]]` | List of log dicts | *required* |

Returns:

| Type                | Description           |
| ------------------- | --------------------- |
| `list[AaveV3Event]` | List of parsed events |

#### extract_supply_amount

```
extract_supply_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract supply amount from a transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_withdraw_amount

```
extract_withdraw_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract withdraw amount from a transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_borrow_amount

```
extract_borrow_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract borrow amount from a transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_repay_amount

```
extract_repay_amount(receipt: dict[str, Any]) -> int | None
```

Extract repay amount from a transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_a_token_received

```
extract_a_token_received(
    receipt: dict[str, Any],
) -> int | None
```

Extract aToken amount received from a transaction receipt.

Looks for Transfer events from the zero address (minting).

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_borrow_rate

```
extract_borrow_rate(
    receipt: dict[str, Any],
) -> Decimal | None
```

Extract borrow rate from a transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### extract_debt_token

```
extract_debt_token(receipt: dict[str, Any]) -> str | None
```

Extract debt token address from a Borrow transaction receipt.

Finds the variable debt token by looking for Transfer events from 0x0 (minting) where the amount matches the borrow amount. The emitting contract address is the debt token.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

#### extract_supply_rate

```
extract_supply_rate(
    receipt: dict[str, Any],
) -> Decimal | None
```

Extract supply rate (APY) from a transaction receipt.

Reads the currentLiquidityRate from the ReserveDataUpdated event, which is emitted by the Aave V3 pool on every supply/borrow/repay/withdraw.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### extract_supply_amounts

```
extract_supply_amounts(
    receipt: dict[str, Any],
) -> SupplyAmountsResult | None
```

Extract aggregated supply data from a transaction receipt.

Returns a SupplyAmountsResult combining supply_amount, a_token_received, and supply_rate into a single structured object. Called by ResultEnricher for SUPPLY intents.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type                  | Description |
| --------------------- | ----------- |
| \`SupplyAmountsResult | None\`      |

#### extract_borrow_amounts

```
extract_borrow_amounts(
    receipt: dict[str, Any],
) -> BorrowAmountsResult | None
```

Extract aggregated borrow data from a transaction receipt.

Returns a BorrowAmountsResult combining borrow_amount, borrow_rate, and debt_token into a single structured object. Called by ResultEnricher for BORROW intents.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type                  | Description |
| --------------------- | ----------- |
| \`BorrowAmountsResult | None\`      |

#### extract_repay_amounts

```
extract_repay_amounts(
    receipt: dict[str, Any],
) -> RepayAmountsResult | None
```

Extract aggregated repay data from a transaction receipt.

Returns a RepayAmountsResult combining repay_amount and remaining_debt into a single structured object. Called by ResultEnricher for REPAY intents.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type                 | Description |
| -------------------- | ----------- |
| \`RepayAmountsResult | None\`      |

#### extract_withdraw_amounts

```
extract_withdraw_amounts(
    receipt: dict[str, Any],
) -> WithdrawAmountsResult | None
```

Extract aggregated withdraw data from a transaction receipt.

Returns a WithdrawAmountsResult combining withdraw_amount and a_token_burned into a single structured object. Called by ResultEnricher for WITHDRAW intents.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type                    | Description |
| ----------------------- | ----------- |
| \`WithdrawAmountsResult | None\`      |

#### extract_a_token_burned

```
extract_a_token_burned(
    receipt: dict[str, Any],
) -> int | None
```

Extract aToken amount burned from a WITHDRAW transaction receipt.

Looks for Transfer events TO the zero address (burning) in the receipt. These are emitted by the aToken contract when tokens are burned on withdrawal.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_remaining_debt

```
extract_remaining_debt(
    receipt: dict[str, Any],
) -> int | None
```

Extract remaining debt after a Repay transaction.

Aave V3 does not emit authoritative post-repay debt balance in receipt events. Debt token Transfer amounts use scaled units (not raw amounts), and accrued interest can cause Mint events during burns. Determining remaining debt reliably requires an on-chain state query (balanceOf on the debt token).

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### is_aave_event

```
is_aave_event(topic: str | bytes) -> bool
```

Check if a topic is a known Aave V3 event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                            |
| ------ | -------------------------------------- |
| `bool` | True if topic is a known Aave V3 event |

#### get_event_type

```
get_event_type(topic: str | bytes) -> AaveV3EventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type              | Description           |
| ----------------- | --------------------- |
| `AaveV3EventType` | Event type or UNKNOWN |

### BorrowEventData

```
BorrowEventData(
    reserve: str,
    user: str,
    on_behalf_of: str,
    amount: Decimal,
    interest_rate_mode: int,
    borrow_rate: Decimal = Decimal("0"),
    referral_code: int = 0,
)
```

Parsed data from Borrow event.

Attributes:

| Name                 | Type      | Description                      |
| -------------------- | --------- | -------------------------------- |
| `reserve`            | `str`     | Asset address that was borrowed  |
| `user`               | `str`     | User who initiated the borrow    |
| `on_behalf_of`       | `str`     | Address that received the debt   |
| `amount`             | `Decimal` | Amount borrowed (in token units) |
| `interest_rate_mode` | `int`     | 1 = stable, 2 = variable         |
| `borrow_rate`        | `Decimal` | Current borrow rate (ray, 1e27)  |
| `referral_code`      | `int`     | Referral code used               |

#### is_variable_rate

```
is_variable_rate: bool
```

Check if borrow is variable rate.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### FlashLoanEventData

```
FlashLoanEventData(
    target: str,
    initiator: str,
    asset: str,
    amount: Decimal,
    interest_rate_mode: int,
    premium: Decimal = Decimal("0"),
    referral_code: int = 0,
)
```

Parsed data from FlashLoan event.

Attributes:

| Name                 | Type      | Description                                       |
| -------------------- | --------- | ------------------------------------------------- |
| `target`             | `str`     | Contract that received the flash loan             |
| `initiator`          | `str`     | Address that initiated the flash loan             |
| `asset`              | `str`     | Asset address that was borrowed                   |
| `amount`             | `Decimal` | Amount borrowed (in token units)                  |
| `interest_rate_mode` | `int`     | Debt mode (0 = no debt, 1 = stable, 2 = variable) |
| `premium`            | `Decimal` | Premium paid (in token units)                     |
| `referral_code`      | `int`     | Referral code used                                |

#### opened_debt

```
opened_debt: bool
```

Check if flash loan opened debt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### IsolationModeDebtUpdatedEventData

```
IsolationModeDebtUpdatedEventData(
    asset: str, total_debt: Decimal
)
```

Parsed data from IsolationModeTotalDebtUpdated event.

Attributes:

| Name         | Type      | Description                             |
| ------------ | --------- | --------------------------------------- |
| `asset`      | `str`     | Asset address                           |
| `total_debt` | `Decimal` | New total debt (in USD with 2 decimals) |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### LiquidationCallEventData

```
LiquidationCallEventData(
    collateral_asset: str,
    debt_asset: str,
    user: str,
    debt_to_cover: Decimal,
    liquidated_collateral_amount: Decimal,
    liquidator: str,
    receive_atoken: bool = False,
)
```

Parsed data from LiquidationCall event.

Attributes:

| Name                           | Type      | Description                                             |
| ------------------------------ | --------- | ------------------------------------------------------- |
| `collateral_asset`             | `str`     | Collateral asset that was seized                        |
| `debt_asset`                   | `str`     | Debt asset that was repaid                              |
| `user`                         | `str`     | User who was liquidated                                 |
| `debt_to_cover`                | `Decimal` | Amount of debt repaid (in debt token units)             |
| `liquidated_collateral_amount` | `Decimal` | Amount of collateral seized (in collateral token units) |
| `liquidator`                   | `str`     | Address that performed the liquidation                  |
| `receive_atoken`               | `bool`    | Whether liquidator received aTokens                     |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### ParseResult

```
ParseResult(
    success: bool,
    events: list[AaveV3Event] = list(),
    supplies: list[SupplyEventData] = list(),
    withdraws: list[WithdrawEventData] = list(),
    borrows: list[BorrowEventData] = list(),
    repays: list[RepayEventData] = list(),
    flash_loans: list[FlashLoanEventData] = list(),
    liquidations: list[LiquidationCallEventData] = list(),
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
)
```

Result of parsing a receipt.

Attributes:

| Name               | Type                             | Description               |
| ------------------ | -------------------------------- | ------------------------- |
| `success`          | `bool`                           | Whether parsing succeeded |
| `events`           | `list[AaveV3Event]`              | List of parsed events     |
| `supplies`         | `list[SupplyEventData]`          | Supply events             |
| `withdraws`        | `list[WithdrawEventData]`        | Withdraw events           |
| `borrows`          | `list[BorrowEventData]`          | Borrow events             |
| `repays`           | `list[RepayEventData]`           | Repay events              |
| `flash_loans`      | `list[FlashLoanEventData]`       | Flash loan events         |
| `liquidations`     | `list[LiquidationCallEventData]` | Liquidation events        |
| `error`            | \`str                            | None\`                    |
| `transaction_hash` | `str`                            | Transaction hash          |
| `block_number`     | `int`                            | Block number              |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### RepayEventData

```
RepayEventData(
    reserve: str,
    user: str,
    repayer: str,
    amount: Decimal,
    use_atokens: bool = False,
)
```

Parsed data from Repay event.

Attributes:

| Name          | Type      | Description                             |
| ------------- | --------- | --------------------------------------- |
| `reserve`     | `str`     | Asset address that was repaid           |
| `user`        | `str`     | User whose debt was repaid              |
| `repayer`     | `str`     | Address that made the repayment         |
| `amount`      | `Decimal` | Amount repaid (in token units)          |
| `use_atokens` | `bool`    | Whether aTokens were used for repayment |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### ReserveDataUpdatedEventData

```
ReserveDataUpdatedEventData(
    reserve: str,
    liquidity_rate: Decimal,
    stable_borrow_rate: Decimal,
    variable_borrow_rate: Decimal,
    liquidity_index: Decimal = Decimal("0"),
    variable_borrow_index: Decimal = Decimal("0"),
)
```

Parsed data from ReserveDataUpdated event.

Attributes:

| Name                    | Type      | Description                               |
| ----------------------- | --------- | ----------------------------------------- |
| `reserve`               | `str`     | Asset address                             |
| `liquidity_rate`        | `Decimal` | Current supply/liquidity rate (ray, 1e27) |
| `stable_borrow_rate`    | `Decimal` | Current stable borrow rate (ray, 1e27)    |
| `variable_borrow_rate`  | `Decimal` | Current variable borrow rate (ray, 1e27)  |
| `liquidity_index`       | `Decimal` | Current liquidity index                   |
| `variable_borrow_index` | `Decimal` | Current variable borrow index             |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SupplyEventData

```
SupplyEventData(
    reserve: str,
    user: str,
    on_behalf_of: str,
    amount: Decimal,
    referral_code: int = 0,
)
```

Parsed data from Supply event.

Attributes:

| Name            | Type      | Description                       |
| --------------- | --------- | --------------------------------- |
| `reserve`       | `str`     | Asset address that was supplied   |
| `user`          | `str`     | User who initiated the supply     |
| `on_behalf_of`  | `str`     | Address that received the aTokens |
| `amount`        | `Decimal` | Amount supplied (in token units)  |
| `referral_code` | `int`     | Referral code used                |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### UserEModeSetEventData

```
UserEModeSetEventData(user: str, category_id: int)
```

Parsed data from UserEModeSet event.

Attributes:

| Name          | Type  | Description                                                        |
| ------------- | ----- | ------------------------------------------------------------------ |
| `user`        | `str` | User address                                                       |
| `category_id` | `int` | E-Mode category ID (0 = none, 1 = ETH correlated, 2 = stablecoins) |

#### category_name

```
category_name: str
```

Get category name.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### WithdrawEventData

```
WithdrawEventData(
    reserve: str, user: str, to: str, amount: Decimal
)
```

Parsed data from Withdraw event.

Attributes:

| Name      | Type      | Description                       |
| --------- | --------- | --------------------------------- |
| `reserve` | `str`     | Asset address that was withdrawn  |
| `user`    | `str`     | User who initiated the withdrawal |
| `to`      | `str`     | Address that received the tokens  |
| `amount`  | `Decimal` | Amount withdrawn (in token units) |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

# Aerodrome

Connector for Aerodrome (Solidly-based) DEX on Base.

## almanak.framework.connectors.aerodrome

Aerodrome Finance Connector.

This package provides integration with Aerodrome Finance, a Solidly-based AMM on Base chain. Aerodrome supports dual pool types:

- Volatile pools: x\*y=k formula (0.3% fee)
- Stable pools: x^3\*y + y^3\*x formula (0.05% fee)

Key Features:

- Token swaps (exact input)
- Liquidity provision (add/remove)
- Fungible LP tokens (not NFT positions like Uniswap V3)

Example

from almanak.framework.connectors.aerodrome import AerodromeAdapter, AerodromeConfig

config = AerodromeConfig( chain="base", wallet_address="0x...", ) adapter = AerodromeAdapter(config)

### Execute a volatile pool swap

result = adapter.swap_exact_input( token_in="USDC", token_out="WETH", amount_in=Decimal("1000"), stable=False, )

### Execute a stable pool swap

result = adapter.swap_exact_input( token_in="USDC", token_out="USDbC", amount_in=Decimal("1000"), stable=True, )

### AerodromeAdapter

```
AerodromeAdapter(
    config: AerodromeConfig,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Aerodrome Finance DEX protocol.

This adapter provides methods for:

- Executing token swaps (exact input)
- Adding and removing liquidity
- Building swap and LP transactions
- Handling ERC-20 approvals
- Managing slippage protection

Example

config = AerodromeConfig( chain="base", wallet_address="0x...", ) adapter = AerodromeAdapter(config)

#### Execute a volatile pool swap

result = adapter.swap_exact_input( token_in="USDC", token_out="WETH", amount_in=Decimal("1000"), stable=False, slippage_bps=50, )

#### Execute a stable pool swap

result = adapter.swap_exact_input( token_in="USDC", token_out="USDbC", amount_in=Decimal("1000"), stable=True, slippage_bps=10, )

Initialize the adapter.

Parameters:

| Name             | Type              | Description                     | Default                                                   |
| ---------------- | ----------------- | ------------------------------- | --------------------------------------------------------- |
| `config`         | `AerodromeConfig` | Aerodrome adapter configuration | *required*                                                |
| `token_resolver` | \`TokenResolver   | None\`                          | Optional TokenResolver instance. If None, uses singleton. |

#### swap_exact_input

```
swap_exact_input(
    token_in: str,
    token_out: str,
    amount_in: Decimal,
    stable: bool = False,
    slippage_bps: int | None = None,
    recipient: str | None = None,
    tick_spacing: int = 100,
    use_classic: bool = False,
) -> SwapResult
```

Build a swap transaction with exact input amount.

By default, routes through the Slipstream CL (concentrated liquidity) pool. Use `use_classic=True` to opt into the Classic (v1) volatile/stable router.

Parameters:

| Name           | Type      | Description                                                 | Default                                                    |
| -------------- | --------- | ----------------------------------------------------------- | ---------------------------------------------------------- |
| `token_in`     | `str`     | Input token symbol or address                               | *required*                                                 |
| `token_out`    | `str`     | Output token symbol or address                              | *required*                                                 |
| `amount_in`    | `Decimal` | Amount of input token (in token units, not wei)             | *required*                                                 |
| `stable`       | `bool`    | Pool type for Classic routing (True=stable, False=volatile) | `False`                                                    |
| `slippage_bps` | \`int     | None\`                                                      | Slippage tolerance in basis points (default from config)   |
| `recipient`    | \`str     | None\`                                                      | Address to receive output tokens (default: wallet_address) |
| `tick_spacing` | `int`     | Slipstream CL tick spacing (default 100)                    | `100`                                                      |
| `use_classic`  | `bool`    | If True, route through Classic router instead of CL         | `False`                                                    |

Returns:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `SwapResult` | SwapResult with transaction data |

#### add_liquidity

```
add_liquidity(
    token_a: str,
    token_b: str,
    amount_a: Decimal,
    amount_b: Decimal,
    stable: bool = False,
    slippage_bps: int | None = None,
    recipient: str | None = None,
) -> LiquidityResult
```

Build an add liquidity transaction.

Parameters:

| Name           | Type      | Description                        | Default                            |
| -------------- | --------- | ---------------------------------- | ---------------------------------- |
| `token_a`      | `str`     | First token symbol or address      | *required*                         |
| `token_b`      | `str`     | Second token symbol or address     | *required*                         |
| `amount_a`     | `Decimal` | Amount of token A (in token units) | *required*                         |
| `amount_b`     | `Decimal` | Amount of token B (in token units) | *required*                         |
| `stable`       | `bool`    | Pool type                          | `False`                            |
| `slippage_bps` | \`int     | None\`                             | Slippage tolerance in basis points |
| `recipient`    | \`str     | None\`                             | Address to receive LP tokens       |

Returns:

| Type              | Description                           |
| ----------------- | ------------------------------------- |
| `LiquidityResult` | LiquidityResult with transaction data |

#### remove_liquidity

```
remove_liquidity(
    token_a: str,
    token_b: str,
    liquidity: Decimal,
    stable: bool = False,
    slippage_bps: int | None = None,
    recipient: str | None = None,
    pool_address: str | None = None,
) -> LiquidityResult
```

Build a remove liquidity transaction.

Parameters:

| Name           | Type      | Description                                 | Default                                                                                                                                                       |
| -------------- | --------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token_a`      | `str`     | First token symbol or address               | *required*                                                                                                                                                    |
| `token_b`      | `str`     | Second token symbol or address              | *required*                                                                                                                                                    |
| `liquidity`    | `Decimal` | LP token amount to burn (in LP token units) | *required*                                                                                                                                                    |
| `stable`       | `bool`    | Pool type                                   | `False`                                                                                                                                                       |
| `slippage_bps` | \`int     | None\`                                      | Slippage tolerance in basis points                                                                                                                            |
| `recipient`    | \`str     | None\`                                      | Address to receive tokens                                                                                                                                     |
| `pool_address` | \`str     | None\`                                      | Pre-resolved pool address. If provided, skips the direct RPC lookup (required for deployed mode where the strategy container has no outbound network access). |

Returns:

| Type              | Description                           |
| ----------------- | ------------------------------------- |
| `LiquidityResult` | LiquidityResult with transaction data |

#### compile_swap_intent

```
compile_swap_intent(
    intent: SwapIntent,
    stable: bool = False,
    price_oracle: dict[str, Decimal] | None = None,
) -> ActionBundle
```

Compile a SwapIntent to an ActionBundle.

Parameters:

| Name           | Type                 | Description                             | Default                                   |
| -------------- | -------------------- | --------------------------------------- | ----------------------------------------- |
| `intent`       | `SwapIntent`         | The SwapIntent to compile               | *required*                                |
| `stable`       | `bool`               | Pool type (True=stable, False=volatile) | `False`                                   |
| `price_oracle` | \`dict[str, Decimal] | None\`                                  | Optional price oracle for USD conversions |

Returns:

| Type           | Description                                        |
| -------------- | -------------------------------------------------- |
| `ActionBundle` | ActionBundle containing transactions for execution |

#### set_allowance

```
set_allowance(
    token: str, spender: str, amount: int
) -> None
```

Set cached allowance (for testing).

#### clear_allowance_cache

```
clear_allowance_cache() -> None
```

Clear the allowance cache.

### AerodromeConfig

```
AerodromeConfig(
    chain: str,
    wallet_address: str,
    default_slippage_bps: int = 50,
    deadline_seconds: int = DEFAULT_DEADLINE_SECONDS,
    price_provider: dict[str, Decimal] | None = None,
    allow_placeholder_prices: bool = False,
    rpc_url: str | None = None,
)
```

Configuration for AerodromeAdapter.

Attributes:

| Name                       | Type                 | Description                                                                                             |
| -------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------- |
| `chain`                    | `str`                | Target blockchain ("base" for Aerodrome, "optimism" for Velodrome V2)                                   |
| `wallet_address`           | `str`                | Address executing transactions                                                                          |
| `default_slippage_bps`     | `int`                | Default slippage tolerance in basis points (default 50 = 0.5%)                                          |
| `deadline_seconds`         | `int`                | Transaction deadline in seconds (default 300 = 5 minutes)                                               |
| `price_provider`           | \`dict[str, Decimal] | None\`                                                                                                  |
| `allow_placeholder_prices` | `bool`               | If False (default), raises ValueError when no price_provider is given. Set to True ONLY for unit tests. |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### LiquidityResult

```
LiquidityResult(
    success: bool,
    transactions: list[TransactionData] = list(),
    token_a: str = "",
    token_b: str = "",
    amount_a: int = 0,
    amount_b: int = 0,
    liquidity: int = 0,
    stable: bool = False,
    error: str | None = None,
    gas_estimate: int = 0,
)
```

Result of a liquidity operation.

Attributes:

| Name           | Type                    | Description                              |
| -------------- | ----------------------- | ---------------------------------------- |
| `success`      | `bool`                  | Whether operation was built successfully |
| `transactions` | `list[TransactionData]` | List of transactions to execute          |
| `token_a`      | `str`                   | First token address                      |
| `token_b`      | `str`                   | Second token address                     |
| `amount_a`     | `int`                   | Amount of token A                        |
| `amount_b`     | `int`                   | Amount of token B                        |
| `liquidity`    | `int`                   | LP tokens (minted or burned)             |
| `stable`       | `bool`                  | Pool type                                |
| `error`        | \`str                   | None\`                                   |
| `gas_estimate` | `int`                   | Total gas estimate                       |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PoolType

Bases: `Enum`

Pool type for Aerodrome.

### SwapQuote

```
SwapQuote(
    token_in: str,
    token_out: str,
    amount_in: int,
    amount_out: int,
    stable: bool,
    gas_estimate: int = AERODROME_GAS_ESTIMATES["swap"],
    price_impact_bps: int = 0,
    effective_price: Decimal = Decimal("0"),
    quoted_at: datetime = (lambda: datetime.now(UTC))(),
)
```

Quote for a swap operation.

Attributes:

| Name               | Type       | Description                             |
| ------------------ | ---------- | --------------------------------------- |
| `token_in`         | `str`      | Input token address                     |
| `token_out`        | `str`      | Output token address                    |
| `amount_in`        | `int`      | Input amount in wei                     |
| `amount_out`       | `int`      | Output amount in wei                    |
| `stable`           | `bool`     | Pool type (True=stable, False=volatile) |
| `gas_estimate`     | `int`      | Estimated gas for the swap              |
| `price_impact_bps` | `int`      | Price impact in basis points            |
| `effective_price`  | `Decimal`  | Effective price of the swap             |
| `quoted_at`        | `datetime` | Timestamp when quote was fetched        |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapResult

```
SwapResult(
    success: bool,
    transactions: list[TransactionData] = list(),
    quote: SwapQuote | None = None,
    amount_in: int = 0,
    amount_out_minimum: int = 0,
    error: str | None = None,
    gas_estimate: int = 0,
)
```

Result of a swap operation.

Attributes:

| Name                 | Type                    | Description                             |
| -------------------- | ----------------------- | --------------------------------------- |
| `success`            | `bool`                  | Whether the swap was built successfully |
| `transactions`       | `list[TransactionData]` | List of transactions to execute         |
| `quote`              | \`SwapQuote             | None\`                                  |
| `amount_in`          | `int`                   | Actual input amount                     |
| `amount_out_minimum` | `int`                   | Minimum output amount (with slippage)   |
| `error`              | \`str                   | None\`                                  |
| `gas_estimate`       | `int`                   | Total gas estimate for all transactions |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapType

Bases: `Enum`

Type of swap operation.

### TransactionData

```
TransactionData(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
    tx_type: str = "swap",
)
```

Transaction data for execution.

Attributes:

| Name           | Type  | Description                                                          |
| -------------- | ----- | -------------------------------------------------------------------- |
| `to`           | `str` | Target contract address                                              |
| `value`        | `int` | Native token value to send                                           |
| `data`         | `str` | Encoded calldata                                                     |
| `gas_estimate` | `int` | Estimated gas                                                        |
| `description`  | `str` | Human-readable description                                           |
| `tx_type`      | `str` | Type of transaction (approve, swap, add_liquidity, remove_liquidity) |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### AerodromeEvent

```
AerodromeEvent(
    event_type: AerodromeEventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)
```

Parsed Aerodrome event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> AerodromeEvent
```

Create from dictionary.

### AerodromeEventType

Bases: `Enum`

Aerodrome event types.

### AerodromeReceiptParser

```
AerodromeReceiptParser(
    chain: str = "base",
    token0_address: str | None = None,
    token1_address: str | None = None,
    token0_symbol: str | None = None,
    token1_symbol: str | None = None,
    token0_decimals: int | None = None,
    token1_decimals: int | None = None,
    quoted_price: Decimal | None = None,
    **kwargs: Any,
)
```

Parser for Aerodrome transaction receipts.

Refactored to use base infrastructure utilities for hex decoding and event registry management. Maintains full backward compatibility.

Initialize the parser.

Parameters:

| Name              | Type      | Description                                      | Default                                 |
| ----------------- | --------- | ------------------------------------------------ | --------------------------------------- |
| `chain`           | `str`     | Blockchain network (for token symbol resolution) | `'base'`                                |
| `token0_address`  | \`str     | None\`                                           | Address of token0 in the pool           |
| `token1_address`  | \`str     | None\`                                           | Address of token1 in the pool           |
| `token0_symbol`   | \`str     | None\`                                           | Symbol of token0                        |
| `token1_symbol`   | \`str     | None\`                                           | Symbol of token1                        |
| `token0_decimals` | \`int     | None\`                                           | Decimals for token0                     |
| `token1_decimals` | \`int     | None\`                                           | Decimals for token1                     |
| `quoted_price`    | \`Decimal | None\`                                           | Expected price for slippage calculation |

#### parse_receipt

```
parse_receipt(
    receipt: dict[str, Any],
    quoted_amount_out: int | None = None,
) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name                | Type             | Description              | Default                                         |
| ------------------- | ---------------- | ------------------------ | ----------------------------------------------- |
| `receipt`           | `dict[str, Any]` | Transaction receipt dict | *required*                                      |
| `quoted_amount_out` | \`int            | None\`                   | Expected output amount for slippage calculation |

Returns:

| Type          | Description                                     |
| ------------- | ----------------------------------------------- |
| `ParseResult` | ParseResult with extracted events and swap data |

#### extract_swap_amounts

```
extract_swap_amounts(
    receipt: dict[str, Any],
) -> SwapAmounts | None
```

Extract swap amounts from a transaction receipt.

Resolves token decimals independently from ERC-20 Transfer events in the receipt, so it produces correct human-readable amounts even when the parser was constructed without token metadata (the enrichment path).

Parameters:

| Name      | Type             | Description                                            | Default    |
| --------- | ---------------- | ------------------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' and 'from' fields | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`SwapAmounts | None\`      |

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> LPCloseData | None
```

Extract LP close data from transaction receipt.

Primary path: extracts from Burn events (amount0, amount1). Fallback path: extracts from Transfer events when Burn event is not detected (some Aerodrome pool variants may not emit a standard Burn event, but always emit Transfer events for the returned tokens).

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`LPCloseData | None\`      |

#### extract_liquidity

```
extract_liquidity(receipt: dict[str, Any]) -> int | None
```

Extract liquidity from LP mint transaction receipt.

For Aerodrome V1, this extracts the LP tokens minted from Transfer events.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### is_aerodrome_event

```
is_aerodrome_event(topic: str | bytes) -> bool
```

Check if a topic is a known Aerodrome event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                              |
| ------ | ---------------------------------------- |
| `bool` | True if topic is a known Aerodrome event |

#### get_event_type

```
get_event_type(topic: str | bytes) -> AerodromeEventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type                 | Description           |
| -------------------- | --------------------- |
| `AerodromeEventType` | Event type or UNKNOWN |

### BurnEventData

```
BurnEventData(
    sender: str,
    amount0: int,
    amount1: int,
    to: str,
    pool_address: str,
)
```

Parsed data from Burn event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### MintEventData

```
MintEventData(
    sender: str,
    amount0: int,
    amount1: int,
    pool_address: str,
)
```

Parsed data from Mint event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### ParsedLiquidityResult

```
ParsedLiquidityResult(
    operation: str,
    token0: str,
    token1: str,
    token0_symbol: str,
    token1_symbol: str,
    amount0: int,
    amount1: int,
    amount0_decimal: Decimal,
    amount1_decimal: Decimal,
    pool_address: str,
)
```

High-level liquidity result extracted from receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### ParsedSwapResult

```
ParsedSwapResult(
    token_in: str,
    token_out: str,
    token_in_symbol: str,
    token_out_symbol: str,
    amount_in: int,
    amount_out: int,
    amount_in_decimal: Decimal,
    amount_out_decimal: Decimal,
    effective_price: Decimal,
    slippage_bps: int,
    pool_address: str,
)
```

High-level swap result extracted from receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### to_swap_result_payload

```
to_swap_result_payload() -> SwapResultPayload
```

Convert to SwapResultPayload for event emission.

### ParseResult

```
ParseResult(
    success: bool,
    events: list[AerodromeEvent] = list(),
    swap_events: list[SwapEventData] = list(),
    mint_events: list[MintEventData] = list(),
    burn_events: list[BurnEventData] = list(),
    transfer_events: list[TransferEventData] = list(),
    swap_result: ParsedSwapResult | None = None,
    liquidity_result: ParsedLiquidityResult | None = None,
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
    transaction_success: bool = True,
)
```

Result of parsing a receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapEventData

```
SwapEventData(
    sender: str,
    to: str,
    amount0_in: int,
    amount1_in: int,
    amount0_out: int,
    amount1_out: int,
    pool_address: str,
)
```

Parsed data from Swap event.

#### token0_is_input

```
token0_is_input: bool
```

Check if token0 is the input token.

#### token1_is_input

```
token1_is_input: bool
```

Check if token1 is the input token.

#### amount_in

```
amount_in: int
```

Get the input amount.

#### amount_out

```
amount_out: int
```

Get the output amount.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### TransferEventData

```
TransferEventData(
    from_addr: str,
    to_addr: str,
    value: int,
    token_address: str,
)
```

Parsed data from Transfer event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### AerodromeSDK

```
AerodromeSDK(
    chain: str = "base",
    rpc_url: str | None = None,
    token_resolver: TokenResolver | None = None,
)
```

Low-level SDK for Aerodrome/Velodrome Finance (Solidly forks).

This SDK provides direct interaction with Solidly-fork contracts (Aerodrome on Base, Velodrome V2 on Optimism):

- Pool queries (reserves, amounts)
- Transaction building (swaps, liquidity)
- ABI encoding for all operations

Example

sdk = AerodromeSDK(chain="base")

#### Get quote for swap

quote = sdk.get_swap_quote( token_in="0x...", token_out="0x...", amount_in=1000000, stable=False, )

#### Build swap transaction

tx = sdk.build_swap_exact_tokens_tx( amount_in=1000000, amount_out_min=990000, routes=[SwapRoute(token_in, token_out, stable=False)], recipient="0x...", deadline=int(time.time()) + 300, sender="0x...", )

Initialize the SDK.

Parameters:

| Name             | Type            | Description                                                      | Default                                                   |
| ---------------- | --------------- | ---------------------------------------------------------------- | --------------------------------------------------------- |
| `chain`          | `str`           | Target chain ("base" for Aerodrome, "optimism" for Velodrome V2) | `'base'`                                                  |
| `rpc_url`        | \`str           | None\`                                                           | RPC endpoint URL (optional)                               |
| `token_resolver` | \`TokenResolver | None\`                                                           | Optional TokenResolver instance. If None, uses singleton. |

#### get_pool_address

```
get_pool_address(
    token_a: str, token_b: str, stable: bool
) -> str | None
```

Get pool address from factory (uses internal RPC or env var).

Parameters:

| Name      | Type   | Description          | Default    |
| --------- | ------ | -------------------- | ---------- |
| `token_a` | `str`  | First token address  | *required* |
| `token_b` | `str`  | Second token address | *required* |
| `stable`  | `bool` | Pool type            | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

#### get_pool_address_from_factory

```
get_pool_address_from_factory(
    token_a: str, token_b: str, stable: bool, web3: Any
) -> str | None
```

Get pool address from factory contract.

Parameters:

| Name      | Type   | Description          | Default    |
| --------- | ------ | -------------------- | ---------- |
| `token_a` | `str`  | First token address  | *required* |
| `token_b` | `str`  | Second token address | *required* |
| `stable`  | `bool` | Pool type            | *required* |
| `web3`    | `Any`  | Web3 instance        | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

#### get_pool_info

```
get_pool_info(
    token_a: str, token_b: str, stable: bool, web3: Any
) -> PoolInfo | None
```

Get full pool information.

Parameters:

| Name      | Type   | Description          | Default    |
| --------- | ------ | -------------------- | ---------- |
| `token_a` | `str`  | First token address  | *required* |
| `token_b` | `str`  | Second token address | *required* |
| `stable`  | `bool` | Pool type            | *required* |
| `web3`    | `Any`  | Web3 instance        | *required* |

Returns:

| Type       | Description |
| ---------- | ----------- |
| \`PoolInfo | None\`      |

#### get_amount_out

```
get_amount_out(
    amount_in: int,
    token_in: str,
    token_out: str,
    stable: bool,
    web3: Any,
) -> int | None
```

Get expected output amount for a swap.

Parameters:

| Name        | Type   | Description          | Default    |
| ----------- | ------ | -------------------- | ---------- |
| `amount_in` | `int`  | Input amount         | *required* |
| `token_in`  | `str`  | Input token address  | *required* |
| `token_out` | `str`  | Output token address | *required* |
| `stable`    | `bool` | Pool type            | *required* |
| `web3`      | `Any`  | Web3 instance        | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### get_amounts_out

```
get_amounts_out(
    amount_in: int, routes: list[SwapRoute], web3: Any
) -> list[int] | None
```

Get expected output amounts for multi-hop swap.

Parameters:

| Name        | Type              | Description         | Default    |
| ----------- | ----------------- | ------------------- | ---------- |
| `amount_in` | `int`             | Input amount        | *required* |
| `routes`    | `list[SwapRoute]` | List of swap routes | *required* |
| `web3`      | `Any`             | Web3 instance       | *required* |

Returns:

| Type        | Description |
| ----------- | ----------- |
| \`list[int] | None\`      |

#### build_approve_tx

```
build_approve_tx(
    token_address: str,
    spender: str,
    amount: int,
    sender: str,
    web3: Any,
) -> dict[str, Any]
```

Build ERC-20 approve transaction.

Parameters:

| Name            | Type  | Description                                       | Default    |
| --------------- | ----- | ------------------------------------------------- | ---------- |
| `token_address` | `str` | Token to approve                                  | *required* |
| `spender`       | `str` | Address to approve for spending                   | *required* |
| `amount`        | `int` | Amount to approve (use MAX_UINT256 for unlimited) | *required* |
| `sender`        | `str` | Transaction sender                                | *required* |
| `web3`          | `Any` | Web3 instance                                     | *required* |

Returns:

| Type             | Description            |
| ---------------- | ---------------------- |
| `dict[str, Any]` | Transaction dictionary |

#### build_swap_exact_tokens_tx

```
build_swap_exact_tokens_tx(
    amount_in: int,
    amount_out_min: int,
    routes: list[SwapRoute],
    recipient: str,
    deadline: int,
    sender: str,
    web3: Any,
) -> dict[str, Any]
```

Build swapExactTokensForTokens transaction.

Parameters:

| Name             | Type              | Description                                 | Default    |
| ---------------- | ----------------- | ------------------------------------------- | ---------- |
| `amount_in`      | `int`             | Input token amount                          | *required* |
| `amount_out_min` | `int`             | Minimum output amount (slippage protection) | *required* |
| `routes`         | `list[SwapRoute]` | Swap routes                                 | *required* |
| `recipient`      | `str`             | Recipient address                           | *required* |
| `deadline`       | `int`             | Unix timestamp deadline                     | *required* |
| `sender`         | `str`             | Transaction sender                          | *required* |
| `web3`           | `Any`             | Web3 instance                               | *required* |

Returns:

| Type             | Description            |
| ---------------- | ---------------------- |
| `dict[str, Any]` | Transaction dictionary |

#### build_add_liquidity_tx

```
build_add_liquidity_tx(
    token_a: str,
    token_b: str,
    stable: bool,
    amount_a_desired: int,
    amount_b_desired: int,
    amount_a_min: int,
    amount_b_min: int,
    recipient: str,
    deadline: int,
    sender: str,
    web3: Any,
) -> dict[str, Any]
```

Build addLiquidity transaction.

Parameters:

| Name               | Type   | Description               | Default    |
| ------------------ | ------ | ------------------------- | ---------- |
| `token_a`          | `str`  | First token address       | *required* |
| `token_b`          | `str`  | Second token address      | *required* |
| `stable`           | `bool` | Pool type                 | *required* |
| `amount_a_desired` | `int`  | Desired amount of token A | *required* |
| `amount_b_desired` | `int`  | Desired amount of token B | *required* |
| `amount_a_min`     | `int`  | Minimum amount of token A | *required* |
| `amount_b_min`     | `int`  | Minimum amount of token B | *required* |
| `recipient`        | `str`  | LP token recipient        | *required* |
| `deadline`         | `int`  | Unix timestamp deadline   | *required* |
| `sender`           | `str`  | Transaction sender        | *required* |
| `web3`             | `Any`  | Web3 instance             | *required* |

Returns:

| Type             | Description            |
| ---------------- | ---------------------- |
| `dict[str, Any]` | Transaction dictionary |

#### build_remove_liquidity_tx

```
build_remove_liquidity_tx(
    token_a: str,
    token_b: str,
    stable: bool,
    liquidity: int,
    amount_a_min: int,
    amount_b_min: int,
    recipient: str,
    deadline: int,
    sender: str,
    web3: Any,
) -> dict[str, Any]
```

Build removeLiquidity transaction.

Parameters:

| Name           | Type   | Description                | Default    |
| -------------- | ------ | -------------------------- | ---------- |
| `token_a`      | `str`  | First token address        | *required* |
| `token_b`      | `str`  | Second token address       | *required* |
| `stable`       | `bool` | Pool type                  | *required* |
| `liquidity`    | `int`  | LP token amount to burn    | *required* |
| `amount_a_min` | `int`  | Minimum token A to receive | *required* |
| `amount_b_min` | `int`  | Minimum token B to receive | *required* |
| `recipient`    | `str`  | Token recipient            | *required* |
| `deadline`     | `int`  | Unix timestamp deadline    | *required* |
| `sender`       | `str`  | Transaction sender         | *required* |
| `web3`         | `Any`  | Web3 instance              | *required* |

Returns:

| Type             | Description            |
| ---------------- | ---------------------- |
| `dict[str, Any]` | Transaction dictionary |

#### build_wrap_eth_tx

```
build_wrap_eth_tx(
    amount: int, sender: str, web3: Any
) -> dict[str, Any]
```

Build WETH wrap (deposit) transaction.

Parameters:

| Name     | Type  | Description        | Default    |
| -------- | ----- | ------------------ | ---------- |
| `amount` | `int` | ETH amount to wrap | *required* |
| `sender` | `str` | Transaction sender | *required* |
| `web3`   | `Any` | Web3 instance      | *required* |

Returns:

| Type             | Description            |
| ---------------- | ---------------------- |
| `dict[str, Any]` | Transaction dictionary |

#### build_unwrap_eth_tx

```
build_unwrap_eth_tx(
    amount: int, sender: str, web3: Any
) -> dict[str, Any]
```

Build WETH unwrap (withdraw) transaction.

Parameters:

| Name     | Type  | Description           | Default    |
| -------- | ----- | --------------------- | ---------- |
| `amount` | `int` | WETH amount to unwrap | *required* |
| `sender` | `str` | Transaction sender    | *required* |
| `web3`   | `Any` | Web3 instance         | *required* |

Returns:

| Type             | Description            |
| ---------------- | ---------------------- |
| `dict[str, Any]` | Transaction dictionary |

#### resolve_token

```
resolve_token(token: str) -> str
```

Resolve token symbol to address using TokenResolver.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `token` | `str` | Token symbol or address | *required* |

Returns:

| Type  | Description   |
| ----- | ------------- |
| `str` | Token address |

Raises:

| Type                   | Description                     |
| ---------------------- | ------------------------------- |
| `TokenResolutionError` | If the token cannot be resolved |

#### get_token_symbol

```
get_token_symbol(address: str) -> str
```

Get token symbol from address using TokenResolver.

Parameters:

| Name      | Type  | Description   | Default    |
| --------- | ----- | ------------- | ---------- |
| `address` | `str` | Token address | *required* |

Returns:

| Type  | Description  |
| ----- | ------------ |
| `str` | Token symbol |

#### get_token_decimals

```
get_token_decimals(symbol: str) -> int
```

Get token decimals from symbol using TokenResolver.

Parameters:

| Name     | Type  | Description  | Default    |
| -------- | ----- | ------------ | ---------- |
| `symbol` | `str` | Token symbol | *required* |

Returns:

| Type  | Description    |
| ----- | -------------- |
| `int` | Token decimals |

Raises:

| Type                   | Description                      |
| ---------------------- | -------------------------------- |
| `TokenResolutionError` | If decimals cannot be determined |

### AerodromeSDKError

Bases: `Exception`

Base exception for Aerodrome SDK errors.

### InsufficientLiquidityError

Bases: `AerodromeSDKError`

Raised when pool has insufficient liquidity.

### PoolInfo

```
PoolInfo(
    address: str,
    token0: str,
    token1: str,
    stable: bool,
    reserve0: int = 0,
    reserve1: int = 0,
    decimals0: int = 18,
    decimals1: int = 18,
)
```

Information about an Aerodrome pool.

Attributes:

| Name        | Type   | Description                              |
| ----------- | ------ | ---------------------------------------- |
| `address`   | `str`  | Pool contract address                    |
| `token0`    | `str`  | First token address                      |
| `token1`    | `str`  | Second token address                     |
| `stable`    | `bool` | True for stable pool, False for volatile |
| `reserve0`  | `int`  | Current reserve of token0                |
| `reserve1`  | `int`  | Current reserve of token1                |
| `decimals0` | `int`  | Decimals of token0                       |
| `decimals1` | `int`  | Decimals of token1                       |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PoolNotFoundError

Bases: `AerodromeSDKError`

Raised when a pool doesn't exist.

### SwapRoute

```
SwapRoute(
    from_token: str,
    to_token: str,
    stable: bool,
    factory: str | None = None,
)
```

A single hop in a swap route.

Attributes:

| Name         | Type   | Description                             |
| ------------ | ------ | --------------------------------------- |
| `from_token` | `str`  | Input token address                     |
| `to_token`   | `str`  | Output token address                    |
| `stable`     | `bool` | Pool type (True=stable, False=volatile) |
| `factory`    | \`str  | None\`                                  |

#### to_tuple

```
to_tuple(default_factory: str) -> tuple
```

Convert to tuple format for contract call.

All addresses are checksummed to prevent web3.py rejection which would silently fall back to zero slippage protection.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SDKSwapQuote

```
SDKSwapQuote(
    amount_in: int,
    amount_out: int,
    routes: list[SwapRoute],
    price_impact_bps: int = 0,
    gas_estimate: int = AERODROME_GAS_ESTIMATES["swap"],
)
```

Quote for a swap operation.

Attributes:

| Name               | Type              | Description                            |
| ------------------ | ----------------- | -------------------------------------- |
| `amount_in`        | `int`             | Input amount                           |
| `amount_out`       | `int`             | Expected output amount                 |
| `routes`           | `list[SwapRoute]` | List of swap routes                    |
| `price_impact_bps` | `int`             | Estimated price impact in basis points |
| `gas_estimate`     | `int`             | Estimated gas for the swap             |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

# Balancer

Connector for Balancer DEX.

## almanak.framework.connectors.balancer

Balancer Flash Loan Connector.

This module provides an adapter for executing flash loans via Balancer's Vault contract. Balancer flash loans have zero fees (no premium), making them ideal for arbitrage.

Balancer Vault features:

- Zero-fee flash loans (no premium)
- Single or multi-token flash loans
- All supported tokens available from liquidity pools
- Simple interface: flashLoan(recipient, tokens, amounts, userData)

Supported chains:

- Ethereum
- Arbitrum
- Optimism
- Polygon
- Base

Example

from almanak.framework.connectors.balancer import BalancerFlashLoanAdapter, BalancerFlashLoanConfig

config = BalancerFlashLoanConfig( chain="arbitrum", wallet_address="0x...", ) adapter = BalancerFlashLoanAdapter(config)

### Execute flash loan

result = adapter.flash_loan( recipient="0x...", tokens=["USDC"], amounts=[Decimal("100000")], )

### BalancerFlashLoanAdapter

```
BalancerFlashLoanAdapter(
    chain: str, protocol: str = "balancer"
)
```

Adapter for Balancer Vault flash loans.

Balancer flash loans have zero fees, making them ideal for arbitrage strategies. The Vault contract holds all pool liquidity, enabling large flash loans.

Example

config = BalancerFlashLoanConfig( chain="arbitrum", wallet_address="0x...", ) adapter = BalancerFlashLoanAdapter(config)

#### Get flash loan calldata

calldata = adapter.get_flash_loan_calldata( recipient="0x...", tokens=["0x...USDC", "0x...WETH"], amounts=[1000000000, 500000000000000000], user_data=b"", )

Initialize the adapter.

Parameters:

| Name       | Type  | Description                       | Default      |
| ---------- | ----- | --------------------------------- | ------------ |
| `chain`    | `str` | Target blockchain                 | *required*   |
| `protocol` | `str` | Protocol name (always "balancer") | `'balancer'` |

#### get_vault_address

```
get_vault_address() -> str
```

Get the Balancer Vault address.

#### get_flash_loan_calldata

```
get_flash_loan_calldata(
    recipient: str,
    tokens: list[str],
    amounts: list[int],
    user_data: bytes = b"",
) -> bytes
```

Generate calldata for a Balancer flash loan.

Balancer flashLoan function: flashLoan( IFlashLoanRecipient recipient, IERC20[] memory tokens, uint256[] memory amounts, bytes memory userData )

Parameters:

| Name        | Type        | Description                                                  | Default    |
| ----------- | ----------- | ------------------------------------------------------------ | ---------- |
| `recipient` | `str`       | Contract address that will receive and handle the flash loan | *required* |
| `tokens`    | `list[str]` | List of token addresses to borrow                            | *required* |
| `amounts`   | `list[int]` | List of amounts to borrow (in token's smallest units)        | *required* |
| `user_data` | `bytes`     | Extra data to pass to receiver's receiveFlashLoan            | `b''`      |

Returns:

| Type    | Description                                    |
| ------- | ---------------------------------------------- |
| `bytes` | Encoded calldata for the flashLoan transaction |

#### get_flash_loan_simple_calldata

```
get_flash_loan_simple_calldata(
    recipient: str,
    token: str,
    amount: int,
    user_data: bytes = b"",
) -> bytes
```

Generate calldata for a single-token flash loan.

This is a convenience method that wraps get_flash_loan_calldata for single-token flash loans.

Parameters:

| Name        | Type    | Description                                       | Default    |
| ----------- | ------- | ------------------------------------------------- | ---------- |
| `recipient` | `str`   | Contract address that will receive the flash loan | *required* |
| `token`     | `str`   | Token address to borrow                           | *required* |
| `amount`    | `int`   | Amount to borrow (in token's smallest units)      | *required* |
| `user_data` | `bytes` | Extra data to pass to receiver's receiveFlashLoan | `b''`      |

Returns:

| Type    | Description                                    |
| ------- | ---------------------------------------------- |
| `bytes` | Encoded calldata for the flashLoan transaction |

#### estimate_flash_loan_gas

```
estimate_flash_loan_gas() -> int
```

Estimate gas for a multi-token flash loan (base only, not including callbacks).

#### estimate_flash_loan_simple_gas

```
estimate_flash_loan_simple_gas() -> int
```

Estimate gas for a single-token flash loan (base only, not including callbacks).

### BalancerFlashLoanConfig

```
BalancerFlashLoanConfig(chain: str, wallet_address: str)
```

Configuration for Balancer flash loan adapter.

Attributes:

| Name             | Type  | Description                                                     |
| ---------------- | ----- | --------------------------------------------------------------- |
| `chain`          | `str` | Target blockchain (ethereum, arbitrum, optimism, polygon, base) |
| `wallet_address` | `str` | Address executing the flash loan                                |

### BalancerFlashLoanParams

```
BalancerFlashLoanParams(
    recipient: str,
    tokens: list[str],
    amounts: list[int],
    user_data: bytes = bytes(),
)
```

Parameters for a Balancer flash loan.

Attributes:

| Name        | Type        | Description                                                    |
| ----------- | ----------- | -------------------------------------------------------------- |
| `recipient` | `str`       | Contract that will receive the flash loan and handle callbacks |
| `tokens`    | `list[str]` | List of token addresses to borrow                              |
| `amounts`   | `list[int]` | List of amounts to borrow (in wei)                             |
| `user_data` | `bytes`     | Arbitrary bytes to pass to the receiver's receiveFlashLoan()   |

### TransactionResult

```
TransactionResult(
    success: bool,
    calldata: bytes = bytes(),
    to: str = "",
    value: int = 0,
    gas_estimate: int = 0,
    error: str | None = None,
)
```

Result of a transaction operation.

Attributes:

| Name           | Type    | Description                                  |
| -------------- | ------- | -------------------------------------------- |
| `success`      | `bool`  | Whether the operation succeeded              |
| `calldata`     | `bytes` | Generated calldata for the transaction       |
| `to`           | `str`   | Target contract address                      |
| `value`        | `int`   | ETH value to send (always 0 for flash loans) |
| `gas_estimate` | `int`   | Estimated gas for the transaction            |
| `error`        | \`str   | None\`                                       |

# Base Infrastructure

Shared base classes and utilities used by all connectors.

## BaseReceiptParser

## almanak.framework.connectors.base.BaseReceiptParser

```
BaseReceiptParser(
    registry: EventRegistry | None = None,
    known_topics: set[str] | None = None,
)
```

Bases: `ABC`

Abstract base class for receipt parsers using template method pattern.

This class implements the common receipt parsing flow and provides hook methods for protocol-specific customization. Subclasses must implement:

- \_decode_log_data(): Protocol-specific log decoding
- \_create_event(): Create protocol-specific event object
- \_build_result(): Build protocol-specific result object

The template method parse_receipt() handles:

- Transaction status validation
- Log iteration and filtering
- Event creation and collection
- Error handling

Attributes:

| Name                    | Type             | Description                                                                                                                                                                                |
| ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `registry`              |                  | EventRegistry for topic lookups (optional)                                                                                                                                                 |
| `known_topics`          |                  | Set of known topic signatures (optional)                                                                                                                                                   |
| `SUPPORTED_EXTRACTIONS` | `frozenset[str]` | Class-level frozenset declaring which extraction fields this parser supports (e.g., {"swap_amounts", "position_id"}). Used by ResultEnricher to warn when expected fields are unsupported. |

Example

> > > from almanak.framework.connectors.base import BaseReceiptParser
> > >
> > > class MyProtocolParser(BaseReceiptParser[MyEvent, MyResult]): ... def **init**(self): ... super().**init**(registry=my_registry) ... ... def \_decode_log_data(self, event_name, topics, data, contract_address): ... # Decode protocol-specific log data ... if event_name == "Swap": ... return { ... "amount_in": HexDecoder.decode_uint256(data, 0), ... "amount_out": HexDecoder.decode_uint256(data, 32), ... } ... return {} ... ... def \_create_event(self, event_name, log_index, tx_hash, ...): ... # Create protocol-specific event object ... return MyEvent(...) ... ... def \_build_result(self, events, receipt, \*\*kwargs): ... # Build protocol-specific result ... return MyResult(success=True, events=events)

Initialize the base parser.

Parameters:

| Name           | Type            | Description | Default                                                       |
| -------------- | --------------- | ----------- | ------------------------------------------------------------- |
| `registry`     | \`EventRegistry | None\`      | EventRegistry for topic lookups (optional)                    |
| `known_topics` | \`set[str]      | None\`      | Set of known topic signatures (optional, used if no registry) |

### parse_receipt

```
parse_receipt(receipt: dict[str, Any], **kwargs) -> TResult
```

Parse a transaction receipt (template method).

This is the main entry point that implements the template method pattern. It handles common parsing logic and calls hook methods for protocol-specific customization.

Flow:

1. Validate transaction status
1. Extract transaction metadata
1. Iterate through logs
1. For each log: a. Check if event is known b. Decode log data (via \_decode_log_data hook) c. Create event object (via \_create_event hook)
1. Build final result (via \_build_result hook)

Parameters:

| Name       | Type             | Description                                                                                                                                                                                                                  | Default    |
| ---------- | ---------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `receipt`  | `dict[str, Any]` | Transaction receipt dict from web3.py containing: - transactionHash: Transaction hash (bytes or hex string) - blockNumber: Block number (int) - status: Transaction status (1=success, 0=reverted) - logs: List of log dicts | *required* |
| `**kwargs` | `Any`            | Additional protocol-specific parameters                                                                                                                                                                                      | `{}`       |

Returns:

| Type      | Description                     |
| --------- | ------------------------------- |
| `TResult` | Protocol-specific result object |

Raises:

| Type        | Description                                                 |
| ----------- | ----------------------------------------------------------- |
| `Exception` | If parsing fails critically (caught and returned in result) |

## EventRegistry

## almanak.framework.connectors.base.EventRegistry

```
EventRegistry(
    event_topics: dict[str, str],
    event_type_map: dict[str, Any],
)
```

Registry for managing event topic mappings.

This class manages the mapping between event topic signatures (keccak256 hashes) and event names/types. Provides fast lookup and validation methods.

Attributes:

| Name             | Type             | Description                                         |
| ---------------- | ---------------- | --------------------------------------------------- |
| `event_topics`   |                  | Mapping from event name to topic signature          |
| `topic_to_event` | `dict[str, str]` | Reverse mapping from topic signature to event name  |
| `event_type_map` |                  | Mapping from event name to enum type                |
| `known_topics`   |                  | Set of all known topic signatures (for fast lookup) |

Example

> > > from enum import Enum from almanak.framework.connectors.base import EventRegistry
> > >
> > > class MyEventType(Enum): ... SWAP = "SWAP" ... MINT = "MINT"
> > >
> > > EVENT_TOPICS = { ... "Swap": "0xc42079...", ... "Mint": "0x7a5308...", ... }
> > >
> > > EVENT_NAME_TO_TYPE = { ... "Swap": MyEventType.SWAP, ... "Mint": MyEventType.MINT, ... }
> > >
> > > registry = EventRegistry(EVENT_TOPICS, EVENT_NAME_TO_TYPE) registry.get_event_name("0xc42079...") 'Swap' registry.get_event_type("Swap")

Initialize the event registry.

Parameters:

| Name             | Type             | Description                                                                                        | Default    |
| ---------------- | ---------------- | -------------------------------------------------------------------------------------------------- | ---------- |
| `event_topics`   | `dict[str, str]` | Mapping from event name to topic signature Example: {"Swap": "0xc42079...", "Mint": "0x7a5308..."} | *required* |
| `event_type_map` | `dict[str, Any]` | Mapping from event name to enum type Example: {"Swap": MyEventType.SWAP, "Mint": MyEventType.MINT} | *required* |

### get_event_name

```
get_event_name(topic: str) -> str | None
```

Get event name from topic signature.

Parameters:

| Name    | Type  | Description                            | Default    |
| ------- | ----- | -------------------------------------- | ---------- |
| `topic` | `str` | Event topic signature (keccak256 hash) | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

Example

> > > registry.get_event_name("0xc42079...") 'Swap'

### get_event_type

```
get_event_type(event_name: str) -> Any | None
```

Get event type enum from event name.

Parameters:

| Name         | Type  | Description               | Default    |
| ------------ | ----- | ------------------------- | ---------- |
| `event_name` | `str` | Event name (e.g., "Swap") | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`Any | None\`      |

Example

> > > registry.get_event_type("Swap")

### get_event_type_from_topic

```
get_event_type_from_topic(topic: str) -> Any | None
```

Get event type enum directly from topic signature.

Convenience method that combines get_event_name() and get_event_type().

Parameters:

| Name    | Type  | Description                            | Default    |
| ------- | ----- | -------------------------------------- | ---------- |
| `topic` | `str` | Event topic signature (keccak256 hash) | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`Any | None\`      |

Example

> > > registry.get_event_type_from_topic("0xc42079...")

### is_known_event

```
is_known_event(topic: str) -> bool
```

Check if a topic is a known event.

Parameters:

| Name    | Type  | Description                    | Default    |
| ------- | ----- | ------------------------------ | ---------- |
| `topic` | `str` | Event topic signature to check | *required* |

Returns:

| Type   | Description                      |
| ------ | -------------------------------- |
| `bool` | True if topic is in the registry |

Example

> > > registry.is_known_event("0xc42079...") True registry.is_known_event("0xunknown...") False

### get_topic_signature

```
get_topic_signature(event_name: str) -> str | None
```

Get topic signature from event name.

Parameters:

| Name         | Type  | Description               | Default    |
| ------------ | ----- | ------------------------- | ---------- |
| `event_name` | `str` | Event name (e.g., "Swap") | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

Example

> > > registry.get_topic_signature("Swap") '0xc42079...'

### __len__

```
__len__() -> int
```

Get number of registered events.

### __contains__

```
__contains__(topic: str) -> bool
```

Check if topic is in registry (allows 'in' operator).

### __repr__

```
__repr__() -> str
```

Get string representation of registry.

## HexDecoder

## almanak.framework.connectors.base.HexDecoder

Static utilities for decoding hex-encoded event data.

This class provides methods for decoding common EVM types from hex strings, including:

- Addresses (20 bytes)
- Unsigned integers: uint256, uint160, uint128
- Signed integers: int256, int128, int24
- Dynamic arrays (for batch events)
- Raw bytes32 values

All methods are static and handle both bytes and string inputs.

### normalize_hex

```
normalize_hex(value: Any) -> str
```

Normalize a hex value to a string without '0x' prefix.

Parameters:

| Name    | Type  | Description                        | Default    |
| ------- | ----- | ---------------------------------- | ---------- |
| `value` | `Any` | Bytes or string value to normalize | *required* |

Returns:

| Type  | Description                    |
| ----- | ------------------------------ |
| `str` | Hex string without '0x' prefix |

### topic_to_address

```
topic_to_address(topic: Any) -> str
```

Convert a log topic to an Ethereum address.

Topics are 32 bytes, but addresses are only 20 bytes. This extracts the last 20 bytes as the address.

Parameters:

| Name    | Type  | Description                       | Default    |
| ------- | ----- | --------------------------------- | ---------- |
| `topic` | `Any` | Topic value (bytes or hex string) | *required* |

Returns:

| Type  | Description                                                           |
| ----- | --------------------------------------------------------------------- |
| `str` | Lowercase address with '0x' prefix, or empty string if topic is empty |

Example

> > > HexDecoder.topic_to_address( ... "0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" ... ) '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'

### topic_to_bytes32

```
topic_to_bytes32(topic: Any) -> str
```

Convert a log topic to a bytes32 hex string.

Parameters:

| Name    | Type  | Description                       | Default    |
| ------- | ----- | --------------------------------- | ---------- |
| `topic` | `Any` | Topic value (bytes or hex string) | *required* |

Returns:

| Type  | Description                              |
| ----- | ---------------------------------------- |
| `str` | Full 32-byte hex string with '0x' prefix |

Example

> > > HexDecoder.topic_to_bytes32(b'\\x00' * 31 + b'\\x01') '0x0000000000000000000000000000000000000000000000000000000000000001'

### decode_uint256

```
decode_uint256(hex_str: str, offset: int = 0) -> int
```

Decode an unsigned 256-bit integer from hex string.

Parameters:

| Name      | Type  | Description                                 | Default    |
| --------- | ----- | ------------------------------------------- | ---------- |
| `hex_str` | `str` | Hex string to decode (with or without '0x') | *required* |
| `offset`  | `int` | Byte offset to start reading from           | `0`        |

Returns:

| Type  | Description                    |
| ----- | ------------------------------ |
| `int` | Decoded unsigned integer value |

Example

> > > HexDecoder.decode_uint256("0x00000000000000000000000000000000000000000000000000000000000003e8") 1000

### decode_uint160

```
decode_uint160(hex_str: str, offset: int = 0) -> int
```

Decode an unsigned 160-bit integer from hex string.

Used for Uniswap V3 sqrtPriceX96 which is uint160.

Parameters:

| Name      | Type  | Description                                 | Default    |
| --------- | ----- | ------------------------------------------- | ---------- |
| `hex_str` | `str` | Hex string to decode (with or without '0x') | *required* |
| `offset`  | `int` | Byte offset to start reading from           | `0`        |

Returns:

| Type  | Description                    |
| ----- | ------------------------------ |
| `int` | Decoded unsigned integer value |

### decode_uint128

```
decode_uint128(hex_str: str, offset: int = 0) -> int
```

Decode an unsigned 128-bit integer from hex string.

Used for Uniswap V3 liquidity which is uint128.

Parameters:

| Name      | Type  | Description                                 | Default    |
| --------- | ----- | ------------------------------------------- | ---------- |
| `hex_str` | `str` | Hex string to decode (with or without '0x') | *required* |
| `offset`  | `int` | Byte offset to start reading from           | `0`        |

Returns:

| Type  | Description                    |
| ----- | ------------------------------ |
| `int` | Decoded unsigned integer value |

### decode_int256

```
decode_int256(hex_str: str, offset: int = 0) -> int
```

Decode a signed 256-bit integer from hex string.

Handles two's complement representation for negative numbers.

Parameters:

| Name      | Type  | Description                                 | Default    |
| --------- | ----- | ------------------------------------------- | ---------- |
| `hex_str` | `str` | Hex string to decode (with or without '0x') | *required* |
| `offset`  | `int` | Byte offset to start reading from           | `0`        |

Returns:

| Type  | Description                                    |
| ----- | ---------------------------------------------- |
| `int` | Decoded signed integer value (can be negative) |

Example

> > > #### Positive value
> > >
> > > HexDecoder.decode_int256("0x00000000000000000000000000000000000000000000000000000000000003e8") 1000
> > >
> > > #### Negative value (two's complement)
> > >
> > > HexDecoder.decode_int256("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc18") -1000

### decode_int128

```
decode_int128(hex_str: str, offset: int = 0) -> int
```

Decode a signed 128-bit integer from hex string.

Used by Curve for token amounts. Handles two's complement.

Parameters:

| Name      | Type  | Description                                 | Default    |
| --------- | ----- | ------------------------------------------- | ---------- |
| `hex_str` | `str` | Hex string to decode (with or without '0x') | *required* |
| `offset`  | `int` | Byte offset to start reading from           | `0`        |

Returns:

| Type  | Description                                    |
| ----- | ---------------------------------------------- |
| `int` | Decoded signed integer value (can be negative) |

### decode_int24

```
decode_int24(hex_str: str, offset: int = 0) -> int
```

Decode a signed 24-bit integer from hex string.

Used for Uniswap V3 ticks. Value is stored in 256-bit slot but only uses 24 bits. Handles two's complement.

Parameters:

| Name      | Type  | Description                                 | Default    |
| --------- | ----- | ------------------------------------------- | ---------- |
| `hex_str` | `str` | Hex string to decode (with or without '0x') | *required* |
| `offset`  | `int` | Byte offset to start reading from           | `0`        |

Returns:

| Type  | Description                                    |
| ----- | ---------------------------------------------- |
| `int` | Decoded signed integer value (can be negative) |

Example

> > > #### Positive tick
> > >
> > > HexDecoder.decode_int24("0x0000000000000000000000000000000000000000000000000000000000000064") 100
> > >
> > > #### Negative tick
> > >
> > > HexDecoder.decode_int24("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff9c") -100

### decode_dynamic_array

```
decode_dynamic_array(
    hex_str: str, offset: int = 0
) -> list[int]
```

Decode a dynamic array from hex string.

Dynamic arrays in EVM events are encoded as:

- Offset to array data (32 bytes)
- Array length (32 bytes)
- Array elements (32 bytes each)

Used by TraderJoe V2 for bin IDs and Polymarket for batch transfers.

Parameters:

| Name      | Type  | Description                                 | Default    |
| --------- | ----- | ------------------------------------------- | ---------- |
| `hex_str` | `str` | Hex string to decode (with or without '0x') | *required* |
| `offset`  | `int` | Byte offset to start reading array offset   | `0`        |

Returns:

| Type        | Description                    |
| ----------- | ------------------------------ |
| `list[int]` | List of decoded uint256 values |

Example

> > > #### Array [1, 2, 3] encoded in event data
> > >
> > > data = "0x" + "0" * 64 + "0" * 62 + "03" + "0" * 62 + "01" + "0" * 62 + "02" + "0" * 62 + "03" HexDecoder.decode_dynamic_array(data, 0) [1, 2, 3]

### decode_address_from_data

```
decode_address_from_data(
    hex_str: str, offset: int = 0
) -> str
```

Decode an address from event data (not indexed topic).

Unlike indexed addresses which are in topics, non-indexed addresses appear in the data section as 32-byte values with leading zeros.

Parameters:

| Name      | Type  | Description                                 | Default    |
| --------- | ----- | ------------------------------------------- | ---------- |
| `hex_str` | `str` | Hex string to decode (with or without '0x') | *required* |
| `offset`  | `int` | Byte offset to start reading from           | `0`        |

Returns:

| Type  | Description                        |
| ----- | ---------------------------------- |
| `str` | Lowercase address with '0x' prefix |

Example

> > > HexDecoder.decode_address_from_data( ... "0x000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" ... ) '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48'

### split_into_chunks

```
split_into_chunks(
    hex_str: str, chunk_size: int = 64
) -> list[str]
```

Split hex string into chunks of specified size.

Useful for parsing event data with multiple parameters.

Parameters:

| Name         | Type  | Description                                                  | Default    |
| ------------ | ----- | ------------------------------------------------------------ | ---------- |
| `hex_str`    | `str` | Hex string to split (with or without '0x')                   | *required* |
| `chunk_size` | `int` | Size of each chunk in hex characters (default 64 = 32 bytes) | `64`       |

Returns:

| Type        | Description        |
| ----------- | ------------------ |
| `list[str]` | List of hex chunks |

Example

> > > data = "0x" + "0" * 64 + "1" * 64 chunks = HexDecoder.split_into_chunks(data) len(chunks) 2

# Bridges

Connector for cross-chain bridge protocols.

## almanak.framework.connectors.bridges

Bridge Connectors.

This package contains adapters for cross-chain bridge protocols, providing a unified interface for bridging assets between chains.

Available Bridges:

- Across: Fast bridge using optimistic verification (Arbitrum, Optimism, Base, Polygon, Ethereum)
- Stargate: LayerZero-based bridge for stablecoins and native assets

Example

from almanak.framework.connectors.bridges import BridgeAdapter, BridgeQuote, BridgeStatus

### Get a quote for bridging

quote = adapter.get_quote( token="USDC", amount=Decimal("1000"), from_chain="arbitrum", to_chain="optimism", max_slippage=Decimal("0.005"), )

### Build the deposit transaction

tx = adapter.build_deposit_tx(quote, recipient="0x...")

### AcrossBridgeAdapter

```
AcrossBridgeAdapter(
    config: AcrossConfig | None = None,
    token_resolver: TokenResolver | None = None,
)
```

Bases: `BridgeAdapter`

Across Protocol bridge adapter implementation.

Provides integration with the Across bridge for fast cross-chain transfers using relayers and optimistic verification.

Features:

- Fast finality via relayer network
- Competitive fees through relayer competition
- Support for ETH, USDC, WBTC and other major tokens
- Multi-chain support (Ethereum, Arbitrum, Optimism, Base, Polygon)

Example

adapter = AcrossBridgeAdapter()

#### Get quote

quote = adapter.get_quote( token="USDC", amount=Decimal("1000"), from_chain="arbitrum", to_chain="optimism", )

#### Build transaction

tx = adapter.build_deposit_tx(quote, "0xRecipient...")

#### Check status after deposit

status = adapter.check_status(deposit_tx_hash)

Initialize Across bridge adapter.

Parameters:

| Name             | Type            | Description | Default                                                   |
| ---------------- | --------------- | ----------- | --------------------------------------------------------- |
| `config`         | \`AcrossConfig  | None\`      | Optional configuration. Uses defaults if not provided.    |
| `token_resolver` | \`TokenResolver | None\`      | Optional TokenResolver instance. If None, uses singleton. |

#### name

```
name: str
```

Get bridge adapter name.

#### supported_tokens

```
supported_tokens: list[str]
```

Get list of supported tokens.

#### supported_routes

```
supported_routes: list[BridgeRoute]
```

Get list of supported bridge routes.

#### get_quote

```
get_quote(
    token: str,
    amount: Decimal,
    from_chain: str,
    to_chain: str,
    max_slippage: Decimal = Decimal("0.005"),
) -> BridgeQuote
```

Get a quote for bridging tokens via Across.

Calls the Across API to get current fee and time estimates for the specified transfer.

Parameters:

| Name           | Type      | Description                                 | Default            |
| -------------- | --------- | ------------------------------------------- | ------------------ |
| `token`        | `str`     | Token symbol (e.g., "USDC", "ETH", "WBTC")  | *required*         |
| `amount`       | `Decimal` | Amount to bridge in token units             | *required*         |
| `from_chain`   | `str`     | Source chain (e.g., "arbitrum", "optimism") | *required*         |
| `to_chain`     | `str`     | Destination chain                           | *required*         |
| `max_slippage` | `Decimal` | Maximum slippage tolerance (default 0.5%)   | `Decimal('0.005')` |

Returns:

| Type          | Description                                         |
| ------------- | --------------------------------------------------- |
| `BridgeQuote` | BridgeQuote with fee, timing, and route information |

Raises:

| Type               | Description                  |
| ------------------ | ---------------------------- |
| `AcrossQuoteError` | If quote cannot be retrieved |

#### build_deposit_tx

```
build_deposit_tx(
    quote: BridgeQuote, recipient: str
) -> dict[str, Any]
```

Build the deposit transaction for an Across bridge transfer.

Creates the transaction data to call depositV3() on the SpokePool contract.

Parameters:

| Name        | Type          | Description                                    | Default    |
| ----------- | ------------- | ---------------------------------------------- | ---------- |
| `quote`     | `BridgeQuote` | BridgeQuote from get_quote()                   | *required* |
| `recipient` | `str`         | Address to receive tokens on destination chain | *required* |

Returns:

| Type             | Description                                             |
| ---------------- | ------------------------------------------------------- |
| `dict[str, Any]` | Transaction data dict with 'to', 'value', 'data' fields |

Raises:

| Type                     | Description                    |
| ------------------------ | ------------------------------ |
| `AcrossTransactionError` | If transaction cannot be built |

#### check_status

```
check_status(bridge_deposit_id: str) -> BridgeStatus
```

Check the status of an Across bridge transfer.

Polls the Across API to check if a deposit has been filled on the destination chain.

Parameters:

| Name                | Type  | Description                           | Default    |
| ------------------- | ----- | ------------------------------------- | ---------- |
| `bridge_deposit_id` | `str` | Source chain deposit transaction hash | *required* |

Returns:

| Type           | Description                               |
| -------------- | ----------------------------------------- |
| `BridgeStatus` | BridgeStatus with current transfer status |

Raises:

| Type                | Description                   |
| ------------------- | ----------------------------- |
| `AcrossStatusError` | If status cannot be retrieved |

#### estimate_completion_time

```
estimate_completion_time(
    from_chain: str, to_chain: str
) -> int
```

Estimate completion time for a route.

Returns typical completion time based on historical data and relayer network activity.

Parameters:

| Name         | Type  | Description                  | Default    |
| ------------ | ----- | ---------------------------- | ---------- |
| `from_chain` | `str` | Source chain identifier      | *required* |
| `to_chain`   | `str` | Destination chain identifier | *required* |

Returns:

| Type  | Description                          |
| ----- | ------------------------------------ |
| `int` | Estimated completion time in seconds |

Raises:

| Type          | Description               |
| ------------- | ------------------------- |
| `AcrossError` | If route is not supported |

### AcrossConfig

```
AcrossConfig(
    api_base_url: str = "https://app.across.to/api",
    timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS,
    request_timeout: int = 30,
    max_retries: int = 3,
)
```

Configuration for Across bridge adapter.

Attributes:

| Name              | Type  | Description                                    |
| ----------------- | ----- | ---------------------------------------------- |
| `api_base_url`    | `str` | Across API base URL                            |
| `timeout_seconds` | `int` | Timeout for bridge operations (default 30 min) |
| `request_timeout` | `int` | HTTP request timeout in seconds                |
| `max_retries`     | `int` | Maximum number of retry attempts for API calls |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

### AcrossError

Bases: `BridgeError`

Base exception for Across-related errors.

### AcrossQuoteError

Bases: `AcrossError`, `BridgeQuoteError`

Error when retrieving an Across quote.

### AcrossStatusError

Bases: `AcrossError`, `BridgeStatusError`

Error when checking Across transfer status.

### AcrossTransactionError

Bases: `AcrossError`, `BridgeTransactionError`

Error when building or submitting an Across transaction.

### BridgeAdapter

Bases: `ABC`

Abstract base class for bridge protocol adapters.

All bridge adapters must implement this interface to provide a consistent API for cross-chain asset transfers.

Bridge adapters handle:

1. Quote retrieval - Get fee and time estimates for a transfer
1. Transaction building - Build the deposit transaction
1. Status tracking - Poll for transfer completion
1. Time estimation - Estimate completion times for routes

Example implementation

class AcrossBridgeAdapter(BridgeAdapter): @property def name(self) -> str: return "Across"

```
@property
def supported_tokens(self) -> list[str]:
    return ["ETH", "USDC", "WBTC"]

def get_quote(self, token, amount, from_chain, to_chain, max_slippage):
    # Call Across API for quote
    pass

def build_deposit_tx(self, quote, recipient):
    # Build deposit transaction
    pass
```

#### name

```
name: str
```

Get the bridge adapter name.

Returns:

| Type  | Description                                                    |
| ----- | -------------------------------------------------------------- |
| `str` | Human-readable name of the bridge (e.g., "Across", "Stargate") |

#### supported_tokens

```
supported_tokens: list[str]
```

Get list of supported tokens.

Returns:

| Type        | Description                                    |
| ----------- | ---------------------------------------------- |
| `list[str]` | List of token symbols supported by this bridge |
| `list[str]` | (e.g., ["ETH", "USDC", "WBTC"])                |

#### supported_routes

```
supported_routes: list[BridgeRoute]
```

Get list of supported bridge routes.

Returns:

| Type                | Description                                        |
| ------------------- | -------------------------------------------------- |
| `list[BridgeRoute]` | List of BridgeRoute objects describing supported   |
| `list[BridgeRoute]` | chain-to-chain routes with their tokens and limits |

#### get_quote

```
get_quote(
    token: str,
    amount: Decimal,
    from_chain: str,
    to_chain: str,
    max_slippage: Decimal = Decimal("0.005"),
) -> BridgeQuote
```

Get a quote for bridging tokens.

Retrieves fee and timing information for a potential bridge transfer. The quote contains all information needed to execute the transfer.

Parameters:

| Name           | Type      | Description                                                           | Default            |
| -------------- | --------- | --------------------------------------------------------------------- | ------------------ |
| `token`        | `str`     | Token symbol to bridge (e.g., "ETH", "USDC")                          | *required*         |
| `amount`       | `Decimal` | Amount to bridge in token units                                       | *required*         |
| `from_chain`   | `str`     | Source chain identifier (e.g., "arbitrum", "optimism")                | *required*         |
| `to_chain`     | `str`     | Destination chain identifier                                          | *required*         |
| `max_slippage` | `Decimal` | Maximum slippage tolerance as decimal (e.g., 0.005 = 0.5%, 0.01 = 1%) | `Decimal('0.005')` |

Returns:

| Type          | Description                                         |
| ------------- | --------------------------------------------------- |
| `BridgeQuote` | BridgeQuote with fee, timing, and route information |

Raises:

| Type               | Description                                                                            |
| ------------------ | -------------------------------------------------------------------------------------- |
| `BridgeQuoteError` | If quote cannot be retrieved (unsupported route, amount out of range, API error, etc.) |

#### build_deposit_tx

```
build_deposit_tx(
    quote: BridgeQuote, recipient: str
) -> dict[str, Any]
```

Build the deposit transaction for a bridge transfer.

Creates the transaction data needed to initiate the bridge transfer on the source chain.

Parameters:

| Name        | Type          | Description                                    | Default    |
| ----------- | ------------- | ---------------------------------------------- | ---------- |
| `quote`     | `BridgeQuote` | BridgeQuote from get_quote()                   | *required* |
| `recipient` | `str`         | Address to receive tokens on destination chain | *required* |

Returns:

| Type             | Description                                                                                                                           |
| ---------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `dict[str, Any]` | Transaction data dict with: - to: Contract address to call - value: ETH value to send (for native transfers) - data: Encoded calldata |

Raises:

| Type                     | Description                                                             |
| ------------------------ | ----------------------------------------------------------------------- |
| `BridgeTransactionError` | If transaction cannot be built (quote expired, invalid recipient, etc.) |

#### check_status

```
check_status(bridge_deposit_id: str) -> BridgeStatus
```

Check the status of a bridge transfer.

Polls the bridge for the current status of an in-flight transfer.

Parameters:

| Name                | Type  | Description                                                                                           | Default    |
| ------------------- | ----- | ----------------------------------------------------------------------------------------------------- | ---------- |
| `bridge_deposit_id` | `str` | Bridge-specific deposit identifier (returned from deposit transaction or derived from source tx hash) | *required* |

Returns:

| Type           | Description                                                       |
| -------------- | ----------------------------------------------------------------- |
| `BridgeStatus` | BridgeStatus with current transfer status and transaction details |

Raises:

| Type                | Description                                                         |
| ------------------- | ------------------------------------------------------------------- |
| `BridgeStatusError` | If status cannot be retrieved (unknown deposit ID, API error, etc.) |

#### estimate_completion_time

```
estimate_completion_time(
    from_chain: str, to_chain: str
) -> int
```

Estimate completion time for a route.

Returns the typical completion time in seconds for a bridge transfer between two chains.

Parameters:

| Name         | Type  | Description                  | Default    |
| ------------ | ----- | ---------------------------- | ---------- |
| `from_chain` | `str` | Source chain identifier      | *required* |
| `to_chain`   | `str` | Destination chain identifier | *required* |

Returns:

| Type  | Description                          |
| ----- | ------------------------------------ |
| `int` | Estimated completion time in seconds |

Raises:

| Type          | Description               |
| ------------- | ------------------------- |
| `BridgeError` | If route is not supported |

#### supports_token

```
supports_token(token: str) -> bool
```

Check if bridge supports a token.

Parameters:

| Name    | Type  | Description           | Default    |
| ------- | ----- | --------------------- | ---------- |
| `token` | `str` | Token symbol to check | *required* |

Returns:

| Type   | Description                |
| ------ | -------------------------- |
| `bool` | True if token is supported |

#### supports_route

```
supports_route(from_chain: str, to_chain: str) -> bool
```

Check if bridge supports a route.

Parameters:

| Name         | Type  | Description                  | Default    |
| ------------ | ----- | ---------------------------- | ---------- |
| `from_chain` | `str` | Source chain identifier      | *required* |
| `to_chain`   | `str` | Destination chain identifier | *required* |

Returns:

| Type   | Description                |
| ------ | -------------------------- |
| `bool` | True if route is supported |

#### get_route

```
get_route(
    from_chain: str, to_chain: str
) -> BridgeRoute | None
```

Get route information.

Parameters:

| Name         | Type  | Description                  | Default    |
| ------------ | ----- | ---------------------------- | ---------- |
| `from_chain` | `str` | Source chain identifier      | *required* |
| `to_chain`   | `str` | Destination chain identifier | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`BridgeRoute | None\`      |

#### validate_transfer

```
validate_transfer(
    token: str,
    amount: Decimal,
    from_chain: str,
    to_chain: str,
) -> tuple[bool, str | None]
```

Validate a transfer before getting a quote.

Parameters:

| Name         | Type      | Description       | Default    |
| ------------ | --------- | ----------------- | ---------- |
| `token`      | `str`     | Token symbol      | *required* |
| `amount`     | `Decimal` | Amount to bridge  | *required* |
| `from_chain` | `str`     | Source chain      | *required* |
| `to_chain`   | `str`     | Destination chain | *required* |

Returns:

| Type               | Description |
| ------------------ | ----------- |
| \`tuple\[bool, str | None\]\`    |

### BridgeError

Bases: `Exception`

Base exception for bridge-related errors.

### BridgeQuote

```
BridgeQuote(
    bridge_name: str,
    token: str,
    input_amount: Decimal,
    output_amount: Decimal,
    from_chain: str,
    to_chain: str,
    fee_amount: Decimal,
    fee_usd: Decimal | None = None,
    gas_fee_amount: Decimal = Decimal("0"),
    relayer_fee_amount: Decimal = Decimal("0"),
    estimated_time_seconds: int = 300,
    quote_timestamp: datetime = (
        lambda: datetime.now(UTC)
    )(),
    expires_at: datetime | None = None,
    slippage_tolerance: Decimal = Decimal("0.005"),
    route_data: dict[str, Any] = dict(),
    quote_id: str | None = None,
)
```

Quote for a bridge transfer.

Contains all information needed to execute a bridge transfer, including fees, timing, and the resulting amount on destination.

Attributes:

| Name                     | Type             | Description                                      |
| ------------------------ | ---------------- | ------------------------------------------------ |
| `bridge_name`            | `str`            | Name of the bridge providing this quote          |
| `token`                  | `str`            | Token being bridged                              |
| `input_amount`           | `Decimal`        | Amount being sent from source chain              |
| `output_amount`          | `Decimal`        | Expected amount on destination (after fees)      |
| `from_chain`             | `str`            | Source chain identifier                          |
| `to_chain`               | `str`            | Destination chain identifier                     |
| `fee_amount`             | `Decimal`        | Total fee in token units                         |
| `fee_usd`                | \`Decimal        | None\`                                           |
| `gas_fee_amount`         | `Decimal`        | Gas fee portion (in native token)                |
| `relayer_fee_amount`     | `Decimal`        | Relayer/protocol fee portion (in bridged token)  |
| `estimated_time_seconds` | `int`            | Estimated completion time in seconds             |
| `quote_timestamp`        | `datetime`       | When quote was generated                         |
| `expires_at`             | \`datetime       | None\`                                           |
| `slippage_tolerance`     | `Decimal`        | Maximum slippage as decimal (e.g., 0.005 = 0.5%) |
| `route_data`             | `dict[str, Any]` | Bridge-specific route information                |
| `quote_id`               | \`str            | None\`                                           |

#### fee_percentage

```
fee_percentage: Decimal
```

Get fee as percentage of input amount.

#### is_expired

```
is_expired: bool
```

Check if quote has expired.

#### time_until_expiry

```
time_until_expiry: timedelta | None
```

Get time until quote expires.

#### estimated_completion_time

```
estimated_completion_time: datetime
```

Get estimated completion timestamp.

#### __post_init__

```
__post_init__() -> None
```

Set default expiration if not provided.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### BridgeQuoteError

Bases: `BridgeError`

Error when retrieving a bridge quote.

### BridgeRoute

```
BridgeRoute(
    from_chain: str,
    to_chain: str,
    tokens: list[str] = list(),
    min_amount: Decimal = Decimal("0"),
    max_amount: Decimal = Decimal("0"),
    estimated_time_seconds: int = 300,
    is_active: bool = True,
)
```

Represents a bridge route between two chains.

Attributes:

| Name                     | Type        | Description                              |
| ------------------------ | ----------- | ---------------------------------------- |
| `from_chain`             | `str`       | Source chain identifier                  |
| `to_chain`               | `str`       | Destination chain identifier             |
| `tokens`                 | `list[str]` | List of tokens supported on this route   |
| `min_amount`             | `Decimal`   | Minimum transfer amount (in token units) |
| `max_amount`             | `Decimal`   | Maximum transfer amount (in token units) |
| `estimated_time_seconds` | `int`       | Typical completion time                  |
| `is_active`              | `bool`      | Whether route is currently active        |

#### supports_token

```
supports_token(token: str) -> bool
```

Check if route supports a specific token.

Parameters:

| Name    | Type  | Description           | Default    |
| ------- | ----- | --------------------- | ---------- |
| `token` | `str` | Token symbol to check | *required* |

Returns:

| Type   | Description                              |
| ------ | ---------------------------------------- |
| `bool` | True if token is supported on this route |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### BridgeStatus

```
BridgeStatus(
    bridge_name: str,
    bridge_deposit_id: str,
    status: BridgeStatusEnum,
    from_chain: str,
    to_chain: str,
    token: str,
    input_amount: Decimal,
    output_amount: Decimal | None = None,
    source_tx_hash: str | None = None,
    destination_tx_hash: str | None = None,
    deposited_at: datetime | None = None,
    filled_at: datetime | None = None,
    completed_at: datetime | None = None,
    error_message: str | None = None,
    relay_id: str | None = None,
    fill_deadline: datetime | None = None,
)
```

Status of a bridge transfer.

Tracks the progress of an in-flight bridge transfer including source and destination chain transaction details.

Attributes:

| Name                  | Type               | Description                        |
| --------------------- | ------------------ | ---------------------------------- |
| `bridge_name`         | `str`              | Name of the bridge                 |
| `bridge_deposit_id`   | `str`              | Bridge-specific deposit identifier |
| `status`              | `BridgeStatusEnum` | Current status of the transfer     |
| `from_chain`          | `str`              | Source chain identifier            |
| `to_chain`            | `str`              | Destination chain identifier       |
| `token`               | `str`              | Token being bridged                |
| `input_amount`        | `Decimal`          | Amount sent from source chain      |
| `output_amount`       | \`Decimal          | None\`                             |
| `source_tx_hash`      | \`str              | None\`                             |
| `destination_tx_hash` | \`str              | None\`                             |
| `deposited_at`        | \`datetime         | None\`                             |
| `filled_at`           | \`datetime         | None\`                             |
| `completed_at`        | \`datetime         | None\`                             |
| `error_message`       | \`str              | None\`                             |
| `relay_id`            | \`str              | None\`                             |
| `fill_deadline`       | \`datetime         | None\`                             |

#### is_complete

```
is_complete: bool
```

Check if transfer is complete (success or failure).

#### is_success

```
is_success: bool
```

Check if transfer completed successfully.

#### is_pending

```
is_pending: bool
```

Check if transfer is still in progress.

#### elapsed_time

```
elapsed_time: timedelta | None
```

Get elapsed time since deposit.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### BridgeStatusEnum

Bases: `Enum`

Status of a bridge transfer.

States

PENDING: Transfer initiated but not yet detected on source chain DEPOSITED: Deposit confirmed on source chain IN_FLIGHT: Transfer in progress (relaying/bridging) FILLED: Destination chain credit detected, awaiting confirmations COMPLETED: Transfer fully completed and confirmed FAILED: Transfer failed (may need manual intervention) EXPIRED: Quote expired before execution REFUNDED: Transfer refunded on source chain

### BridgeStatusError

Bases: `BridgeError`

Error when checking bridge transfer status.

### BridgeTransactionError

Bases: `BridgeError`

Error when building or submitting a bridge transaction.

### BridgeScore

```
BridgeScore(
    bridge: BridgeAdapter,
    quote: BridgeQuote | None = None,
    cost_score: float = 1.0,
    speed_score: float = 1.0,
    liquidity_score: float = 1.0,
    reliability_score: float = 1.0,
    overall_score: float = 1.0,
    is_available: bool = False,
    unavailable_reason: str | None = None,
)
```

Score for a bridge based on selection criteria.

Attributes:

| Name                 | Type            | Description                                         |
| -------------------- | --------------- | --------------------------------------------------- |
| `bridge`             | `BridgeAdapter` | The bridge adapter being scored                     |
| `quote`              | \`BridgeQuote   | None\`                                              |
| `cost_score`         | `float`         | Normalized cost score (0-1, lower is better)        |
| `speed_score`        | `float`         | Normalized speed score (0-1, lower is better)       |
| `liquidity_score`    | `float`         | Normalized liquidity score (0-1, lower is better)   |
| `reliability_score`  | `float`         | Normalized reliability score (0-1, lower is better) |
| `overall_score`      | `float`         | Weighted overall score                              |
| `is_available`       | `bool`          | Whether bridge can fulfill request                  |
| `unavailable_reason` | \`str           | None\`                                              |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### BridgeSelectionResult

```
BridgeSelectionResult(
    bridge: BridgeAdapter | None = None,
    quote: BridgeQuote | None = None,
    scores: list[BridgeScore] = list(),
    selection_reasoning: str = "",
)
```

Result of bridge selection.

Attributes:

| Name                  | Type                | Description                             |
| --------------------- | ------------------- | --------------------------------------- |
| `bridge`              | \`BridgeAdapter     | None\`                                  |
| `quote`               | \`BridgeQuote       | None\`                                  |
| `scores`              | `list[BridgeScore]` | Scores for all evaluated bridges        |
| `selection_reasoning` | `str`               | Human-readable explanation of selection |

#### is_success

```
is_success: bool
```

Check if a bridge was successfully selected.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### BridgeSelector

```
BridgeSelector(
    bridges: list[BridgeAdapter],
    reliability_scores: dict[str, float] | None = None,
    default_priority: SelectionPriority = SelectionPriority.COST,
)
```

Selects optimal bridge for cross-chain transfers.

The selector evaluates all registered bridge adapters and selects the best one based on configurable priority criteria.

Attributes:

| Name                 | Type | Description                              |
| -------------------- | ---- | ---------------------------------------- |
| `bridges`            |      | List of registered bridge adapters       |
| `reliability_scores` |      | Historical reliability scores per bridge |
| `default_priority`   |      | Default selection priority               |

Example

selector = BridgeSelector([ AcrossBridgeAdapter(), StargateBridgeAdapter(), ])

result = selector.select_bridge( token="USDC", amount=Decimal("1000"), from_chain="arbitrum", to_chain="optimism", priority="cost", )

Initialize the bridge selector.

Parameters:

| Name                 | Type                  | Description                         | Default                                            |
| -------------------- | --------------------- | ----------------------------------- | -------------------------------------------------- |
| `bridges`            | `list[BridgeAdapter]` | List of bridge adapters to evaluate | *required*                                         |
| `reliability_scores` | \`dict[str, float]    | None\`                              | Optional custom reliability scores per bridge name |
| `default_priority`   | `SelectionPriority`   | Default selection priority          | `COST`                                             |

#### select_bridge

```
select_bridge(
    token: str,
    amount: Decimal,
    from_chain: str,
    to_chain: str,
    priority: str = "cost",
    max_slippage: Decimal = Decimal("0.005"),
) -> BridgeSelectionResult
```

Select the optimal bridge for a transfer.

Evaluates all registered bridges for the given route and returns the best one based on the priority criteria.

Parameters:

| Name           | Type      | Description                                                      | Default            |
| -------------- | --------- | ---------------------------------------------------------------- | ------------------ |
| `token`        | `str`     | Token symbol to bridge (e.g., "ETH", "USDC")                     | *required*         |
| `amount`       | `Decimal` | Amount to bridge in token units                                  | *required*         |
| `from_chain`   | `str`     | Source chain identifier                                          | *required*         |
| `to_chain`     | `str`     | Destination chain identifier                                     | *required*         |
| `priority`     | `str`     | Selection priority ("cost", "speed", "liquidity", "reliability") | `'cost'`           |
| `max_slippage` | `Decimal` | Maximum slippage tolerance                                       | `Decimal('0.005')` |

Returns:

| Type                    | Description                                          |
| ----------------------- | ---------------------------------------------------- |
| `BridgeSelectionResult` | BridgeSelectionResult with selected bridge and quote |

Raises:

| Type                     | Description                          |
| ------------------------ | ------------------------------------ |
| `NoBridgeAvailableError` | If no bridge can fulfill the request |

#### get_available_bridges

```
get_available_bridges(
    token: str, from_chain: str, to_chain: str
) -> list[BridgeAdapter]
```

Get list of bridges that support a route.

Parameters:

| Name         | Type  | Description       | Default    |
| ------------ | ----- | ----------------- | ---------- |
| `token`      | `str` | Token symbol      | *required* |
| `from_chain` | `str` | Source chain      | *required* |
| `to_chain`   | `str` | Destination chain | *required* |

Returns:

| Type                  | Description                                    |
| --------------------- | ---------------------------------------------- |
| `list[BridgeAdapter]` | List of bridge adapters that support the route |

#### select_bridge_with_fallback

```
select_bridge_with_fallback(
    token: str,
    amount: Decimal,
    from_chain: str,
    to_chain: str,
    priority: str = "cost",
    max_slippage: Decimal = Decimal("0.005"),
    excluded_bridges: list[str] | None = None,
) -> BridgeSelectionResult
```

Select bridge with automatic fallback if primary fails.

If the primary selection fails for any reason, attempts to use the next best bridge as a fallback.

Parameters:

| Name               | Type        | Description                | Default                                        |
| ------------------ | ----------- | -------------------------- | ---------------------------------------------- |
| `token`            | `str`       | Token symbol to bridge     | *required*                                     |
| `amount`           | `Decimal`   | Amount to bridge           | *required*                                     |
| `from_chain`       | `str`       | Source chain               | *required*                                     |
| `to_chain`         | `str`       | Destination chain          | *required*                                     |
| `priority`         | `str`       | Selection priority         | `'cost'`                                       |
| `max_slippage`     | `Decimal`   | Maximum slippage tolerance | `Decimal('0.005')`                             |
| `excluded_bridges` | \`list[str] | None\`                     | List of bridge names to exclude from selection |

Returns:

| Type                    | Description                                |
| ----------------------- | ------------------------------------------ |
| `BridgeSelectionResult` | BridgeSelectionResult with selected bridge |

Raises:

| Type                     | Description                                  |
| ------------------------ | -------------------------------------------- |
| `NoBridgeAvailableError` | If no bridge (including fallbacks) available |

### BridgeSelectorError

Bases: `BridgeError`

Base exception for bridge selector errors.

### NoBridgeAvailableError

Bases: `BridgeSelectorError`

Raised when no bridge can fulfill the request.

### SelectionPriority

Bases: `Enum`

Priority for bridge selection.

Attributes:

| Name          | Type | Description                  |
| ------------- | ---- | ---------------------------- |
| `COST`        |      | Minimize total fees          |
| `SPEED`       |      | Minimize completion time     |
| `LIQUIDITY`   |      | Prefer deeper liquidity      |
| `RELIABILITY` |      | Prefer more reliable bridges |

### StargateBridgeAdapter

```
StargateBridgeAdapter(
    config: StargateConfig | None = None,
    token_resolver: TokenResolver | None = None,
)
```

Bases: `BridgeAdapter`

Stargate Protocol bridge adapter implementation.

Provides integration with the Stargate bridge for cross-chain transfers using LayerZero messaging infrastructure.

Features:

- Unified liquidity pools for efficient capital utilization
- Instant guaranteed finality via LayerZero messaging
- Native asset transfers without wrapped tokens
- Support for USDC, USDT, ETH across major chains

Example

adapter = StargateBridgeAdapter()

#### Get quote

quote = adapter.get_quote( token="USDC", amount=Decimal("1000"), from_chain="arbitrum", to_chain="optimism", )

#### Build transaction

tx = adapter.build_deposit_tx(quote, "0xRecipient...")

#### Check status after deposit

status = adapter.check_status(deposit_tx_hash)

Initialize Stargate bridge adapter.

Parameters:

| Name             | Type             | Description | Default                                                   |
| ---------------- | ---------------- | ----------- | --------------------------------------------------------- |
| `config`         | \`StargateConfig | None\`      | Optional configuration. Uses defaults if not provided.    |
| `token_resolver` | \`TokenResolver  | None\`      | Optional TokenResolver instance. If None, uses singleton. |

#### name

```
name: str
```

Get bridge adapter name.

#### supported_tokens

```
supported_tokens: list[str]
```

Get list of supported tokens.

#### supported_routes

```
supported_routes: list[BridgeRoute]
```

Get list of supported bridge routes.

#### get_quote

```
get_quote(
    token: str,
    amount: Decimal,
    from_chain: str,
    to_chain: str,
    max_slippage: Decimal = Decimal("0.005"),
) -> BridgeQuote
```

Get a quote for bridging tokens via Stargate.

Calculates fees including LayerZero messaging fees and protocol fees for the specified transfer.

Parameters:

| Name           | Type      | Description                                 | Default            |
| -------------- | --------- | ------------------------------------------- | ------------------ |
| `token`        | `str`     | Token symbol (e.g., "USDC", "USDT", "ETH")  | *required*         |
| `amount`       | `Decimal` | Amount to bridge in token units             | *required*         |
| `from_chain`   | `str`     | Source chain (e.g., "arbitrum", "optimism") | *required*         |
| `to_chain`     | `str`     | Destination chain                           | *required*         |
| `max_slippage` | `Decimal` | Maximum slippage tolerance (default 0.5%)   | `Decimal('0.005')` |

Returns:

| Type          | Description                                         |
| ------------- | --------------------------------------------------- |
| `BridgeQuote` | BridgeQuote with fee, timing, and route information |

Raises:

| Type                 | Description                  |
| -------------------- | ---------------------------- |
| `StargateQuoteError` | If quote cannot be retrieved |

#### build_deposit_tx

```
build_deposit_tx(
    quote: BridgeQuote, recipient: str
) -> dict[str, Any]
```

Build the deposit transaction for a Stargate bridge transfer.

Creates the transaction data to call send() on the Stargate OFT/Pool contract.

Parameters:

| Name        | Type          | Description                                    | Default    |
| ----------- | ------------- | ---------------------------------------------- | ---------- |
| `quote`     | `BridgeQuote` | BridgeQuote from get_quote()                   | *required* |
| `recipient` | `str`         | Address to receive tokens on destination chain | *required* |

Returns:

| Type             | Description                                             |
| ---------------- | ------------------------------------------------------- |
| `dict[str, Any]` | Transaction data dict with 'to', 'value', 'data' fields |

Raises:

| Type                       | Description                    |
| -------------------------- | ------------------------------ |
| `StargateTransactionError` | If transaction cannot be built |

#### check_status

```
check_status(bridge_deposit_id: str) -> BridgeStatus
```

Check the status of a Stargate bridge transfer.

Polls the LayerZero scan API to check if the cross-chain message has been delivered on the destination chain.

Parameters:

| Name                | Type  | Description                           | Default    |
| ------------------- | ----- | ------------------------------------- | ---------- |
| `bridge_deposit_id` | `str` | Source chain deposit transaction hash | *required* |

Returns:

| Type           | Description                               |
| -------------- | ----------------------------------------- |
| `BridgeStatus` | BridgeStatus with current transfer status |

Raises:

| Type                  | Description                   |
| --------------------- | ----------------------------- |
| `StargateStatusError` | If status cannot be retrieved |

#### estimate_completion_time

```
estimate_completion_time(
    from_chain: str, to_chain: str
) -> int
```

Estimate completion time for a route.

Returns typical completion time based on LayerZero messaging and chain finality requirements.

Parameters:

| Name         | Type  | Description                  | Default    |
| ------------ | ----- | ---------------------------- | ---------- |
| `from_chain` | `str` | Source chain identifier      | *required* |
| `to_chain`   | `str` | Destination chain identifier | *required* |

Returns:

| Type  | Description                          |
| ----- | ------------------------------------ |
| `int` | Estimated completion time in seconds |

Raises:

| Type            | Description               |
| --------------- | ------------------------- |
| `StargateError` | If route is not supported |

### StargateConfig

```
StargateConfig(
    api_base_url: str = "https://api.stargate.finance/v1",
    layerzero_scan_url: str = "https://api.layerzeroscan.com",
    timeout_seconds: int = DEFAULT_TIMEOUT_SECONDS,
    request_timeout: int = 30,
    max_retries: int = 3,
)
```

Configuration for Stargate bridge adapter.

Attributes:

| Name                 | Type  | Description                                    |
| -------------------- | ----- | ---------------------------------------------- |
| `api_base_url`       | `str` | Stargate API base URL                          |
| `layerzero_scan_url` | `str` | LayerZero scan API URL for message tracking    |
| `timeout_seconds`    | `int` | Timeout for bridge operations (default 30 min) |
| `request_timeout`    | `int` | HTTP request timeout in seconds                |
| `max_retries`        | `int` | Maximum number of retry attempts for API calls |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

### StargateError

Bases: `BridgeError`

Base exception for Stargate-related errors.

### StargateQuoteError

Bases: `StargateError`, `BridgeQuoteError`

Error when retrieving a Stargate quote.

### StargateStatusError

Bases: `StargateError`, `BridgeStatusError`

Error when checking Stargate transfer status.

### StargateTransactionError

Bases: `StargateError`, `BridgeTransactionError`

Error when building or submitting a Stargate transaction.

# Compound V3

Connector for Compound V3 lending protocol.

## almanak.framework.connectors.compound_v3

Compound V3 (Comet) Connector.

This module provides adapters and utilities for interacting with Compound V3, a lending protocol with single borrowable assets and multiple collateral options.

Compound V3 Features:

- Single borrowable asset (base) per market (USDC, WETH, USDT)
- Multiple collateral assets per market
- No cTokens for collateral (only base asset is tokenized)
- Simplified interest rate model
- Efficient liquidation mechanism

Supported Chains:

- Ethereum
- Arbitrum

Example

from almanak.framework.connectors.compound_v3 import ( CompoundV3Adapter, CompoundV3Config, CompoundV3ReceiptParser, )

### Initialize adapter

config = CompoundV3Config( chain="ethereum", wallet_address="0x...", market="usdc", ) adapter = CompoundV3Adapter(config)

### Get market info

market_info = adapter.get_market_info() print(f"Market: {market_info.name}")

### Build a supply transaction

result = adapter.supply( amount=Decimal("1000"), )

### Parse transaction receipts

parser = CompoundV3ReceiptParser() events = parser.parse_receipt(receipt)

### CompoundV3Adapter

```
CompoundV3Adapter(
    config: CompoundV3Config,
    price_oracle: PriceOracle | None = None,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Compound V3 (Comet) lending protocol.

This adapter provides methods for interacting with Compound V3:

- Supply/withdraw base assets (lending)
- Supply/withdraw collateral assets
- Borrow/repay base assets
- Health factor calculations

Compound V3 uses a single borrowable asset (base) per market with multiple collateral options. Unlike traditional Compound, collateral does not earn interest - only base asset suppliers earn yield.

Example

config = CompoundV3Config( chain="ethereum", wallet_address="0x...", market="usdc", ) adapter = CompoundV3Adapter(config)

#### Supply base asset to earn interest

result = adapter.supply(amount=Decimal("1000"))

#### Supply collateral for borrowing

result = adapter.supply_collateral(asset="WETH", amount=Decimal("1.0"))

#### Borrow against collateral

result = adapter.borrow(amount=Decimal("500"))

Initialize the adapter.

Parameters:

| Name             | Type               | Description           | Default                                                 |
| ---------------- | ------------------ | --------------------- | ------------------------------------------------------- |
| `config`         | `CompoundV3Config` | Adapter configuration | *required*                                              |
| `price_oracle`   | \`PriceOracle      | None\`                | Optional price oracle callback                          |
| `token_resolver` | \`TokenResolver    | None\`                | Optional TokenResolver instance (defaults to singleton) |

#### supply

```
supply(
    amount: Decimal, on_behalf_of: str | None = None
) -> TransactionResult
```

Build a supply transaction for the base asset.

Supplies the base asset (e.g., USDC) to earn interest.

Parameters:

| Name           | Type      | Description                    | Default                                        |
| -------------- | --------- | ------------------------------ | ---------------------------------------------- |
| `amount`       | `Decimal` | Amount of base token to supply | *required*                                     |
| `on_behalf_of` | \`str     | None\`                         | Address to credit (defaults to wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### withdraw

```
withdraw(
    amount: Decimal,
    receiver: str | None = None,
    withdraw_all: bool = False,
) -> TransactionResult
```

Build a withdraw transaction for the base asset.

Withdraws supplied base asset from the market.

Parameters:

| Name           | Type      | Description                                | Default                                                |
| -------------- | --------- | ------------------------------------------ | ------------------------------------------------------ |
| `amount`       | `Decimal` | Amount of base token to withdraw           | *required*                                             |
| `receiver`     | \`str     | None\`                                     | Address to receive tokens (defaults to wallet_address) |
| `withdraw_all` | `bool`    | If True, withdraws all supplied base asset | `False`                                                |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### supply_collateral

```
supply_collateral(
    asset: str,
    amount: Decimal,
    on_behalf_of: str | None = None,
) -> TransactionResult
```

Build a supply collateral transaction.

Supplies collateral to enable borrowing.

Parameters:

| Name           | Type      | Description                            | Default                                        |
| -------------- | --------- | -------------------------------------- | ---------------------------------------------- |
| `asset`        | `str`     | Collateral asset symbol (e.g., "WETH") | *required*                                     |
| `amount`       | `Decimal` | Amount of collateral to supply         | *required*                                     |
| `on_behalf_of` | \`str     | None\`                                 | Address to credit (defaults to wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### withdraw_collateral

```
withdraw_collateral(
    asset: str,
    amount: Decimal,
    receiver: str | None = None,
    withdraw_all: bool = False,
) -> TransactionResult
```

Build a withdraw collateral transaction.

Withdraws collateral from the market.

Parameters:

| Name           | Type      | Description                                      | Default                                                |
| -------------- | --------- | ------------------------------------------------ | ------------------------------------------------------ |
| `asset`        | `str`     | Collateral asset symbol (e.g., "WETH")           | *required*                                             |
| `amount`       | `Decimal` | Amount of collateral to withdraw                 | *required*                                             |
| `receiver`     | \`str     | None\`                                           | Address to receive tokens (defaults to wallet_address) |
| `withdraw_all` | `bool`    | If True, withdraws all collateral for this asset | `False`                                                |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### borrow

```
borrow(
    amount: Decimal, receiver: str | None = None
) -> TransactionResult
```

Build a borrow transaction.

Borrows the base asset against supplied collateral.

Parameters:

| Name       | Type      | Description                    | Default                                                         |
| ---------- | --------- | ------------------------------ | --------------------------------------------------------------- |
| `amount`   | `Decimal` | Amount of base token to borrow | *required*                                                      |
| `receiver` | \`str     | None\`                         | Address to receive borrowed tokens (defaults to wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### repay

```
repay(
    amount: Decimal,
    on_behalf_of: str | None = None,
    repay_all: bool = False,
) -> TransactionResult
```

Build a repay transaction.

Repays borrowed base asset.

Parameters:

| Name           | Type      | Description                   | Default                                        |
| -------------- | --------- | ----------------------------- | ---------------------------------------------- |
| `amount`       | `Decimal` | Amount of base token to repay | *required*                                     |
| `on_behalf_of` | \`str     | None\`                        | Address with debt (defaults to wallet_address) |
| `repay_all`    | `bool`    | If True, repays full debt     | `False`                                        |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### get_market_info

```
get_market_info() -> CompoundV3MarketInfo
```

Get information about the current market.

Returns:

| Type                   | Description                              |
| ---------------------- | ---------------------------------------- |
| `CompoundV3MarketInfo` | CompoundV3MarketInfo with market details |

#### get_supported_collaterals

```
get_supported_collaterals() -> list[str]
```

Get list of supported collateral assets for the current market.

Returns:

| Type        | Description                      |
| ----------- | -------------------------------- |
| `list[str]` | List of collateral asset symbols |

#### get_collateral_info

```
get_collateral_info(asset: str) -> dict[str, Any] | None
```

Get information about a collateral asset.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `asset` | `str` | Collateral asset symbol | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`dict[str, Any] | None\`      |

#### calculate_health_factor

```
calculate_health_factor(
    collateral_balances: dict[str, Decimal],
    borrow_balance: Decimal,
) -> CompoundV3HealthFactor
```

Calculate health factor for a position.

Parameters:

| Name                  | Type                 | Description                             | Default    |
| --------------------- | -------------------- | --------------------------------------- | ---------- |
| `collateral_balances` | `dict[str, Decimal]` | Dictionary of collateral asset balances | *required* |
| `borrow_balance`      | `Decimal`            | Amount of borrowed base asset           | *required* |

Returns:

| Type                     | Description                                    |
| ------------------------ | ---------------------------------------------- |
| `CompoundV3HealthFactor` | CompoundV3HealthFactor with health calculation |

#### build_approve_transaction

```
build_approve_transaction(
    token: str, amount: Decimal | None = None
) -> TransactionResult
```

Build an ERC20 approval transaction for the Comet contract.

Parameters:

| Name     | Type      | Description             | Default                                   |
| -------- | --------- | ----------------------- | ----------------------------------------- |
| `token`  | `str`     | Token symbol to approve | *required*                                |
| `amount` | \`Decimal | None\`                  | Amount to approve (None for max approval) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

### CompoundV3Config

```
CompoundV3Config(
    chain: str,
    wallet_address: str,
    market: str = "usdc",
    default_slippage_bps: int = 50,
)
```

Configuration for Compound V3 adapter.

Attributes:

| Name                   | Type  | Description                                |
| ---------------------- | ----- | ------------------------------------------ |
| `chain`                | `str` | Blockchain network (ethereum, arbitrum)    |
| `wallet_address`       | `str` | User wallet address                        |
| `market`               | `str` | Market identifier (usdc, weth, usdt, etc.) |
| `default_slippage_bps` | `int` | Default slippage tolerance in basis points |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

### CompoundV3HealthFactor

```
CompoundV3HealthFactor(
    collateral_value_usd: Decimal,
    borrow_value_usd: Decimal,
    borrow_capacity_usd: Decimal,
    liquidation_threshold_usd: Decimal,
    health_factor: Decimal,
    is_liquidatable: bool = False,
)
```

Health factor calculation for a Compound V3 position.

Attributes:

| Name                        | Type      | Description                                               |
| --------------------------- | --------- | --------------------------------------------------------- |
| `collateral_value_usd`      | `Decimal` | Total value of collateral in USD                          |
| `borrow_value_usd`          | `Decimal` | Total value of borrowed assets in USD                     |
| `borrow_capacity_usd`       | `Decimal` | Maximum borrowable amount based on collateral             |
| `liquidation_threshold_usd` | `Decimal` | USD debt level at which liquidation occurs                |
| `health_factor`             | `Decimal` | Calculated health factor (liquidation_threshold / borrow) |
| `is_liquidatable`           | `bool`    | Whether the position can be liquidated                    |

#### is_healthy

```
is_healthy: bool
```

Check if position is healthy (HF >= 1).

#### available_borrow_usd

```
available_borrow_usd: Decimal
```

Get remaining borrowable amount in USD.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### CompoundV3MarketInfo

```
CompoundV3MarketInfo(
    market_id: str,
    name: str,
    base_token: str,
    base_token_address: str,
    comet_address: str,
    collaterals: dict[str, dict[str, Any]],
)
```

Information about a Compound V3 market.

Attributes:

| Name                 | Type                        | Description                                 |
| -------------------- | --------------------------- | ------------------------------------------- |
| `market_id`          | `str`                       | Market identifier (e.g., "usdc")            |
| `name`               | `str`                       | Human-readable market name                  |
| `base_token`         | `str`                       | Symbol of the base token (borrowable asset) |
| `base_token_address` | `str`                       | Address of the base token                   |
| `comet_address`      | `str`                       | Address of the Comet contract               |
| `collaterals`        | `dict[str, dict[str, Any]]` | Dictionary of supported collateral assets   |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### CompoundV3Position

```
CompoundV3Position(
    market_id: str,
    base_balance: Decimal = Decimal("0"),
    collateral_balances: dict[str, Decimal] = dict(),
)
```

User position in a Compound V3 market.

Attributes:

| Name                  | Type                 | Description                                                  |
| --------------------- | -------------------- | ------------------------------------------------------------ |
| `market_id`           | `str`                | Market identifier                                            |
| `base_balance`        | `Decimal`            | Balance of base token (positive = supply, negative = borrow) |
| `collateral_balances` | `dict[str, Decimal]` | Balances of collateral tokens                                |

#### is_supplier

```
is_supplier: bool
```

Check if user is a net supplier.

#### is_borrower

```
is_borrower: bool
```

Check if user is a net borrower.

#### borrow_balance

```
borrow_balance: Decimal
```

Get the borrow balance (positive value).

#### supply_balance

```
supply_balance: Decimal
```

Get the supply balance.

#### has_collateral

```
has_collateral: bool
```

Check if user has any collateral.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### TransactionResult

```
TransactionResult(
    success: bool,
    tx_data: dict[str, Any] | None = None,
    gas_estimate: int = 0,
    description: str = "",
    error: str | None = None,
)
```

Result of a transaction build operation.

Attributes:

| Name           | Type             | Description                 |
| -------------- | ---------------- | --------------------------- |
| `success`      | `bool`           | Whether operation succeeded |
| `tx_data`      | \`dict[str, Any] | None\`                      |
| `gas_estimate` | `int`            | Estimated gas               |
| `description`  | `str`            | Human-readable description  |
| `error`        | \`str            | None\`                      |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### CompoundV3Event

```
CompoundV3Event(
    event_type: CompoundV3EventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(tz=None))(),
)
```

Parsed Compound V3 event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> CompoundV3Event
```

Create from dictionary.

### CompoundV3EventType

Bases: `Enum`

Compound V3 event types.

### CompoundV3ReceiptParser

```
CompoundV3ReceiptParser(
    base_decimals: int = 6, **kwargs: Any
)
```

Parser for Compound V3 transaction receipts.

Refactored to use base infrastructure utilities for hex decoding and event registry management. Maintains all event parsing and aggregation logic.

Initialize the parser.

Parameters:

| Name            | Type  | Description                                      | Default |
| --------------- | ----- | ------------------------------------------------ | ------- |
| `base_decimals` | `int` | Decimals for the base token (default 6 for USDC) | `6`     |
| `**kwargs`      | `Any` | Additional arguments (ignored for compatibility) | `{}`    |

#### parse_receipt

```
parse_receipt(
    receipt: dict[str, Any],
    comet_address: str | None = None,
) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name            | Type             | Description                    | Default                                          |
| --------------- | ---------------- | ------------------------------ | ------------------------------------------------ |
| `receipt`       | `dict[str, Any]` | Transaction receipt dictionary | *required*                                       |
| `comet_address` | \`str            | None\`                         | Optional Comet contract address to filter events |

Returns:

| Type          | Description                                        |
| ------------- | -------------------------------------------------- |
| `ParseResult` | ParseResult with parsed events and aggregated data |

#### parse_logs

```
parse_logs(
    logs: list[dict[str, Any]],
    tx_hash: str = "",
    block_number: int = 0,
) -> list[CompoundV3Event]
```

Parse a list of logs.

#### is_compound_v3_event

```
is_compound_v3_event(topic: str | bytes) -> bool
```

Check if a topic is a known Compound V3 event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                                |
| ------ | ------------------------------------------ |
| `bool` | True if topic is a known Compound V3 event |

#### get_event_type

```
get_event_type(topic: str | bytes) -> CompoundV3EventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type                  | Description                                  |
| --------------------- | -------------------------------------------- |
| `CompoundV3EventType` | Event type enum or UNKNOWN if not recognized |

#### extract_supply_amount

```
extract_supply_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract supply amount from transaction receipt.

In Compound V3, supplying the base asset increases lending position and can also repay borrowed debt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_withdraw_amount

```
extract_withdraw_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract withdraw amount from transaction receipt.

In Compound V3, withdrawing the base asset decreases lending position and can create a borrow if withdrawn amount exceeds supplied amount.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_borrow_amount

```
extract_borrow_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract borrow amount from transaction receipt.

In Compound V3, borrowing is done via the Withdraw event when the user withdraws more than their supplied balance. This method returns the withdraw amount which represents the borrowed amount in that case.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_repay_amount

```
extract_repay_amount(receipt: dict[str, Any]) -> int | None
```

Extract repay amount from transaction receipt.

In Compound V3, repaying is done via the Supply event when the user has outstanding debt. This method returns the supply amount which represents the repaid amount in that case.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

### ParseResult

```
ParseResult(
    success: bool,
    events: list[CompoundV3Event] = list(),
    supply_amount: Decimal = Decimal("0"),
    withdraw_amount: Decimal = Decimal("0"),
    collateral_supplied: dict[str, Decimal] = dict(),
    collateral_withdrawn: dict[str, Decimal] = dict(),
    error: str | None = None,
)
```

Result of parsing a transaction receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

# Curve

Connector for Curve Finance DEX.

## almanak.framework.connectors.curve

Curve Finance Connector.

This module provides the Curve Finance adapter for executing swaps and managing liquidity positions on Curve pools across multiple chains.

Supported chains:

- Ethereum
- Arbitrum

Supported operations:

- SWAP: Token swaps via Curve pools (StableSwap, CryptoSwap, Tricrypto)
- LP_OPEN: Add liquidity to Curve pools
- LP_CLOSE: Remove liquidity from Curve pools

Example

from almanak.framework.connectors.curve import CurveAdapter, CurveConfig

config = CurveConfig( chain="ethereum", wallet_address="0x...", ) adapter = CurveAdapter(config)

### Execute a swap

result = adapter.swap( pool_address="0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", # 3pool token_in="USDC", token_out="DAI", amount_in=Decimal("1000"), )

### Add liquidity

lp_result = adapter.add_liquidity( pool_address="0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", amounts=[Decimal("1000"), Decimal("1000"), Decimal("1000")], # DAI, USDC, USDT )

### CurveAdapter

```
CurveAdapter(
    config: CurveConfig,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Curve Finance DEX protocol.

This adapter provides methods for:

- Executing token swaps via Curve pools
- Adding liquidity to pools (LP_OPEN)
- Removing liquidity from pools (LP_CLOSE)
- Handling ERC-20 approvals
- Managing slippage protection

Example

config = CurveConfig( chain="ethereum", wallet_address="0x...", ) adapter = CurveAdapter(config)

#### Execute a swap on 3pool

result = adapter.swap( pool_address="0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7", token_in="USDC", token_out="DAI", amount_in=Decimal("1000"), )

Initialize the adapter.

Parameters:

| Name             | Type            | Description                 | Default                                                   |
| ---------------- | --------------- | --------------------------- | --------------------------------------------------------- |
| `config`         | `CurveConfig`   | Curve adapter configuration | *required*                                                |
| `token_resolver` | \`TokenResolver | None\`                      | Optional TokenResolver instance. If None, uses singleton. |

#### get_pool_info

```
get_pool_info(pool_address: str) -> PoolInfo | None
```

Get information about a pool.

Parameters:

| Name           | Type  | Description           | Default    |
| -------------- | ----- | --------------------- | ---------- |
| `pool_address` | `str` | Pool contract address | *required* |

Returns:

| Type       | Description |
| ---------- | ----------- |
| \`PoolInfo | None\`      |

#### get_pool_by_name

```
get_pool_by_name(name: str) -> PoolInfo | None
```

Get pool info by name.

Parameters:

| Name   | Type  | Description                            | Default    |
| ------ | ----- | -------------------------------------- | ---------- |
| `name` | `str` | Pool name (e.g., "3pool", "frax_usdc") | *required* |

Returns:

| Type       | Description |
| ---------- | ----------- |
| \`PoolInfo | None\`      |

#### swap

```
swap(
    pool_address: str,
    token_in: str,
    token_out: str,
    amount_in: Decimal,
    slippage_bps: int | None = None,
    recipient: str | None = None,
    price_ratio: Decimal | None = None,
) -> SwapResult
```

Build a swap transaction on a Curve pool.

Parameters:

| Name           | Type      | Description                                     | Default                                                                                                                                                                                                                                                                                                                      |
| -------------- | --------- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pool_address` | `str`     | Pool contract address                           | *required*                                                                                                                                                                                                                                                                                                                   |
| `token_in`     | `str`     | Input token symbol or address                   | *required*                                                                                                                                                                                                                                                                                                                   |
| `token_out`    | `str`     | Output token symbol or address                  | *required*                                                                                                                                                                                                                                                                                                                   |
| `amount_in`    | `Decimal` | Amount of input token (in token units, not wei) | *required*                                                                                                                                                                                                                                                                                                                   |
| `slippage_bps` | \`int     | None\`                                          | Slippage tolerance in basis points (default from config)                                                                                                                                                                                                                                                                     |
| `recipient`    | \`str     | None\`                                          | Address to receive output tokens (default: wallet_address)                                                                                                                                                                                                                                                                   |
| `price_ratio`  | \`Decimal | None\`                                          | Price of input token / price of output token (e.g., if swapping USDT at $1 for WETH at $2500, price_ratio = 1/2500 = 0.0004). Required for CryptoSwap/Tricrypto pools; StableSwap pools ignore it. When None and pool is CryptoSwap, the swap fails (fail-closed) rather than executing with inaccurate slippage protection. |

Returns:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `SwapResult` | SwapResult with transaction data |

#### add_liquidity

```
add_liquidity(
    pool_address: str,
    amounts: list[Decimal],
    slippage_bps: int | None = None,
    recipient: str | None = None,
) -> LiquidityResult
```

Build an add_liquidity transaction (LP_OPEN).

Parameters:

| Name           | Type            | Description                                       | Default                                                    |
| -------------- | --------------- | ------------------------------------------------- | ---------------------------------------------------------- |
| `pool_address` | `str`           | Pool contract address                             | *required*                                                 |
| `amounts`      | `list[Decimal]` | List of token amounts to deposit (in token units) | *required*                                                 |
| `slippage_bps` | \`int           | None\`                                            | Slippage tolerance for min LP tokens (default from config) |
| `recipient`    | \`str           | None\`                                            | Address to receive LP tokens (default: wallet_address)     |

Returns:

| Type              | Description                           |
| ----------------- | ------------------------------------- |
| `LiquidityResult` | LiquidityResult with transaction data |

#### remove_liquidity

```
remove_liquidity(
    pool_address: str,
    lp_amount: Decimal,
    slippage_bps: int | None = None,
    recipient: str | None = None,
) -> LiquidityResult
```

Build a remove_liquidity transaction (LP_CLOSE, proportional).

Parameters:

| Name           | Type      | Description                 | Default                                                 |
| -------------- | --------- | --------------------------- | ------------------------------------------------------- |
| `pool_address` | `str`     | Pool contract address       | *required*                                              |
| `lp_amount`    | `Decimal` | Amount of LP tokens to burn | *required*                                              |
| `slippage_bps` | \`int     | None\`                      | Slippage tolerance for min output (default from config) |
| `recipient`    | \`str     | None\`                      | Address to receive tokens (default: wallet_address)     |

Returns:

| Type              | Description                           |
| ----------------- | ------------------------------------- |
| `LiquidityResult` | LiquidityResult with transaction data |

#### remove_liquidity_one_coin

```
remove_liquidity_one_coin(
    pool_address: str,
    lp_amount: Decimal,
    coin_index: int,
    slippage_bps: int | None = None,
    recipient: str | None = None,
) -> LiquidityResult
```

Build a remove_liquidity_one_coin transaction (LP_CLOSE, single-sided).

Parameters:

| Name           | Type      | Description                  | Default                                             |
| -------------- | --------- | ---------------------------- | --------------------------------------------------- |
| `pool_address` | `str`     | Pool contract address        | *required*                                          |
| `lp_amount`    | `Decimal` | Amount of LP tokens to burn  | *required*                                          |
| `coin_index`   | `int`     | Index of the coin to receive | *required*                                          |
| `slippage_bps` | \`int     | None\`                       | Slippage tolerance (default from config)            |
| `recipient`    | \`str     | None\`                       | Address to receive tokens (default: wallet_address) |

Returns:

| Type              | Description                           |
| ----------------- | ------------------------------------- |
| `LiquidityResult` | LiquidityResult with transaction data |

#### set_allowance

```
set_allowance(
    token: str, spender: str, amount: int
) -> None
```

Set cached allowance (for testing).

Parameters:

| Name      | Type  | Description      | Default    |
| --------- | ----- | ---------------- | ---------- |
| `token`   | `str` | Token address    | *required* |
| `spender` | `str` | Spender address  | *required* |
| `amount`  | `int` | Allowance amount | *required* |

#### clear_allowance_cache

```
clear_allowance_cache() -> None
```

Clear the allowance cache.

### CurveConfig

```
CurveConfig(
    chain: str,
    wallet_address: str,
    default_slippage_bps: int = 50,
    deadline_seconds: int = 300,
    rpc_url: str | None = None,
)
```

Configuration for CurveAdapter.

Attributes:

| Name                   | Type  | Description                                                    |
| ---------------------- | ----- | -------------------------------------------------------------- |
| `chain`                | `str` | Target blockchain (ethereum, arbitrum)                         |
| `wallet_address`       | `str` | Address executing transactions                                 |
| `default_slippage_bps` | `int` | Default slippage tolerance in basis points (default 50 = 0.5%) |
| `deadline_seconds`     | `int` | Transaction deadline in seconds (default 300 = 5 minutes)      |
| `rpc_url`              | \`str | None\`                                                         |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### LiquidityResult

```
LiquidityResult(
    success: bool,
    transactions: list[TransactionData] = list(),
    pool_address: str = "",
    operation: str = "",
    amounts: list[int] = list(),
    lp_amount: int = 0,
    error: str | None = None,
    gas_estimate: int = 0,
)
```

Result of a liquidity operation.

Attributes:

| Name           | Type                    | Description                                                                 |
| -------------- | ----------------------- | --------------------------------------------------------------------------- |
| `success`      | `bool`                  | Whether the operation was built successfully                                |
| `transactions` | `list[TransactionData]` | List of transactions to execute                                             |
| `pool_address` | `str`                   | Pool address                                                                |
| `operation`    | `str`                   | Operation type (add_liquidity, remove_liquidity, remove_liquidity_one_coin) |
| `amounts`      | `list[int]`             | Token amounts for the operation                                             |
| `lp_amount`    | `int`                   | LP token amount (minted or burned)                                          |
| `error`        | \`str                   | None\`                                                                      |
| `gas_estimate` | `int`                   | Total gas estimate                                                          |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PoolInfo

```
PoolInfo(
    address: str,
    lp_token: str,
    coins: list[str],
    coin_addresses: list[str],
    pool_type: PoolType,
    n_coins: int,
    name: str = "",
    virtual_price: Decimal = (lambda: Decimal("1.0"))(),
)
```

Information about a Curve pool.

Attributes:

| Name             | Type        | Description                                                                                                                                                                                                      |
| ---------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `address`        | `str`       | Pool contract address                                                                                                                                                                                            |
| `lp_token`       | `str`       | LP token address                                                                                                                                                                                                 |
| `coins`          | `list[str]` | List of coin symbols                                                                                                                                                                                             |
| `coin_addresses` | `list[str]` | List of coin addresses                                                                                                                                                                                           |
| `pool_type`      | `PoolType`  | Type of pool (stableswap, cryptoswap, tricrypto)                                                                                                                                                                 |
| `n_coins`        | `int`       | Number of coins in pool                                                                                                                                                                                          |
| `name`           | `str`       | Pool name                                                                                                                                                                                                        |
| `virtual_price`  | `Decimal`   | Pool virtual price (LP token value relative to underlying). Mature pools accumulate fees so virtual_price > 1.0. Used to adjust LP token estimates to prevent over-estimation that causes add_liquidity reverts. |

#### get_coin_index

```
get_coin_index(coin: str) -> int
```

Get the index of a coin in the pool.

Parameters:

| Name   | Type  | Description            | Default    |
| ------ | ----- | ---------------------- | ---------- |
| `coin` | `str` | Coin symbol or address | *required* |

Returns:

| Type  | Description       |
| ----- | ----------------- |
| `int` | Index of the coin |

Raises:

| Type         | Description               |
| ------------ | ------------------------- |
| `ValueError` | If coin not found in pool |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PoolType

Bases: `Enum`

Curve pool type.

### SwapResult

```
SwapResult(
    success: bool,
    transactions: list[TransactionData] = list(),
    pool_address: str = "",
    amount_in: int = 0,
    amount_out_minimum: int = 0,
    token_in: str = "",
    token_out: str = "",
    error: str | None = None,
    gas_estimate: int = 0,
)
```

Result of a swap operation.

Attributes:

| Name                 | Type                    | Description                             |
| -------------------- | ----------------------- | --------------------------------------- |
| `success`            | `bool`                  | Whether the swap was built successfully |
| `transactions`       | `list[TransactionData]` | List of transactions to execute         |
| `pool_address`       | `str`                   | Pool used for swap                      |
| `amount_in`          | `int`                   | Input amount in wei                     |
| `amount_out_minimum` | `int`                   | Minimum output amount (with slippage)   |
| `token_in`           | `str`                   | Input token address                     |
| `token_out`          | `str`                   | Output token address                    |
| `error`              | \`str                   | None\`                                  |
| `gas_estimate`       | `int`                   | Total gas estimate                      |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### TransactionData

```
TransactionData(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
    tx_type: str = "swap",
)
```

Transaction data for execution.

Attributes:

| Name           | Type  | Description                                                          |
| -------------- | ----- | -------------------------------------------------------------------- |
| `to`           | `str` | Target contract address                                              |
| `value`        | `int` | Native token value to send                                           |
| `data`         | `str` | Encoded calldata                                                     |
| `gas_estimate` | `int` | Estimated gas                                                        |
| `description`  | `str` | Human-readable description                                           |
| `tx_type`      | `str` | Type of transaction (approve, swap, add_liquidity, remove_liquidity) |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### AddLiquidityEventData

```
AddLiquidityEventData(
    provider: str,
    token_amounts: list[int],
    fees: list[int],
    invariant: int,
    token_supply: int,
    pool_address: str,
)
```

Parsed data from AddLiquidity event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### CurveEvent

```
CurveEvent(
    event_type: CurveEventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)
```

Parsed Curve event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### CurveEventType

Bases: `Enum`

Curve event types.

### CurveReceiptParser

```
CurveReceiptParser(chain: str = 'ethereum', **kwargs: Any)
```

Parser for Curve Finance transaction receipts.

Refactored to use base infrastructure utilities for hex decoding and event registry management. Maintains full backward compatibility.

Initialize the parser.

Parameters:

| Name       | Type  | Description                                      | Default      |
| ---------- | ----- | ------------------------------------------------ | ------------ |
| `chain`    | `str` | Blockchain network                               | `'ethereum'` |
| `**kwargs` | `Any` | Additional arguments (ignored for compatibility) | `{}`         |

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name      | Type             | Description              | Default    |
| --------- | ---------------- | ------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict | *required* |

Returns:

| Type          | Description                       |
| ------------- | --------------------------------- |
| `ParseResult` | ParseResult with extracted events |

#### extract_swap_amounts

```
extract_swap_amounts(
    receipt: dict[str, Any],
) -> SwapAmounts | None
```

Extract swap amounts from a transaction receipt.

Uses ERC-20 Transfer events to identify token addresses, then resolves actual decimals via TokenResolver for accurate decimal conversion. Falls back to returning None if decimals cannot be resolved (rather than returning wildly wrong amounts).

Parameters:

| Name      | Type             | Description                                            | Default    |
| --------- | ---------------- | ------------------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' and 'from' fields | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`SwapAmounts | None\`      |

#### extract_position_id

```
extract_position_id(
    receipt: dict[str, Any],
) -> int | str | None
```

Extract position identifier from LP transaction receipt.

For Curve (pool-based LP, no NFT positions), returns the LP token contract address. Unlike V3 DEXes where position_id is an NFT tokenId, Curve LP tokens are fungible ERC-20s — the LP token address is the stable identifier for the position.

The minted LP token *amount* is available separately via `extract_liquidity()`.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | str         |

#### extract_liquidity

```
extract_liquidity(
    receipt: dict[str, Any],
) -> Decimal | None
```

Extract LP tokens minted from AddLiquidity transaction.

Returns the LP token amount in **human-readable** form (e.g., `Decimal("98.133")`) by dividing the raw wei value by 10^decimals. This matches the convention expected by the LP_CLOSE compiler, which treats the value as a human-readable amount and converts back to wei internally.

Curve LP tokens always have 18 decimals. If the LP token address is found in the receipt, decimals are resolved via the token resolver; otherwise falls back to 18.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### extract_lp_tokens_received

```
extract_lp_tokens_received(
    receipt: dict[str, Any],
) -> Decimal | None
```

Extract LP tokens received from AddLiquidity transaction.

Looks for Transfer events from the zero address (mint).

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> LPCloseData | None
```

Extract LP close data from transaction receipt.

Looks for RemoveLiquidity, RemoveLiquidityOne, or RemoveLiquidityImbalance events.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`LPCloseData | None\`      |

#### is_curve_event

```
is_curve_event(topic: str | bytes) -> bool
```

Check if a topic is a known Curve event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                          |
| ------ | ------------------------------------ |
| `bool` | True if topic is a known Curve event |

#### get_event_type

```
get_event_type(topic: str | bytes) -> CurveEventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type             | Description           |
| ---------------- | --------------------- |
| `CurveEventType` | Event type or UNKNOWN |

### ParseResult

```
ParseResult(
    success: bool,
    events: list[CurveEvent] = list(),
    swap_events: list[SwapEventData] = list(),
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
    transaction_success: bool = True,
)
```

Result of parsing a receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### RemoveLiquidityEventData

```
RemoveLiquidityEventData(
    provider: str,
    token_amounts: list[int],
    fees: list[int],
    token_supply: int,
    pool_address: str,
)
```

Parsed data from RemoveLiquidity event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapEventData

```
SwapEventData(
    buyer: str,
    sold_id: int,
    tokens_sold: int,
    bought_id: int,
    tokens_bought: int,
    pool_address: str,
)
```

Parsed data from TokenExchange event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

# Drift

Connector for Drift protocol.

## almanak.framework.connectors.drift

Drift Protocol Connector.

Provides perpetual futures trading on Drift (Solana's #1 perps DEX). Supports market orders for opening and closing perp positions.

Key classes:

- DriftAdapter: Compiles PerpOpen/PerpClose intents to ActionBundles
- DriftSDK: Low-level instruction building (PDA derivation, Borsh encoding)
- DriftDataClient: REST client for Drift Data API (market info, funding rates)
- DriftReceiptParser: Parses transaction receipts for fill data
- DriftConfig: Adapter configuration

### DriftAdapter

```
DriftAdapter(
    config: DriftConfig,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Drift protocol integration with the Intent system.

Converts PerpOpenIntent and PerpCloseIntent into ActionBundles containing serialized Solana VersionedTransactions.

Key features:

- Market orders only (MVP scope)
- Automatic account initialization if needed
- Oracle and remaining accounts resolution via RPC
- Sub-account 0 only (default)

#### compile_perp_open_intent

```
compile_perp_open_intent(
    intent: PerpOpenIntent, price_oracle: Any = None
) -> ActionBundle
```

Compile a PerpOpenIntent to an ActionBundle.

Parameters:

| Name           | Type             | Description                                         | Default    |
| -------------- | ---------------- | --------------------------------------------------- | ---------- |
| `intent`       | `PerpOpenIntent` | Perp open intent with market, size, direction, etc. | *required* |
| `price_oracle` | `Any`            | Optional price oracle for USD conversions           | `None`     |

Returns:

| Type           | Description                                       |
| -------------- | ------------------------------------------------- |
| `ActionBundle` | ActionBundle with serialized VersionedTransaction |

#### compile_perp_close_intent

```
compile_perp_close_intent(
    intent: PerpCloseIntent, price_oracle: Any = None
) -> ActionBundle
```

Compile a PerpCloseIntent to an ActionBundle.

Parameters:

| Name           | Type              | Description                                             | Default    |
| -------------- | ----------------- | ------------------------------------------------------- | ---------- |
| `intent`       | `PerpCloseIntent` | Perp close intent with market, direction, optional size | *required* |
| `price_oracle` | `Any`             | Optional price oracle                                   | `None`     |

Returns:

| Type           | Description                                       |
| -------------- | ------------------------------------------------- |
| `ActionBundle` | ActionBundle with serialized VersionedTransaction |

### DriftDataClient

```
DriftDataClient(
    base_url: str = DRIFT_DATA_API_BASE_URL,
    timeout: int = 30,
)
```

Client for the Drift public data API.

Provides read-only access to market data, funding rates, and oracle prices. No authentication required.

Example

client = DriftDataClient() markets = client.get_perp_markets() oracle_prices = client.get_oracle_prices()

#### get_perp_markets

```
get_perp_markets() -> list[DriftMarket]
```

Get all perpetual futures markets.

Returns:

| Type                | Description                                    |
| ------------------- | ---------------------------------------------- |
| `list[DriftMarket]` | List of DriftMarket with market info and stats |

#### get_oracle_prices

```
get_oracle_prices() -> dict[int, Decimal]
```

Get current oracle prices for all perp markets.

Returns:

| Type                 | Description                                |
| -------------------- | ------------------------------------------ |
| `dict[int, Decimal]` | Dict of market_index → oracle price in USD |

#### get_oracle_price

```
get_oracle_price(market_index: int) -> Decimal | None
```

Get oracle price for a specific market.

Parameters:

| Name           | Type  | Description       | Default    |
| -------------- | ----- | ----------------- | ---------- |
| `market_index` | `int` | Perp market index | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### get_funding_rates

```
get_funding_rates(market_index: int) -> list[FundingRate]
```

Get historical funding rates for a market.

Parameters:

| Name           | Type  | Description       | Default    |
| -------------- | ----- | ----------------- | ---------- |
| `market_index` | `int` | Perp market index | *required* |

Returns:

| Type                | Description                     |
| ------------------- | ------------------------------- |
| `list[FundingRate]` | List of FundingRate data points |

#### get_market_info

```
get_market_info(market_index: int) -> DriftMarket | None
```

Get detailed info for a specific perp market.

Parameters:

| Name           | Type  | Description       | Default    |
| -------------- | ----- | ----------------- | ---------- |
| `market_index` | `int` | Perp market index | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`DriftMarket | None\`      |

### DriftAccountNotFoundError

```
DriftAccountNotFoundError(
    message: str, account_type: str = "", address: str = ""
)
```

Bases: `DriftError`

Exception raised when a Drift account is not found on-chain.

Attributes:

| Name           | Type | Description                                  |
| -------------- | ---- | -------------------------------------------- |
| `message`      |      | Error message                                |
| `account_type` |      | Type of account (e.g., "User", "PerpMarket") |
| `address`      |      | The address that was looked up               |

### DriftAPIError

```
DriftAPIError(
    message: str,
    status_code: int,
    endpoint: str | None = None,
    error_code: str | None = None,
)
```

Bases: `DriftError`

Exception raised for errors in the Drift Data API response.

Attributes:

| Name          | Type | Description                      |
| ------------- | ---- | -------------------------------- |
| `message`     |      | Error message                    |
| `status_code` |      | HTTP status code of the response |
| `endpoint`    |      | The API endpoint that was called |
| `error_code`  |      | Drift-specific error code        |

### DriftConfigError

```
DriftConfigError(
    message: str, parameter: str | None = None
)
```

Bases: `DriftError`

Exception raised for configuration errors.

Attributes:

| Name        | Type | Description                                               |
| ----------- | ---- | --------------------------------------------------------- |
| `message`   |      | Error message                                             |
| `parameter` |      | Name of the configuration parameter that caused the error |

### DriftError

Bases: `Exception`

Base exception class for all Drift connector errors.

### DriftMarketError

```
DriftMarketError(message: str, market: str = '')
```

Bases: `DriftError`

Exception raised for market-related errors.

Attributes:

| Name      | Type | Description       |
| --------- | ---- | ----------------- |
| `message` |      | Error message     |
| `market`  |      | Market identifier |

### DriftValidationError

```
DriftValidationError(
    message: str,
    field: str | None = None,
    value: Any | None = None,
)
```

Bases: `DriftError`

Exception raised for validation errors.

Attributes:

| Name      | Type | Description                              |
| --------- | ---- | ---------------------------------------- |
| `message` |      | Error message                            |
| `field`   |      | Name of the field that failed validation |
| `value`   |      | The invalid value                        |

### DriftConfig

```
DriftConfig(
    wallet_address: str,
    rpc_url: str = "",
    sub_account_id: int = 0,
    data_api_base_url: str = "https://data.api.drift.trade",
    timeout: int = 30,
)
```

Configuration for Drift adapter.

Attributes:

| Name                | Type  | Description                      |
| ------------------- | ----- | -------------------------------- |
| `wallet_address`    | `str` | Solana public key (Base58)       |
| `rpc_url`           | `str` | Solana RPC endpoint URL          |
| `sub_account_id`    | `int` | Drift sub-account ID (default 0) |
| `data_api_base_url` | `str` | Drift Data API base URL          |
| `timeout`           | `int` | Request timeout in seconds       |

### DriftMarket

```
DriftMarket(
    market_index: int,
    symbol: str = "",
    base_asset_symbol: str = "",
    oracle_price: Decimal = Decimal("0"),
    funding_rate: Decimal = Decimal("0"),
    funding_rate_24h: Decimal = Decimal("0"),
    open_interest: Decimal = Decimal("0"),
    volume_24h: Decimal = Decimal("0"),
    mark_price: Decimal = Decimal("0"),
)
```

A Drift perpetual futures market.

Attributes:

| Name                | Type      | Description                       |
| ------------------- | --------- | --------------------------------- |
| `market_index`      | `int`     | On-chain market index             |
| `symbol`            | `str`     | Market symbol (e.g., "SOL-PERP")  |
| `base_asset_symbol` | `str`     | Base asset symbol (e.g., "SOL")   |
| `oracle_price`      | `Decimal` | Current oracle price in USD       |
| `funding_rate`      | `Decimal` | Current funding rate (hourly)     |
| `funding_rate_24h`  | `Decimal` | 24-hour average funding rate      |
| `open_interest`     | `Decimal` | Total open interest in base units |
| `volume_24h`        | `Decimal` | 24-hour volume in USD             |
| `mark_price`        | `Decimal` | Current mark price                |

#### from_api_response

```
from_api_response(data: dict[str, Any]) -> DriftMarket
```

Create from Drift Data API market response.

### DriftPerpPosition

```
DriftPerpPosition(
    market_index: int = 0,
    base_asset_amount: int = 0,
    quote_asset_amount: int = 0,
    last_cumulative_funding_rate: int = 0,
    open_orders: int = 0,
)
```

A user's perpetual position on Drift.

Parsed from on-chain User account data.

Attributes:

| Name                           | Type  | Description                                        |
| ------------------------------ | ----- | -------------------------------------------------- |
| `market_index`                 | `int` | Perp market index                                  |
| `base_asset_amount`            | `int` | Signed base amount (positive=long, negative=short) |
| `quote_asset_amount`           | `int` | Quote amount (accumulated PnL)                     |
| `last_cumulative_funding_rate` | `int` | Last seen funding rate                             |
| `open_orders`                  | `int` | Number of open orders for this market              |

#### is_active

```
is_active: bool
```

Whether this position slot has an active position.

#### is_long

```
is_long: bool
```

Whether the position is long.

### DriftSpotPosition

```
DriftSpotPosition(
    market_index: int = 0,
    scaled_balance: int = 0,
    balance_type: int = 0,
    open_orders: int = 0,
)
```

A user's spot position on Drift.

Parsed from on-chain User account data.

Attributes:

| Name             | Type  | Description                        |
| ---------------- | ----- | ---------------------------------- |
| `market_index`   | `int` | Spot market index                  |
| `scaled_balance` | `int` | Scaled balance (deposit or borrow) |
| `balance_type`   | `int` | 0=Deposit, 1=Borrow                |
| `open_orders`    | `int` | Number of open orders              |

#### is_active

```
is_active: bool
```

Whether this position slot has an active balance.

### DriftUserAccount

```
DriftUserAccount(
    authority: str = "",
    sub_account_id: int = 0,
    perp_positions: list[DriftPerpPosition] = list(),
    spot_positions: list[DriftSpotPosition] = list(),
    exists: bool = True,
)
```

Parsed Drift User account state.

Attributes:

| Name             | Type                      | Description                              |
| ---------------- | ------------------------- | ---------------------------------------- |
| `authority`      | `str`                     | Wallet public key that owns this account |
| `sub_account_id` | `int`                     | Sub-account identifier                   |
| `perp_positions` | `list[DriftPerpPosition]` | List of perp position slots              |
| `spot_positions` | `list[DriftSpotPosition]` | List of spot position slots              |
| `exists`         | `bool`                    | Whether the account exists on-chain      |

#### active_perp_market_indexes

```
active_perp_market_indexes: list[int]
```

Get market indexes of active perp positions.

#### active_spot_market_indexes

```
active_spot_market_indexes: list[int]
```

Get market indexes of active spot positions.

### FundingRate

```
FundingRate(
    timestamp: int = 0,
    funding_rate: Decimal = Decimal("0"),
    market_index: int = 0,
)
```

Funding rate data point.

Attributes:

| Name           | Type      | Description                    |
| -------------- | --------- | ------------------------------ |
| `timestamp`    | `int`     | Unix timestamp                 |
| `funding_rate` | `Decimal` | Hourly funding rate as decimal |
| `market_index` | `int`     | Market index                   |

#### from_api_response

```
from_api_response(data: dict[str, Any]) -> FundingRate
```

Create from Drift Data API response.

### OrderParams

```
OrderParams(
    order_type: int = ORDER_TYPE_MARKET,
    market_type: int = MARKET_TYPE_PERP,
    direction: int = DIRECTION_LONG,
    user_order_id: int = 0,
    base_asset_amount: int = 0,
    price: int = 0,
    market_index: int = 0,
    reduce_only: bool = False,
    post_only: int = POST_ONLY_NONE,
    bit_flags: int = 0,
    max_ts: int | None = None,
    trigger_price: int | None = None,
    trigger_condition: int = TRIGGER_CONDITION_ABOVE,
    oracle_price_offset: int | None = None,
    auction_duration: int | None = None,
    auction_start_price: int | None = None,
    auction_end_price: int | None = None,
)
```

Parameters for a Drift perpetual order.

Maps to the on-chain OrderParams struct that gets Borsh-encoded into instruction data.

Attributes:

| Name                  | Type   | Description                                                            |
| --------------------- | ------ | ---------------------------------------------------------------------- |
| `order_type`          | `int`  | 0=Market, 1=Limit, 2=TriggerMarket, 3=TriggerLimit, 4=Oracle           |
| `market_type`         | `int`  | 0=Perp, 1=Spot                                                         |
| `direction`           | `int`  | 0=Long, 1=Short                                                        |
| `user_order_id`       | `int`  | User-assigned order ID (u8, 0-255)                                     |
| `base_asset_amount`   | `int`  | Position size in base precision (1e9)                                  |
| `price`               | `int`  | Limit price in price precision (1e6), 0 for market orders              |
| `market_index`        | `int`  | Market index (u16)                                                     |
| `reduce_only`         | `bool` | Whether order can only reduce position                                 |
| `post_only`           | `int`  | PostOnlyParam enum (0=None, 1=MustPostOnly, 2=TryPostOnly, 3=Slide)    |
| `bit_flags`           | `int`  | u8 bit field — bit 0: ImmediateOrCancel, bit 1: UpdateHighLeverageMode |
| `max_ts`              | \`int  | None\`                                                                 |
| `trigger_price`       | \`int  | None\`                                                                 |
| `trigger_condition`   | `int`  | 0=Above, 1=Below                                                       |
| `oracle_price_offset` | \`int  | None\`                                                                 |
| `auction_duration`    | \`int  | None\`                                                                 |
| `auction_start_price` | \`int  | None\`                                                                 |
| `auction_end_price`   | \`int  | None\`                                                                 |

### DriftReceiptParser

Receipt parser for Drift Protocol transactions.

Uses balance-delta approach to extract execution information from Solana transaction receipts.

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> dict[str, Any]
```

Parse a Drift transaction receipt.

Parameters:

| Name      | Type             | Description                                                                                                 | Default    |
| --------- | ---------------- | ----------------------------------------------------------------------------------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Solana transaction receipt dict with 'meta' containing preTokenBalances, postTokenBalances, and logMessages | *required* |

Returns:

| Type             | Description                       |
| ---------------- | --------------------------------- |
| `dict[str, Any]` | Parsed result with extracted data |

#### extract_perp_fill

```
extract_perp_fill(
    receipt: dict[str, Any],
) -> dict[str, Any] | None
```

Extract perpetual fill data from a receipt.

Parses Drift program logs for fill events that contain order execution details.

Parameters:

| Name      | Type             | Description         | Default    |
| --------- | ---------------- | ------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`dict[str, Any] | None\`      |

### DriftSDK

```
DriftSDK(
    wallet_address: str,
    rpc_url: str = "",
    timeout: int = 30,
)
```

SDK for building Drift protocol instructions.

Provides methods to:

- Derive PDAs for Drift accounts (state, user, markets)
- Build Solana instructions for perp trading
- Fetch and parse on-chain account data via RPC
- Build remaining accounts lists for order placement

Example

sdk = DriftSDK(wallet_address="your-pubkey", rpc_url="https://...") ix = sdk.build_place_perp_order_ix( order_params=OrderParams(direction=DIRECTION_LONG, ...), remaining_accounts=[...], )

#### get_state_pda

```
get_state_pda() -> Pubkey
```

Derive the Drift state account PDA.

#### get_user_pda

```
get_user_pda(
    authority: Pubkey | None = None, sub_account_id: int = 0
) -> Pubkey
```

Derive a Drift User account PDA.

Parameters:

| Name             | Type     | Description                | Default                                  |
| ---------------- | -------- | -------------------------- | ---------------------------------------- |
| `authority`      | \`Pubkey | None\`                     | Wallet pubkey (defaults to SDK's wallet) |
| `sub_account_id` | `int`    | Sub-account ID (default 0) | `0`                                      |

#### get_user_stats_pda

```
get_user_stats_pda(
    authority: Pubkey | None = None,
) -> Pubkey
```

Derive the User Stats account PDA.

#### get_perp_market_pda

```
get_perp_market_pda(market_index: int) -> Pubkey
```

Derive a Perp Market account PDA.

#### get_spot_market_pda

```
get_spot_market_pda(market_index: int) -> Pubkey
```

Derive a Spot Market account PDA.

#### fetch_user_account

```
fetch_user_account(
    sub_account_id: int = 0,
) -> DriftUserAccount
```

Fetch and parse a Drift User account from on-chain data.

Parameters:

| Name             | Type  | Description             | Default |
| ---------------- | ----- | ----------------------- | ------- |
| `sub_account_id` | `int` | Sub-account ID to fetch | `0`     |

Returns:

| Type               | Description                                                          |
| ------------------ | -------------------------------------------------------------------- |
| `DriftUserAccount` | DriftUserAccount with parsed positions, or exists=False if not found |

#### fetch_market_oracle

```
fetch_market_oracle(market_index: int) -> Pubkey | None
```

Fetch the oracle pubkey from a perp market account.

Parameters:

| Name           | Type  | Description       | Default    |
| -------------- | ----- | ----------------- | ---------- |
| `market_index` | `int` | Perp market index | *required* |

Returns:

| Type     | Description |
| -------- | ----------- |
| \`Pubkey | None\`      |

#### fetch_spot_market_oracle

```
fetch_spot_market_oracle(
    market_index: int,
) -> Pubkey | None
```

Fetch the oracle pubkey from a spot market account.

#### build_remaining_accounts

```
build_remaining_accounts(
    market_index: int, sub_account_id: int = 0
) -> list[AccountMeta]
```

Build the remaining accounts list for a place_perp_order instruction.

Drift requires remaining accounts to include all markets the user has positions in, plus their oracles. The order is:

1. Oracle accounts (readable)
1. Spot market accounts (readable)
1. Perp market accounts (readable)

Parameters:

| Name             | Type  | Description                  | Default    |
| ---------------- | ----- | ---------------------------- | ---------- |
| `market_index`   | `int` | The perp market being traded | *required* |
| `sub_account_id` | `int` | Sub-account ID               | `0`        |

Returns:

| Type                | Description                                |
| ------------------- | ------------------------------------------ |
| `list[AccountMeta]` | List of AccountMeta for remaining accounts |

#### build_place_perp_order_ix

```
build_place_perp_order_ix(
    order_params: OrderParams,
    remaining_accounts: list[AccountMeta],
    sub_account_id: int = 0,
) -> Instruction
```

Build a place_perp_order instruction.

Parameters:

| Name                 | Type                | Description                                     | Default    |
| -------------------- | ------------------- | ----------------------------------------------- | ---------- |
| `order_params`       | `OrderParams`       | Order parameters to encode                      | *required* |
| `remaining_accounts` | `list[AccountMeta]` | Pre-built remaining accounts (oracles, markets) | *required* |
| `sub_account_id`     | `int`               | Sub-account ID                                  | `0`        |

Returns:

| Type          | Description                              |
| ------------- | ---------------------------------------- |
| `Instruction` | Solana Instruction ready for transaction |

#### build_initialize_user_ix

```
build_initialize_user_ix(
    sub_account_id: int = 0,
) -> Instruction
```

Build an initialize_user instruction.

Creates a new Drift User account for the wallet. Must be called before the first order if no account exists.

#### build_initialize_user_stats_ix

```
build_initialize_user_stats_ix() -> Instruction
```

Build an initialize_user_stats instruction.

Must be called before initialize_user if no user stats exist.

#### build_deposit_ix

```
build_deposit_ix(
    amount: int,
    market_index: int = 0,
    sub_account_id: int = 0,
) -> Instruction
```

Build a deposit instruction.

Deposits collateral (typically USDC) into a Drift spot market.

Parameters:

| Name             | Type  | Description                                           | Default    |
| ---------------- | ----- | ----------------------------------------------------- | ---------- |
| `amount`         | `int` | Amount in smallest units (e.g., USDC with 6 decimals) | *required* |
| `market_index`   | `int` | Spot market index (0 = USDC)                          | `0`        |
| `sub_account_id` | `int` | Sub-account ID                                        | `0`        |

#### get_init_instructions

```
get_init_instructions(
    sub_account_id: int = 0,
) -> list[Instruction]
```

Get instructions to initialize user accounts if they don't exist.

Returns an empty list if accounts already exist.

# Enso

Connector for Enso Finance DEX aggregator.

## almanak.framework.connectors.enso

Enso Finance Protocol Connector.

Enso is a routing and composable transaction protocol that aggregates liquidity across multiple DEXs and lending protocols.

This connector provides:

- EnsoClient: SDK for interacting with the Enso Finance API
- EnsoAdapter: Adapter for converting intents to Enso transactions
- EnsoReceiptParser: Parser for extracting results from transaction receipts

Supports both same-chain and cross-chain swaps via Enso's bridge aggregation (Stargate, LayerZero).

Example

from almanak.framework.connectors.enso import EnsoClient, EnsoAdapter, EnsoConfig

### Create client

config = EnsoConfig( api_key="your-api-key", chain="base", wallet_address="0x...", ) client = EnsoClient(config)

### Same-chain swap

route = client.get_route( token_in="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC on Base token_out="0x4200000000000000000000000000000000000006", # WETH on Base amount_in=1000000000, # 1000 USDC slippage_bps=50, # 0.5% )

### Cross-chain swap: Base -> Arbitrum

cross_chain_route = client.get_cross_chain_route( token_in="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC on Base token_out="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH on Arbitrum amount_in=1000000000, destination_chain="arbitrum", )

### EnsoAdapter

```
EnsoAdapter(
    config: EnsoConfig,
    use_safe_route_single: bool = True,
    price_provider: dict[str, Decimal] | None = None,
    allow_placeholder_prices: bool = False,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Enso protocol integration with the Intent system.

This adapter converts high-level SwapIntents into executable transactions using the Enso Finance routing protocol.

Features:

- Multi-DEX routing for optimal swap prices
- Automatic slippage protection via safeRouteSingle
- ERC-20 approval handling

Example

config = EnsoConfig(chain="arbitrum", wallet_address="0x...") adapter = EnsoAdapter(config)

intent = SwapIntent( from_token="USDC", to_token="WETH", amount_usd=Decimal("1000"), ) bundle = adapter.compile_swap_intent(intent)

Initialize the Enso adapter.

Parameters:

| Name                       | Type                 | Description                                                                                             | Default                                                                                                                      |
| -------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `config`                   | `EnsoConfig`         | Enso client configuration                                                                               | *required*                                                                                                                   |
| `use_safe_route_single`    | `bool`               | Whether to transform routes to use safeRouteSingle for slippage protection (default True)               | `True`                                                                                                                       |
| `price_provider`           | \`dict[str, Decimal] | None\`                                                                                                  | Price oracle dict (token symbol -> USD price). Required for production use to calculate accurate slippage amounts.           |
| `allow_placeholder_prices` | `bool`               | If False (default), raises ValueError when no price_provider is given. Set to True ONLY for unit tests. | `False`                                                                                                                      |
| `token_resolver`           | \`TokenResolver      | None\`                                                                                                  | Optional TokenResolver instance for unified token resolution. If None, uses the default singleton from get_token_resolver(). |

Raises:

| Type         | Description                                                             |
| ------------ | ----------------------------------------------------------------------- |
| `ValueError` | If no price_provider is provided and allow_placeholder_prices is False. |

#### resolve_token_address

```
resolve_token_address(token: str) -> str
```

Resolve token symbol or address to address using TokenResolver.

Parameters:

| Name    | Type  | Description                            | Default    |
| ------- | ----- | -------------------------------------- | ---------- |
| `token` | `str` | Token symbol (e.g., "USDC") or address | *required* |

Returns:

| Type  | Description   |
| ----- | ------------- |
| `str` | Token address |

Raises:

| Type                   | Description                     |
| ---------------------- | ------------------------------- |
| `TokenResolutionError` | If the token cannot be resolved |

#### get_token_decimals

```
get_token_decimals(token: str) -> int
```

Get token decimals using TokenResolver.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `token` | `str` | Token symbol or address | *required* |

Returns:

| Type  | Description    |
| ----- | -------------- |
| `int` | Token decimals |

Raises:

| Type                   | Description                      |
| ---------------------- | -------------------------------- |
| `TokenResolutionError` | If decimals cannot be determined |

#### compile_swap_intent

```
compile_swap_intent(
    intent: SwapIntent,
    price_oracle: dict[str, Decimal] | None = None,
) -> ActionBundle
```

Compile a SwapIntent to an ActionBundle using Enso.

Parameters:

| Name           | Type                 | Description               | Default                                   |
| -------------- | -------------------- | ------------------------- | ----------------------------------------- |
| `intent`       | `SwapIntent`         | The SwapIntent to compile | *required*                                |
| `price_oracle` | \`dict[str, Decimal] | None\`                    | Optional price oracle for USD conversions |

Returns:

| Type           | Description                                        |
| -------------- | -------------------------------------------------- |
| `ActionBundle` | ActionBundle containing transactions for execution |

#### get_fresh_swap_transaction

```
get_fresh_swap_transaction(
    metadata: dict[str, Any],
) -> dict[str, Any]
```

Fetch fresh swap transaction data immediately before execution.

IMPORTANT: Enso route data becomes stale within seconds. This method should be called immediately before executing a swap transaction to get fresh route data from the Enso API.

Parameters:

| Name       | Type             | Description                                                       | Default    |
| ---------- | ---------------- | ----------------------------------------------------------------- | ---------- |
| `metadata` | `dict[str, Any]` | The metadata from a compiled ActionBundle containing route_params | *required* |

Returns:

| Type             | Description                                                                                                                                                                                                                                                     |
| ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `dict[str, Any]` | Fresh transaction data dict with keys: - to: Router address - value: Native token value (usually 0) - data: Fresh route calldata - gas_estimate: Raw gas estimate (orchestrator applies buffer) - amount_out: Expected output amount (for logging/verification) |

Raises:

| Type         | Description                              |
| ------------ | ---------------------------------------- |
| `ValueError` | If metadata doesn't contain route_params |
| `Exception`  | If route fetching fails                  |

Example

##### After executing approval, fetch fresh swap data

fresh_tx = adapter.get_fresh_swap_transaction(bundle.metadata)

##### Build and sign transaction with fresh data

unsigned_tx = UnsignedTransaction( to=fresh_tx["to"], data=fresh_tx["data"], value=fresh_tx["value"], gas_limit=fresh_tx["gas_estimate"], ... )

### EnsoClient

```
EnsoClient(config: EnsoConfig)
```

Client for interacting with the Enso Finance API.

This client provides methods for:

- Getting swap routes across multiple DEXs
- Getting quotes for swaps
- Building bundle transactions for complex DeFi operations
- Approving tokens for the Enso router

Example

config = EnsoConfig( chain="arbitrum", wallet_address="0x...", ) client = EnsoClient(config)

#### Get a simple swap route

route = client.get_route( token_in="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", token_out="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", amount_in=1000000000, slippage_bps=50, )

Initialize the Enso client.

Parameters:

| Name     | Type         | Description               | Default    |
| -------- | ------------ | ------------------------- | ---------- |
| `config` | `EnsoConfig` | Enso client configuration | *required* |

#### chain_id

```
chain_id: int
```

Get the chain ID.

#### wallet_address

```
wallet_address: str
```

Get the configured wallet address.

#### get_route

```
get_route(
    token_in: str,
    token_out: str,
    amount_in: int,
    slippage_bps: int = 50,
    from_address: str | None = None,
    receiver: str | None = None,
    routing_strategy: RoutingStrategy | None = None,
    max_price_impact_bps: int | None = None,
    destination_chain_id: int | None = None,
    refund_receiver: str | None = None,
) -> RouteTransaction
```

Get the best swap route from one token to another.

Supports both same-chain swaps and cross-chain swaps via Enso's bridge aggregation (Stargate, LayerZero).

Parameters:

| Name                   | Type              | Description                                            | Default                                                                    |
| ---------------------- | ----------------- | ------------------------------------------------------ | -------------------------------------------------------------------------- |
| `token_in`             | `str`             | Input token address                                    | *required*                                                                 |
| `token_out`            | `str`             | Output token address                                   | *required*                                                                 |
| `amount_in`            | `int`             | Input amount in wei (as integer)                       | *required*                                                                 |
| `slippage_bps`         | `int`             | Slippage tolerance in basis points (default 50 = 0.5%) | `50`                                                                       |
| `from_address`         | \`str             | None\`                                                 | Address executing the swap (defaults to config wallet)                     |
| `receiver`             | \`str             | None\`                                                 | Address to receive output (defaults to from_address)                       |
| `routing_strategy`     | \`RoutingStrategy | None\`                                                 | Routing strategy to use                                                    |
| `max_price_impact_bps` | \`int             | None\`                                                 | Maximum allowed price impact in basis points                               |
| `destination_chain_id` | \`int             | None\`                                                 | Target chain ID for cross-chain swaps (None for same-chain)                |
| `refund_receiver`      | \`str             | None\`                                                 | Address to receive refunds if cross-chain fails (defaults to from_address) |

Returns:

| Type               | Description                                              |
| ------------------ | -------------------------------------------------------- |
| `RouteTransaction` | RouteTransaction with transaction data and route details |

Raises:

| Type                               | Description                       |
| ---------------------------------- | --------------------------------- |
| `EnsoAPIError`                     | If the API request fails          |
| `PriceImpactExceedsThresholdError` | If price impact exceeds threshold |

Example

##### Same-chain swap on Arbitrum

route = client.get_route( token_in="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC token_out="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH amount_in=1000000000, )

##### Cross-chain swap: Base -> Arbitrum

route = client.get_route( token_in="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC on Base token_out="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH on Arbitrum amount_in=1000000000, destination_chain_id=42161, # Arbitrum )

#### get_route_from_params

```
get_route_from_params(
    params: RouteParams,
) -> RouteTransaction
```

Get route using RouteParams object.

Parameters:

| Name     | Type          | Description      | Default    |
| -------- | ------------- | ---------------- | ---------- |
| `params` | `RouteParams` | Route parameters | *required* |

Returns:

| Type               | Description                            |
| ------------------ | -------------------------------------- |
| `RouteTransaction` | RouteTransaction with transaction data |

#### get_quote

```
get_quote(
    token_in: str,
    token_out: str,
    amount_in: int,
    from_address: str | None = None,
    routing_strategy: RoutingStrategy | None = None,
    destination_chain_id: int | None = None,
) -> Quote
```

Get a quote for a swap without building the transaction.

Supports both same-chain and cross-chain quotes.

Parameters:

| Name                   | Type              | Description          | Default                                                      |
| ---------------------- | ----------------- | -------------------- | ------------------------------------------------------------ |
| `token_in`             | `str`             | Input token address  | *required*                                                   |
| `token_out`            | `str`             | Output token address | *required*                                                   |
| `amount_in`            | `int`             | Input amount in wei  | *required*                                                   |
| `from_address`         | \`str             | None\`               | Address executing the swap                                   |
| `routing_strategy`     | \`RoutingStrategy | None\`               | Routing strategy to use                                      |
| `destination_chain_id` | \`int             | None\`               | Target chain ID for cross-chain quotes (None for same-chain) |

Returns:

| Type    | Description                       |
| ------- | --------------------------------- |
| `Quote` | Quote with expected output amount |

#### get_bundle

```
get_bundle(
    bundle_actions: list[BundleAction],
    from_address: str | None = None,
    routing_strategy: RoutingStrategy | None = None,
    skip_quote: bool = False,
) -> dict[str, Any]
```

Get a bundle transaction for multiple DeFi actions.

Bundles allow composing multiple operations (deposits, borrows, swaps) into a single transaction.

Parameters:

| Name               | Type                 | Description                                               | Default                      |
| ------------------ | -------------------- | --------------------------------------------------------- | ---------------------------- |
| `bundle_actions`   | `list[BundleAction]` | List of actions to bundle                                 | *required*                   |
| `from_address`     | \`str                | None\`                                                    | Address executing the bundle |
| `routing_strategy` | \`RoutingStrategy    | None\`                                                    | Routing strategy to use      |
| `skip_quote`       | `bool`               | Skip quote generation (for operations that don't need it) | `False`                      |

Returns:

| Type             | Description                           |
| ---------------- | ------------------------------------- |
| `dict[str, Any]` | Bundle response with transaction data |

#### get_approval

```
get_approval(
    token_address: str,
    amount: int | None = None,
    from_address: str | None = None,
    routing_strategy: RoutingStrategy | None = None,
) -> dict[str, Any]
```

Get approval transaction data for a token.

Parameters:

| Name               | Type              | Description      | Default                                   |
| ------------------ | ----------------- | ---------------- | ----------------------------------------- |
| `token_address`    | `str`             | Token to approve | *required*                                |
| `amount`           | \`int             | None\`           | Amount to approve (defaults to unlimited) |
| `from_address`     | \`str             | None\`           | Address granting approval                 |
| `routing_strategy` | \`RoutingStrategy | None\`           | Routing strategy (determines spender)     |

Returns:

| Type             | Description               |
| ---------------- | ------------------------- |
| `dict[str, Any]` | Approval transaction data |

#### get_router_address

```
get_router_address(
    routing_strategy: RoutingStrategy | None = None,
) -> str
```

Get the Enso router address for the current chain.

Parameters:

| Name               | Type              | Description | Default                               |
| ------------------ | ----------------- | ----------- | ------------------------------------- |
| `routing_strategy` | \`RoutingStrategy | None\`      | Routing strategy (router or delegate) |

Returns:

| Type  | Description             |
| ----- | ----------------------- |
| `str` | Router contract address |

#### resolve_chain_id

```
resolve_chain_id(chain: str | int) -> int
```

Resolve a chain name or ID to a chain ID.

Parameters:

| Name    | Type  | Description | Default                                                 |
| ------- | ----- | ----------- | ------------------------------------------------------- |
| `chain` | \`str | int\`       | Chain name (e.g., "arbitrum") or chain ID (e.g., 42161) |

Returns:

| Type  | Description         |
| ----- | ------------------- |
| `int` | Chain ID as integer |

Raises:

| Type                  | Description                    |
| --------------------- | ------------------------------ |
| `EnsoValidationError` | If chain name is not supported |

#### get_cross_chain_route

```
get_cross_chain_route(
    token_in: str,
    token_out: str,
    amount_in: int,
    destination_chain: str | int,
    slippage_bps: int = 50,
    receiver: str | None = None,
    max_price_impact_bps: int | None = None,
) -> RouteTransaction
```

Convenience method for cross-chain swaps.

This is a simplified interface for cross-chain operations that handles chain ID resolution and sets sensible defaults.

Parameters:

| Name                   | Type  | Description                                            | Default                                                |
| ---------------------- | ----- | ------------------------------------------------------ | ------------------------------------------------------ |
| `token_in`             | `str` | Input token address (on source chain)                  | *required*                                             |
| `token_out`            | `str` | Output token address (on destination chain)            | *required*                                             |
| `amount_in`            | `int` | Input amount in wei                                    | *required*                                             |
| `destination_chain`    | \`str | int\`                                                  | Destination chain name (e.g., "arbitrum") or chain ID  |
| `slippage_bps`         | `int` | Slippage tolerance in basis points (default 50 = 0.5%) | `50`                                                   |
| `receiver`             | \`str | None\`                                                 | Address to receive output (defaults to wallet address) |
| `max_price_impact_bps` | \`int | None\`                                                 | Maximum allowed price impact in basis points           |

Returns:

| Type               | Description                                        |
| ------------------ | -------------------------------------------------- |
| `RouteTransaction` | RouteTransaction with cross-chain transaction data |

Example

##### Bridge USDC from Base to Arbitrum (swap to WETH on arrival)

client = EnsoClient(EnsoConfig(chain="base", wallet_address="0x...")) route = client.get_cross_chain_route( token_in="0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", # USDC on Base token_out="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH on Arbitrum amount_in=1000 * 10\*\*6, # 1000 USDC destination_chain="arbitrum", )

### EnsoConfig

```
EnsoConfig(
    chain: str,
    wallet_address: str,
    api_key: str | None = None,
    base_url: str = "https://api.enso.finance",
    routing_strategy: RoutingStrategy = RoutingStrategy.ROUTER,
    timeout: int = 30,
)
```

Configuration for Enso client.

Attributes:

| Name               | Type              | Description                                           |
| ------------------ | ----------------- | ----------------------------------------------------- |
| `api_key`          | \`str             | None\`                                                |
| `chain`            | `str`             | Chain name (e.g., "arbitrum", "ethereum") or chain ID |
| `wallet_address`   | `str`             | Default wallet address for transactions               |
| `base_url`         | `str`             | Enso API base URL                                     |
| `routing_strategy` | `RoutingStrategy` | Default routing strategy                              |
| `timeout`          | `int`             | Request timeout in seconds                            |

#### chain_id

```
chain_id: int
```

Get numeric chain ID.

#### __post_init__

```
__post_init__() -> None
```

Validate configuration and resolve API key.

### EnsoAPIError

```
EnsoAPIError(
    message: str,
    status_code: int,
    endpoint: str | None = None,
    error_data: dict | None = None,
)
```

Bases: `EnsoError`

Exception raised for errors in the API response.

Attributes:

| Name                | Type | Description                                            |
| ------------------- | ---- | ------------------------------------------------------ |
| `message`           |      | Error message                                          |
| `status_code`       |      | HTTP status code of the response                       |
| `endpoint`          |      | The API endpoint that was called                       |
| `error_type`        |      | Classified error type (e.g., SERVER_ERROR, RATE_LIMIT) |
| `api_error_message` |      | The specific error message from the API response       |
| `error_data`        |      | Parsed error data from the response, if available      |

### EnsoConfigError

```
EnsoConfigError(message: str, parameter: str | None = None)
```

Bases: `EnsoError`

Exception raised for SDK configuration errors.

Attributes:

| Name        | Type | Description                                               |
| ----------- | ---- | --------------------------------------------------------- |
| `message`   |      | Error message                                             |
| `parameter` |      | Name of the configuration parameter that caused the error |

### EnsoError

Bases: `Exception`

Base exception class for all Enso SDK errors.

### EnsoValidationError

```
EnsoValidationError(
    message: str,
    field: str | None = None,
    value: Any | None = None,
)
```

Bases: `EnsoError`

Exception raised for validation errors.

Attributes:

| Name      | Type | Description                              |
| --------- | ---- | ---------------------------------------- |
| `message` |      | Error message                            |
| `field`   |      | Name of the field that failed validation |
| `value`   |      | The invalid value                        |

### PriceImpactExceedsThresholdError

```
PriceImpactExceedsThresholdError(
    message: str,
    price_impact_bps: float | None = None,
    threshold_bps: int | None = None,
)
```

Bases: `EnsoError`

Raised when route price impact exceeds maximum threshold.

Attributes:

| Name               | Type | Description                                  |
| ------------------ | ---- | -------------------------------------------- |
| `message`          |      | Error message                                |
| `price_impact_bps` |      | Actual price impact in basis points          |
| `threshold_bps`    |      | Maximum allowed price impact in basis points |

### Hop

```
Hop(
    token_in: list[str] = list(),
    token_out: list[str] = list(),
    protocol: str = "",
    action: str = "",
    primary: str = "",
)
```

A single hop in a swap route.

Attributes:

| Name        | Type        | Description                     |
| ----------- | ----------- | ------------------------------- |
| `token_in`  | `list[str]` | Input token addresses           |
| `token_out` | `list[str]` | Output token addresses          |
| `protocol`  | `str`       | Protocol used for this hop      |
| `action`    | `str`       | Action performed                |
| `primary`   | `str`       | Primary address (pool/contract) |

#### from_api_response

```
from_api_response(data: dict[str, Any]) -> Hop
```

Create Hop from API response.

### Quote

```
Quote(
    amount_out: str,
    gas: str | None = None,
    price_impact: float | None = None,
    route: list[Hop] | None = None,
    chain_id: int | None = None,
)
```

Quote response from Enso API.

Attributes:

| Name           | Type        | Description            |
| -------------- | ----------- | ---------------------- |
| `amount_out`   | `str`       | Expected output amount |
| `gas`          | \`str       | None\`                 |
| `price_impact` | \`float     | None\`                 |
| `route`        | \`list[Hop] | None\`                 |
| `chain_id`     | \`int       | None\`                 |

#### from_api_response

```
from_api_response(data: dict[str, Any]) -> Quote
```

Create Quote from API response.

#### get_price_impact_percentage

```
get_price_impact_percentage() -> float | None
```

Get price impact as a percentage.

### RouteParams

```
RouteParams(
    from_address: str,
    token_in: str,
    token_out: str,
    amount_in: int,
    chain_id: int,
    slippage_bps: int = 50,
    routing_strategy: RoutingStrategy = RoutingStrategy.ROUTER,
    receiver: str | None = None,
    max_price_impact_bps: int | None = None,
    destination_chain_id: int | None = None,
    refund_receiver: str | None = None,
)
```

Parameters for getting a swap route from Enso.

Attributes:

| Name                   | Type              | Description                                          |
| ---------------------- | ----------------- | ---------------------------------------------------- |
| `from_address`         | `str`             | Address executing the transaction                    |
| `token_in`             | `str`             | Input token address                                  |
| `token_out`            | `str`             | Output token address                                 |
| `amount_in`            | `int`             | Input amount in wei (as int)                         |
| `chain_id`             | `int`             | Source blockchain chain ID                           |
| `slippage_bps`         | `int`             | Slippage tolerance in basis points (e.g., 50 = 0.5%) |
| `routing_strategy`     | `RoutingStrategy` | Routing strategy to use                              |
| `receiver`             | \`str             | None\`                                               |
| `max_price_impact_bps` | \`int             | None\`                                               |
| `destination_chain_id` | \`int             | None\`                                               |
| `refund_receiver`      | \`str             | None\`                                               |

#### is_cross_chain

```
is_cross_chain: bool
```

Check if this is a cross-chain route.

#### to_api_format

```
to_api_format() -> dict[str, Any]
```

Convert parameters to Enso API format.

### RouteTransaction

```
RouteTransaction(
    gas: str,
    tx: Transaction,
    amount_out: dict[str, Any],
    price_impact: float | None = None,
    route: list[Hop] = list(),
    fee_amount: list[str] = list(),
    created_at: int | None = None,
    chain_id: int | None = None,
    destination_chain_id: int | None = None,
    bridge_fee: str | None = None,
    estimated_time: int | None = None,
)
```

Route transaction response from Enso API.

Attributes:

| Name                   | Type             | Description                                           |
| ---------------------- | ---------------- | ----------------------------------------------------- |
| `gas`                  | `str`            | Estimated gas for the transaction                     |
| `tx`                   | `Transaction`    | Transaction data                                      |
| `amount_out`           | `dict[str, Any]` | Expected output amount (as dict with token -> amount) |
| `price_impact`         | \`float          | None\`                                                |
| `route`                | `list[Hop]`      | List of hops in the route                             |
| `fee_amount`           | `list[str]`      | Fee amounts                                           |
| `created_at`           | \`int            | None\`                                                |
| `chain_id`             | \`int            | None\`                                                |
| `destination_chain_id` | \`int            | None\`                                                |
| `bridge_fee`           | \`str            | None\`                                                |
| `estimated_time`       | \`int            | None\`                                                |

#### is_cross_chain

```
is_cross_chain: bool
```

Check if this is a cross-chain route.

#### from_api_response

```
from_api_response(data: dict[str, Any]) -> RouteTransaction
```

Create RouteTransaction from API response.

#### get_amount_out_wei

```
get_amount_out_wei(token_address: str | None = None) -> int
```

Get the output amount in wei.

Parameters:

| Name            | Type  | Description | Default                                                                           |
| --------------- | ----- | ----------- | --------------------------------------------------------------------------------- |
| `token_address` | \`str | None\`      | Specific token address to get amount for. If None, returns the first/only amount. |

Returns:

| Type  | Description                     |
| ----- | ------------------------------- |
| `int` | Output amount in wei as integer |

#### get_price_impact_percentage

```
get_price_impact_percentage() -> float | None
```

Get price impact as a percentage.

Returns:

| Type    | Description |
| ------- | ----------- |
| \`float | None\`      |

### RoutingStrategy

Bases: `StrEnum`

Enso routing strategies.

### Transaction

```
Transaction(
    data: str, to: str, from_address: str, value: str
)
```

Transaction data from Enso API response.

Attributes:

| Name           | Type  | Description                         |
| -------------- | ----- | ----------------------------------- |
| `data`         | `str` | Encoded calldata                    |
| `to`           | `str` | Target contract address             |
| `from_address` | `str` | Sender address                      |
| `value`        | `str` | Native token value to send (in wei) |

#### from_api_response

```
from_api_response(data: dict[str, Any]) -> Transaction
```

Create Transaction from API response.

### EnsoReceiptParser

```
EnsoReceiptParser(**kwargs: Any)
```

Parser for Enso transaction receipts.

This parser extracts swap results from transaction receipts by:

1. Checking transaction status
1. Parsing Transfer event logs to find amounts
1. Identifying the recipient's received amount

Example

parser = EnsoReceiptParser() receipt = web3.eth.get_transaction_receipt(tx_hash)

result = parser.parse_swap_receipt( receipt=receipt, wallet_address="0x...", token_out="0x...", ) print(f"Received: {result.amount_out}")

Initialize EnsoReceiptParser.

Parameters:

| Name       | Type  | Description                                                                                       | Default |
| ---------- | ----- | ------------------------------------------------------------------------------------------------- | ------- |
| `**kwargs` | `Any` | Keyword arguments passed by the receipt_registry. chain: Chain name for token decimal resolution. | `{}`    |

#### parse_swap_receipt

```
parse_swap_receipt(
    receipt: dict[str, Any],
    wallet_address: str,
    token_out: str,
    token_in: str | None = None,
    expected_amount_out: int | None = None,
) -> SwapResult
```

Parse a swap transaction receipt.

Parameters:

| Name                  | Type             | Description                             | Default                               |
| --------------------- | ---------------- | --------------------------------------- | ------------------------------------- |
| `receipt`             | `dict[str, Any]` | Transaction receipt from web3           | *required*                            |
| `wallet_address`      | `str`            | Address that received the output tokens | *required*                            |
| `token_out`           | `str`            | Output token address                    | *required*                            |
| `token_in`            | \`str            | None\`                                  | Input token address (optional)        |
| `expected_amount_out` | \`int            | None\`                                  | Expected output amount for validation |

Returns:

| Type         | Description                 |
| ------------ | --------------------------- |
| `SwapResult` | SwapResult with parsed data |

#### extract_swap_amounts

```
extract_swap_amounts(
    receipt: dict[str, Any],
) -> SwapAmounts | None
```

Extract swap amounts from an Enso swap receipt.

Called by ResultEnricher for SWAP intents. Parses ERC-20 Transfer events to determine the input and output token amounts.

The heuristic:

- amount_in: first Transfer FROM the wallet (tx sender)
- amount_out: last Transfer TO the wallet (final output after routing)

Parameters:

| Name      | Type             | Description                                            | Default    |
| --------- | ---------------- | ------------------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' and 'from' fields | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`SwapAmounts | None\`      |

#### parse_approval_receipt

```
parse_approval_receipt(
    receipt: dict[str, Any],
) -> dict[str, Any]
```

Parse an approval transaction receipt.

Parameters:

| Name      | Type             | Description                   | Default    |
| --------- | ---------------- | ----------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt from web3 | *required* |

Returns:

| Type             | Description               |
| ---------------- | ------------------------- |
| `dict[str, Any]` | Dict with approval result |

# Ethena

Connector for Ethena yield protocol (USDe/sUSDe).

## almanak.framework.connectors.ethena

Ethena Connector.

This module provides an adapter for interacting with Ethena synthetic dollar protocol.

Ethena is a synthetic dollar protocol supporting:

- Stake USDe to receive sUSDe (yield-bearing)
- Unstake sUSDe to receive USDe (with cooldown period)

Supported chains:

- Ethereum (full staking + unstaking)

sUSDe is an ERC4626 vault token that accrues yield from delta-neutral strategies.

Example

from almanak.framework.connectors.ethena import EthenaAdapter, EthenaConfig

config = EthenaConfig( chain="ethereum", wallet_address="0x...", ) adapter = EthenaAdapter(config)

### Stake USDe to receive sUSDe

result = adapter.stake_usde(amount=Decimal("1000.0"))

### EthenaAdapter

```
EthenaAdapter(
    config: EthenaConfig,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Ethena synthetic dollar protocol.

This adapter provides methods for interacting with Ethena:

- Stake USDe to receive sUSDe (yield-bearing vault token)
- Unstake sUSDe to receive USDe (requires cooldown period)

Note: sUSDe is an ERC4626 vault. Unstaking has a cooldown period (typically 7 days) before assets can be withdrawn.

Example

config = EthenaConfig( chain="ethereum", wallet_address="0x...", ) adapter = EthenaAdapter(config)

#### Stake USDe to get sUSDe

result = adapter.stake_usde(Decimal("1000.0"))

#### Start cooldown for unstaking

result = adapter.unstake_susde(Decimal("1000.0"))

Initialize the adapter.

Parameters:

| Name             | Type            | Description           | Default                                                   |
| ---------------- | --------------- | --------------------- | --------------------------------------------------------- |
| `config`         | `EthenaConfig`  | Adapter configuration | *required*                                                |
| `token_resolver` | \`TokenResolver | None\`                | Optional TokenResolver instance. If None, uses singleton. |

#### approve_usde

```
approve_usde(amount: Decimal) -> TransactionResult
```

Build an approval transaction for USDe to sUSDe contract.

This must be called before staking to allow the sUSDe contract to transfer USDe from the user's wallet.

Parameters:

| Name     | Type      | Description               | Default    |
| -------- | --------- | ------------------------- | ---------- |
| `amount` | `Decimal` | Amount of USDe to approve | *required* |

Returns:

| Type                | Description                                      |
| ------------------- | ------------------------------------------------ |
| `TransactionResult` | TransactionResult with approval transaction data |

#### stake_usde

```
stake_usde(
    amount: Decimal, receiver: str | None = None
) -> TransactionResult
```

Build a stake transaction to deposit USDe and receive sUSDe.

Deposits USDe into the sUSDe ERC4626 vault to receive yield-bearing sUSDe.

Parameters:

| Name       | Type      | Description             | Default                                            |
| ---------- | --------- | ----------------------- | -------------------------------------------------- |
| `amount`   | `Decimal` | Amount of USDe to stake | *required*                                         |
| `receiver` | \`str     | None\`                  | Address to receive sUSDe (default: wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### unstake_susde

```
unstake_susde(amount: Decimal) -> TransactionResult
```

Build a transaction to start cooldown for unstaking sUSDe.

Initiates the cooldown period for unstaking sUSDe. After the cooldown period (typically 7 days), the USDe can be withdrawn.

Parameters:

| Name     | Type      | Description                                       | Default    |
| -------- | --------- | ------------------------------------------------- | ---------- |
| `amount` | `Decimal` | Amount of sUSDe assets to unstake (in USDe terms) | *required* |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### complete_unstake

```
complete_unstake(
    receiver: str | None = None,
) -> TransactionResult
```

Build a transaction to complete unstaking after cooldown period.

Completes the unstaking process after the cooldown period (typically 7 days) has elapsed. This withdraws the previously locked USDe to the receiver.

Parameters:

| Name       | Type  | Description | Default                                               |
| ---------- | ----- | ----------- | ----------------------------------------------------- |
| `receiver` | \`str | None\`      | Address to receive the USDe (default: wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### compile_stake_intent

```
compile_stake_intent(
    intent: StakeIntent, market_snapshot: Any | None = None
) -> ActionBundle
```

Compile a StakeIntent to an ActionBundle.

This method converts a high-level StakeIntent into executable transaction data. For Ethena, this stakes USDe to receive sUSDe.

Parameters:

| Name              | Type          | Description                | Default                                    |
| ----------------- | ------------- | -------------------------- | ------------------------------------------ |
| `intent`          | `StakeIntent` | The StakeIntent to compile | *required*                                 |
| `market_snapshot` | \`Any         | None\`                     | Optional market data (not used for Ethena) |

Returns:

| Type           | Description                                          |
| -------------- | ---------------------------------------------------- |
| `ActionBundle` | ActionBundle containing transaction(s) for execution |

Raises:

| Type         | Description                                        |
| ------------ | -------------------------------------------------- |
| `ValueError` | If amount="all" is not resolved before compilation |

#### compile_unstake_intent

```
compile_unstake_intent(
    intent: UnstakeIntent,
    market_snapshot: Any | None = None,
) -> ActionBundle
```

Compile an UnstakeIntent to an ActionBundle.

This method converts a high-level UnstakeIntent into executable transaction data. For Ethena, this supports two phases via `protocol_params`:

Phase 1 (default, `protocol_params={"phase": "cooldown"}`): Initiates the 7-day cooldown by calling `cooldownAssets(uint256)`. Phase 2 (`protocol_params={"phase": "complete"}`): Completes the withdrawal after cooldown by calling `unstake(address)`. Only valid after cooldown has elapsed (use Anvil `evm_increaseTime` for testing).

Parameters:

| Name              | Type            | Description                  | Default                                    |
| ----------------- | --------------- | ---------------------------- | ------------------------------------------ |
| `intent`          | `UnstakeIntent` | The UnstakeIntent to compile | *required*                                 |
| `market_snapshot` | \`Any           | None\`                       | Optional market data (not used for Ethena) |

Returns:

| Type           | Description                                          |
| -------------- | ---------------------------------------------------- |
| `ActionBundle` | ActionBundle containing transaction(s) for execution |

Raises:

| Type         | Description                                        |
| ------------ | -------------------------------------------------- |
| `ValueError` | If amount="all" is not resolved before compilation |

### EthenaConfig

```
EthenaConfig(chain: str, wallet_address: str)
```

Configuration for Ethena adapter.

Attributes:

| Name             | Type  | Description                   |
| ---------------- | ----- | ----------------------------- |
| `chain`          | `str` | Blockchain network (ethereum) |
| `wallet_address` | `str` | User wallet address           |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

### TransactionResult

```
TransactionResult(
    success: bool,
    tx_data: dict[str, Any] | None = None,
    gas_estimate: int = 0,
    description: str = "",
    error: str | None = None,
)
```

Result of a transaction build operation.

Attributes:

| Name           | Type             | Description                 |
| -------------- | ---------------- | --------------------------- |
| `success`      | `bool`           | Whether operation succeeded |
| `tx_data`      | \`dict[str, Any] | None\`                      |
| `gas_estimate` | `int`            | Estimated gas               |
| `description`  | `str`            | Human-readable description  |
| `error`        | \`str            | None\`                      |

### EthenaEventType

Bases: `Enum`

Ethena event types.

### EthenaReceiptParser

```
EthenaReceiptParser(chain: str = 'ethereum', **kwargs: Any)
```

Parser for Ethena transaction receipts.

Refactored to use base infrastructure utilities for hex decoding and event registry management.

Initialize the parser.

Parameters:

| Name       | Type  | Description                                      | Default      |
| ---------- | ----- | ------------------------------------------------ | ------------ |
| `chain`    | `str` | Blockchain network (ethereum)                    | `'ethereum'` |
| `**kwargs` | `Any` | Additional arguments (ignored for compatibility) | `{}`         |

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name      | Type             | Description              | Default    |
| --------- | ---------------- | ------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict | *required* |

Returns:

| Type          | Description                       |
| ------------- | --------------------------------- |
| `ParseResult` | ParseResult with extracted events |

#### parse_stake

```
parse_stake(log: dict[str, Any]) -> StakeEventData | None
```

Parse a Deposit (stake) event from a single log entry.

#### parse_withdraw

```
parse_withdraw(
    log: dict[str, Any],
) -> WithdrawEventData | None
```

Parse a Withdraw event from a single log entry.

#### parse_unstake

```
parse_unstake(
    log: dict[str, Any],
) -> WithdrawEventData | None
```

Backward compatibility alias for parse_withdraw.

#### is_ethena_event

```
is_ethena_event(topic: str | bytes) -> bool
```

Check if a topic is a known Ethena event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                           |
| ------ | ------------------------------------- |
| `bool` | True if topic is a known Ethena event |

#### get_event_type

```
get_event_type(topic: str | bytes) -> EthenaEventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type              | Description           |
| ----------------- | --------------------- |
| `EthenaEventType` | Event type or UNKNOWN |

#### extract_stake_amount

```
extract_stake_amount(receipt: dict[str, Any]) -> int | None
```

Extract stake amount (assets deposited) from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_shares_received

```
extract_shares_received(
    receipt: dict[str, Any],
) -> int | None
```

Extract sUSDe shares received from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_unstake_amount

```
extract_unstake_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract unstake amount (shares burned) from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_underlying_received

```
extract_underlying_received(
    receipt: dict[str, Any],
) -> int | None
```

Extract underlying USDe received from withdrawal.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

### ParseResult

```
ParseResult(
    success: bool,
    stakes: list[StakeEventData] = list(),
    withdraws: list[WithdrawEventData] = list(),
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
)
```

Result of parsing a receipt.

#### unstakes

```
unstakes: list[WithdrawEventData]
```

Backward compatibility alias for withdraws.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### StakeEventData

```
StakeEventData(
    sender: str,
    owner: str,
    assets: Decimal,
    shares: Decimal,
)
```

Parsed data from Deposit event (stake operation).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### WithdrawEventData

```
WithdrawEventData(
    sender: str,
    receiver: str,
    owner: str,
    assets: Decimal,
    shares: Decimal,
)
```

Parsed data from Withdraw event (cooldown or final withdrawal).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

# Flash Loan

Connector for flash loan utilities.

## almanak.framework.connectors.flash_loan

Flash Loan Module.

This module provides flash loan adapters and the FlashLoanSelector for automatic provider selection based on liquidity, fees, and token availability.

Supported providers:

- Aave V3: Most widely supported, 0.09% fee
- Balancer: Zero fees, limited token availability

Example

from almanak.framework.connectors.flash_loan import FlashLoanSelector

selector = FlashLoanSelector(chain="arbitrum")

### Select best provider for USDC flash loan

result = selector.select_provider( token="USDC", amount=Decimal("1000000"), )

print(f"Selected: {result.provider}, fee: {result.fee_amount}")

### FlashLoanProviderInfo

```
FlashLoanProviderInfo(
    provider: str,
    is_available: bool = False,
    fee_bps: int = 0,
    fee_amount: Decimal = Decimal("0"),
    estimated_liquidity_usd: int = 0,
    gas_estimate: int = 0,
    pool_address: str = "",
    reliability_score: float = 0.5,
    score: float = 1.0,
    unavailable_reason: str | None = None,
)
```

Information about a flash loan provider for a specific token.

Attributes:

| Name                      | Type      | Description                                            |
| ------------------------- | --------- | ------------------------------------------------------ |
| `provider`                | `str`     | Provider name ("aave" or "balancer")                   |
| `is_available`            | `bool`    | Whether provider supports this token on this chain     |
| `fee_bps`                 | `int`     | Flash loan fee in basis points                         |
| `fee_amount`              | `Decimal` | Calculated fee amount for the requested loan           |
| `estimated_liquidity_usd` | `int`     | Estimated available liquidity in USD                   |
| `gas_estimate`            | `int`     | Estimated gas for flash loan (base, without callbacks) |
| `pool_address`            | `str`     | Contract address for flash loan                        |
| `reliability_score`       | `float`   | Historical reliability (0-1)                           |
| `score`                   | `float`   | Calculated overall score (lower is better)             |
| `unavailable_reason`      | \`str     | None\`                                                 |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### FlashLoanSelectionResult

```
FlashLoanSelectionResult(
    provider: str | None = None,
    pool_address: str = "",
    fee_bps: int = 0,
    fee_amount: Decimal = Decimal("0"),
    total_repay: Decimal = Decimal("0"),
    gas_estimate: int = 0,
    providers_evaluated: list[
        FlashLoanProviderInfo
    ] = list(),
    selection_reasoning: str = "",
)
```

Result of flash loan provider selection.

Attributes:

| Name                  | Type                          | Description                                   |
| --------------------- | ----------------------------- | --------------------------------------------- |
| `provider`            | \`str                         | None\`                                        |
| `pool_address`        | `str`                         | Contract address to call for flash loan       |
| `fee_bps`             | `int`                         | Fee in basis points for the selected provider |
| `fee_amount`          | `Decimal`                     | Calculated fee amount for the requested loan  |
| `total_repay`         | `Decimal`                     | Total amount to repay (loan + fee)            |
| `gas_estimate`        | `int`                         | Estimated gas for the flash loan              |
| `providers_evaluated` | `list[FlashLoanProviderInfo]` | Information about all evaluated providers     |
| `selection_reasoning` | `str`                         | Human-readable explanation of selection       |

#### is_success

```
is_success: bool
```

Check if a provider was successfully selected.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### FlashLoanSelector

```
FlashLoanSelector(
    chain: str,
    reliability_scores: dict[str, float] | None = None,
    default_priority: SelectionPriority = SelectionPriority.FEE,
)
```

Selects optimal flash loan provider based on configurable criteria.

The selector evaluates Aave V3 and Balancer for the requested token and amount, selecting the best provider based on:

- Fee: Balancer has zero fees, Aave charges 0.09%
- Liquidity: Check if provider has sufficient liquidity
- Reliability: Aave is more battle-tested
- Gas: Balancer is slightly cheaper on gas

Example

selector = FlashLoanSelector(chain="arbitrum")

#### Select provider for 1M USDC flash loan

result = selector.select_provider( token="USDC", amount=Decimal("1000000"), priority="fee", # Will prefer Balancer (zero fee) )

if result.is_success: print(f"Use {result.provider} at {result.pool_address}") print(f"Fee: {result.fee_amount} ({result.fee_bps} bps)")

Initialize the flash loan selector.

Parameters:

| Name                 | Type                | Description                                                     | Default                                         |
| -------------------- | ------------------- | --------------------------------------------------------------- | ----------------------------------------------- |
| `chain`              | `str`               | Target blockchain (ethereum, arbitrum, optimism, polygon, base) | *required*                                      |
| `reliability_scores` | \`dict[str, float]  | None\`                                                          | Optional custom reliability scores per provider |
| `default_priority`   | `SelectionPriority` | Default selection priority                                      | `FEE`                                           |

#### select_provider

```
select_provider(
    token: str,
    amount: Decimal,
    priority: str | None = None,
    min_liquidity_usd: int = 0,
) -> FlashLoanSelectionResult
```

Select the optimal flash loan provider.

Evaluates Aave and Balancer for the requested token and amount, returning the best provider based on the priority criteria.

Parameters:

| Name                | Type      | Description                         | Default                                                       |
| ------------------- | --------- | ----------------------------------- | ------------------------------------------------------------- |
| `token`             | `str`     | Token symbol (e.g., "USDC", "WETH") | *required*                                                    |
| `amount`            | `Decimal` | Flash loan amount in token units    | *required*                                                    |
| `priority`          | \`str     | None\`                              | Selection priority ("fee", "liquidity", "reliability", "gas") |
| `min_liquidity_usd` | `int`     | Minimum required liquidity in USD   | `0`                                                           |

Returns:

| Type                       | Description                                             |
| -------------------------- | ------------------------------------------------------- |
| `FlashLoanSelectionResult` | FlashLoanSelectionResult with selected provider details |

Raises:

| Type                       | Description                              |
| -------------------------- | ---------------------------------------- |
| `NoProviderAvailableError` | If no provider supports the token/amount |

#### get_provider_info

```
get_provider_info(
    provider: str, token: str, amount: Decimal
) -> FlashLoanProviderInfo
```

Get information about a specific provider for a token.

Parameters:

| Name       | Type      | Description                          | Default    |
| ---------- | --------- | ------------------------------------ | ---------- |
| `provider` | `str`     | Provider name ("aave" or "balancer") | *required* |
| `token`    | `str`     | Token symbol                         | *required* |
| `amount`   | `Decimal` | Flash loan amount                    | *required* |

Returns:

| Type                    | Description                            |
| ----------------------- | -------------------------------------- |
| `FlashLoanProviderInfo` | FlashLoanProviderInfo for the provider |

#### is_token_supported

```
is_token_supported(
    token: str, provider: str | None = None
) -> bool
```

Check if a token is supported for flash loans.

Parameters:

| Name       | Type  | Description  | Default                             |
| ---------- | ----- | ------------ | ----------------------------------- |
| `token`    | `str` | Token symbol | *required*                          |
| `provider` | \`str | None\`       | Optional specific provider to check |

Returns:

| Type   | Description                |
| ------ | -------------------------- |
| `bool` | True if token is supported |

#### get_supported_tokens

```
get_supported_tokens(
    provider: str | None = None,
) -> set[str]
```

Get set of tokens supported for flash loans.

Parameters:

| Name       | Type  | Description | Default                             |
| ---------- | ----- | ----------- | ----------------------------------- |
| `provider` | \`str | None\`      | Optional specific provider to check |

Returns:

| Type       | Description                    |
| ---------- | ------------------------------ |
| `set[str]` | Set of supported token symbols |

#### estimate_liquidity

```
estimate_liquidity(
    token: str, provider: str | None = None
) -> dict[str, int]
```

Estimate available liquidity for a token.

Parameters:

| Name       | Type  | Description  | Default                    |
| ---------- | ----- | ------------ | -------------------------- |
| `token`    | `str` | Token symbol | *required*                 |
| `provider` | \`str | None\`       | Optional specific provider |

Returns:

| Type             | Description                                              |
| ---------------- | -------------------------------------------------------- |
| `dict[str, int]` | Dict mapping provider name to estimated liquidity in USD |

### FlashLoanSelectorError

Bases: `Exception`

Base exception for flash loan selector errors.

### NoProviderAvailableError

Bases: `FlashLoanSelectorError`

Raised when no provider can fulfill the flash loan request.

### SelectionPriority

Bases: `Enum`

Priority for flash loan provider selection.

Attributes:

| Name          | Type | Description                                              |
| ------------- | ---- | -------------------------------------------------------- |
| `FEE`         |      | Minimize flash loan fees (Balancer preferred - zero fee) |
| `LIQUIDITY`   |      | Prefer providers with deeper liquidity                   |
| `RELIABILITY` |      | Prefer more battle-tested providers (Aave preferred)     |
| `GAS`         |      | Minimize gas costs                                       |

# Fluid DEX

Connector for Fluid DEX protocol.

## almanak.framework.connectors.fluid

Fluid DEX Connector — Phase 1 (Arbitrum swaps + LP scaffolding).

Provides swap support for Fluid DEX T1 pools on Arbitrum via swapIn(). LP open/close intent routing is wired but LP deposit reverts on-chain (phase 2).

Scope (phase 1):

- Arbitrum only
- Swaps via swapIn() (fully functional)
- LP deposit deferred (Liquidity-layer routing causes reverts)

Key contracts (Arbitrum):

- DexFactory: 0x91716C4EDA1Fb55e84Bf8b4c7085f84285c19085
- DexResolver: 0x11D80CfF056Cef4F9E6d23da8672fE9873e5cC07

Example

from almanak.framework.connectors.fluid import FluidAdapter, FluidConfig

config = FluidConfig( chain="arbitrum", wallet_address="0x...", rpc_url="https://arb-mainnet.g.alchemy.com/v2/...", ) adapter = FluidAdapter(config)

### FluidAdapter

```
FluidAdapter(
    config: FluidConfig,
    token_resolver: TokenResolver | None = None,
)
```

High-level adapter for Fluid DEX LP operations.

Provides LP open/close with compile-time encumbrance guard. Phase 1 operates only on unencumbered pools (no smart-debt/collateral).

Parameters:

| Name             | Type            | Description                                      | Default                                                 |
| ---------------- | --------------- | ------------------------------------------------ | ------------------------------------------------------- |
| `config`         | `FluidConfig`   | FluidConfig with chain, wallet, and RPC settings | *required*                                              |
| `token_resolver` | \`TokenResolver | None\`                                           | Optional TokenResolver for symbol -> address resolution |

#### resolve_token_address

```
resolve_token_address(token: str) -> str
```

Resolve a token symbol or address to checksummed address.

Parameters:

| Name    | Type  | Description                            | Default    |
| ------- | ----- | -------------------------------------- | ---------- |
| `token` | `str` | Token symbol (e.g., "USDC") or address | *required* |

Returns:

| Type  | Description         |
| ----- | ------------------- |
| `str` | Checksummed address |

#### get_token_decimals

```
get_token_decimals(token: str) -> int
```

Get decimals for a token.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `token` | `str` | Token symbol or address | *required* |

Returns:

| Type  | Description                                               |
| ----- | --------------------------------------------------------- |
| `int` | Token decimals (never defaults to 18 — raises if unknown) |

#### find_pool

```
find_pool(token0: str, token1: str) -> str | None
```

Find a Fluid DEX pool for a token pair.

Parameters:

| Name     | Type  | Description                    | Default    |
| -------- | ----- | ------------------------------ | ---------- |
| `token0` | `str` | First token symbol or address  | *required* |
| `token1` | `str` | Second token symbol or address | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

#### get_pool_data

```
get_pool_data(dex_address: str) -> DexPoolData
```

Get full pool data from the resolver.

Parameters:

| Name          | Type  | Description           | Default    |
| ------------- | ----- | --------------------- | ---------- |
| `dex_address` | `str` | Pool contract address | *required* |

Returns:

| Type          | Description                                               |
| ------------- | --------------------------------------------------------- |
| `DexPoolData` | DexPoolData with configs, reserves, and encumbrance flags |

#### build_add_liquidity_transaction

```
build_add_liquidity_transaction(
    dex_address: str,
    amount0: Decimal,
    amount1: Decimal,
    token0_decimals: int,
    token1_decimals: int,
) -> TransactionData
```

Build a transaction to open a new LP position.

Phase 1 limitation: Fluid DEX deposit() reverts on all pools due to complex Liquidity-layer routing. LP open is wired but not functional on-chain. Proper share calculation requires Liquidity-layer integration (follow-up).

Raises:

| Type            | Description                               |
| --------------- | ----------------------------------------- |
| `FluidSDKError` | Always — LP deposit is not yet supported. |

#### build_remove_liquidity_transaction

```
build_remove_liquidity_transaction(
    dex_address: str, nft_id: int
) -> TransactionData
```

Build a transaction to close an LP position (full withdrawal).

Calls operate(nftId, -MAX_INT256, 0, wallet) on the pool contract. The negative max collateral delta means "withdraw everything".

ENCUMBRANCE GUARD: This method refuses to build the transaction if the pool has smart-collateral or smart-debt enabled.

Parameters:

| Name          | Type  | Description              | Default    |
| ------------- | ----- | ------------------------ | ---------- |
| `dex_address` | `str` | Pool contract address    | *required* |
| `nft_id`      | `int` | NFT position ID to close | *required* |

Returns:

| Type              | Description                             |
| ----------------- | --------------------------------------- |
| `TransactionData` | TransactionData with the operate() call |

Raises:

| Type            | Description            |
| --------------- | ---------------------- |
| `FluidSDKError` | If the operation fails |

#### get_position_details

```
get_position_details(
    nft_id: int, dex_address: str
) -> FluidPositionDetails
```

Build FluidPositionDetails for a position.

Reads pool data from the resolver to populate APR fields.

Parameters:

| Name          | Type  | Description           | Default    |
| ------------- | ----- | --------------------- | ---------- |
| `nft_id`      | `int` | NFT position ID       | *required* |
| `dex_address` | `str` | Pool contract address | *required* |

Returns:

| Type                   | Description                                      |
| ---------------------- | ------------------------------------------------ |
| `FluidPositionDetails` | FluidPositionDetails with pool data and APR info |

#### build_approve_tx

```
build_approve_tx(
    token_address: str,
    spender: str,
    amount: int | None = None,
) -> TransactionData
```

Build an ERC20 approval transaction.

Parameters:

| Name            | Type  | Description                     | Default                                |
| --------------- | ----- | ------------------------------- | -------------------------------------- |
| `token_address` | `str` | Token contract address          | *required*                             |
| `spender`       | `str` | Address to approve spending for | *required*                             |
| `amount`        | \`int | None\`                          | Amount to approve (None = max uint256) |

Returns:

| Type              | Description                      |
| ----------------- | -------------------------------- |
| `TransactionData` | TransactionData for the approval |

#### extract_position_id

```
extract_position_id(receipt: dict) -> int | None
```

Extract LP position NFT tokenId from operate() receipt.

Called by ResultEnricher for LP_OPEN intents.

Parameters:

| Name      | Type   | Description              | Default    |
| --------- | ------ | ------------------------ | ---------- |
| `receipt` | `dict` | Transaction receipt dict | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

### FluidConfig

```
FluidConfig(
    chain: str,
    wallet_address: str,
    rpc_url: str,
    default_slippage_bps: int = 50,
)
```

Configuration for Fluid DEX adapter.

Parameters:

| Name                   | Type  | Description                                                     | Default    |
| ---------------------- | ----- | --------------------------------------------------------------- | ---------- |
| `chain`                | `str` | Chain name (must be "arbitrum" for phase 1)                     | *required* |
| `wallet_address`       | `str` | Address of the wallet executing transactions                    | *required* |
| `rpc_url`              | `str` | RPC endpoint URL                                                | *required* |
| `default_slippage_bps` | `int` | Default slippage tolerance in basis points (default: 50 = 0.5%) | `50`       |

### FluidPositionDetails

```
FluidPositionDetails(
    fluid_nft_id: str,
    dex_address: str,
    token0: str,
    token1: str,
    swap_fee_apr: float = 0.0,
    lending_yield_apr: float = 0.0,
    combined_apr: float = 0.0,
    is_smart_collateral: bool = False,
    is_smart_debt: bool = False,
)
```

Typed position details for Fluid DEX LP positions.

Stored in PositionInfo.details as a dict (via asdict()). String NFT ID is consistent with Uniswap V3 / TraderJoe patterns.

Attributes:

| Name                  | Type    | Description                                                 |
| --------------------- | ------- | ----------------------------------------------------------- |
| `fluid_nft_id`        | `str`   | NFT token ID (string for consistency with other connectors) |
| `dex_address`         | `str`   | Pool contract address                                       |
| `token0`              | `str`   | Token0 address                                              |
| `token1`              | `str`   | Token1 address                                              |
| `swap_fee_apr`        | `float` | Swap fee APR (from exchange price data)                     |
| `lending_yield_apr`   | `float` | Lending yield APR (from liquidity resolver)                 |
| `combined_apr`        | `float` | Combined APR (swap_fee_apr + lending_yield_apr)             |
| `is_smart_collateral` | `bool`  | Whether pool has smart collateral enabled                   |
| `is_smart_debt`       | `bool`  | Whether pool has smart debt enabled                         |

### FluidReceiptParser

```
FluidReceiptParser(chain: str = 'arbitrum')
```

Receipt parser for Fluid DEX transactions (swaps and LP operations).

Extracts NFT position IDs and token amounts from LogOperate events. Supports both LP_OPEN (new position) and LP_CLOSE (withdrawal) receipts.

SUPPORTED_EXTRACTIONS declares which ResultEnricher fields this parser supports.

Parameters:

| Name    | Type  | Description                                           | Default      |
| ------- | ----- | ----------------------------------------------------- | ------------ |
| `chain` | `str` | Chain name for token resolution (default: "arbitrum") | `'arbitrum'` |

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> FluidParseResult
```

Parse a Fluid DEX transaction receipt.

Parameters:

| Name      | Type             | Description                                                   | Default    |
| --------- | ---------------- | ------------------------------------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs', 'transactionHash', etc. | *required* |

Returns:

| Type               | Description                                     |
| ------------------ | ----------------------------------------------- |
| `FluidParseResult` | FluidParseResult with extracted events and data |

#### extract_position_id

```
extract_position_id(receipt: dict[str, Any]) -> int | None
```

Extract LP position NFT tokenId from receipt.

Called by ResultEnricher for LP_OPEN intents.

Parameters:

| Name      | Type             | Description              | Default    |
| --------- | ---------------- | ------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_swap_amounts

```
extract_swap_amounts(
    receipt: dict[str, Any],
) -> SwapAmounts | None
```

Extract swap amounts from receipt.

Called by ResultEnricher for SWAP intents. Resolves token decimals to produce human-readable decimal amounts (consistent with other parsers).

Parameters:

| Name      | Type             | Description              | Default    |
| --------- | ---------------- | ------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`SwapAmounts | None\`      |

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> LPCloseData | None
```

Extract LP close data from receipt.

Called by ResultEnricher for LP_CLOSE intents. Returns amounts collected on withdrawal (negative amounts = withdrawn).

Parameters:

| Name      | Type             | Description              | Default    |
| --------- | ---------------- | ------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`LPCloseData | None\`      |

#### extract_liquidity

```
extract_liquidity(receipt: dict[str, Any]) -> int | None
```

Extract liquidity amount from LP_OPEN receipt.

For Fluid DEX, liquidity is represented by the collateral shares (token0_amt + token1_amt deposited).

Parameters:

| Name      | Type             | Description              | Default    |
| --------- | ---------------- | ------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

### FluidSDK

```
FluidSDK(chain: str, rpc_url: str)
```

Low-level Fluid DEX protocol SDK.

Reads pool data directly from pool contracts using raw eth_call:

- constantsView(): token addresses (words 9, 10 of 18-word response)
- readFromStorage(bytes32(0)): dexVariables with smart-collateral/debt flags

Parameters:

| Name      | Type  | Description                                 | Default    |
| --------- | ----- | ------------------------------------------- | ---------- |
| `chain`   | `str` | Chain name (must be "arbitrum" for phase 1) | *required* |
| `rpc_url` | `str` | RPC endpoint URL                            | *required* |

#### get_all_dex_addresses

```
get_all_dex_addresses() -> list[str]
```

Get all Fluid DEX pool addresses from the resolver.

#### get_total_dexes

```
get_total_dexes() -> int
```

Get the total number of Fluid DEX pools.

#### get_dex_data

```
get_dex_data(dex_address: str) -> DexPoolData
```

Get pool data by calling constantsView() and readFromStorage() directly.

Uses raw eth_call to avoid ABI decoding issues with the complex DexEntireData struct. constantsView() returns 18 words:

- word\[9\]: token0 address
- word\[10\]: token1 address

readFromStorage(bytes32(0)) returns dexVariables:

- bit 1: isSmartCollateralEnabled
- bit 2: isSmartDebtEnabled

Parameters:

| Name          | Type  | Description           | Default    |
| ------------- | ----- | --------------------- | ---------- |
| `dex_address` | `str` | Pool contract address | *required* |

Returns:

| Type          | Description                                            |
| ------------- | ------------------------------------------------------ |
| `DexPoolData` | DexPoolData with token addresses and encumbrance flags |

#### is_position_encumbered

```
is_position_encumbered(
    dex_address: str, nft_id: int = 0
) -> bool
```

Check if a specific position has outstanding debt.

In Fluid DEX, ALL pools have smart-debt capability (that's the design). The encumbrance check is at the POSITION level, not pool level:

- Positions we create with newDebt=0 have no debt, so they're safe to close.
- For safety, we verify the pool's smart-debt flag but don't block on it for positions we know were opened without debt.

For phase 1, this always returns False for nft_id=0 (new position check) since we enforce newDebt=0 in build_operate_tx().

Parameters:

| Name          | Type  | Description                                     | Default    |
| ------------- | ----- | ----------------------------------------------- | ---------- |
| `dex_address` | `str` | Pool contract address                           | *required* |
| `nft_id`      | `int` | NFT position ID (0 = checking for new position) | `0`        |

Returns:

| Type   | Description                               |
| ------ | ----------------------------------------- |
| `bool` | True if the position has outstanding debt |

#### find_dex_by_tokens

```
find_dex_by_tokens(token0: str, token1: str) -> str | None
```

Find a Fluid DEX pool for a given token pair.

Token order is automatically handled (tries both orderings).

#### get_swap_quote

```
get_swap_quote(
    dex_address: str,
    swap0to1: bool,
    amount_in: int,
    to: str,
) -> int
```

Get a swap quote (estimate) from a Fluid DEX pool.

Calls swapIn via eth_call with state overrides to simulate token approval and balance. Without overrides, swapIn() reverts because it internally calls transferFrom() which requires prior approval.

Parameters:

| Name          | Type   | Description                                           | Default    |
| ------------- | ------ | ----------------------------------------------------- | ---------- |
| `dex_address` | `str`  | Pool contract address                                 | *required* |
| `swap0to1`    | `bool` | True to swap token0->token1, False for token1->token0 | *required* |
| `amount_in`   | `int`  | Input amount in token's smallest unit                 | *required* |
| `to`          | `str`  | Recipient address                                     | *required* |

Returns:

| Type  | Description                                     |
| ----- | ----------------------------------------------- |
| `int` | Expected output amount in token's smallest unit |

#### build_swap_tx

```
build_swap_tx(
    dex_address: str,
    swap0to1: bool,
    amount_in: int,
    amount_out_min: int,
    to: str,
    value: int = 0,
) -> dict[str, Any]
```

Build a swapIn transaction for a Fluid DEX pool.

Parameters:

| Name             | Type   | Description                                           | Default    |
| ---------------- | ------ | ----------------------------------------------------- | ---------- |
| `dex_address`    | `str`  | Pool contract address                                 | *required* |
| `swap0to1`       | `bool` | True to swap token0->token1, False for token1->token0 | *required* |
| `amount_in`      | `int`  | Input amount in token's smallest unit                 | *required* |
| `amount_out_min` | `int`  | Minimum acceptable output (slippage protection)       | *required* |
| `to`             | `str`  | Recipient address                                     | *required* |
| `value`          | `int`  | Native token value (for ETH-paired swaps)             | `0`        |

Returns:

| Type             | Description                                        |
| ---------------- | -------------------------------------------------- |
| `dict[str, Any]` | Transaction dict with 'to', 'data', 'value', 'gas' |

#### build_operate_tx

```
build_operate_tx(
    dex_address: str,
    nft_id: int,
    new_col: int,
    new_debt: int,
    to: str,
) -> dict[str, Any]
```

Build an operate() transaction for a Fluid DEX pool.

operate() is the main entry point for LP operations:

- Open position: nft_id=0, new_col>0, new_debt=0
- Close position: nft_id=X, new_col\<0 (negative = withdraw), new_debt=0

# GMX V2

Connector for GMX V2 perpetual futures on Arbitrum and Avalanche.

## Overview

GMX V2 is a decentralized perpetuals exchange that supports leveraged long and short positions on major crypto assets. The Almanak SDK integrates GMX V2 through the intent system, supporting `PERP_OPEN` and `PERP_CLOSE` operations.

## Market Format

GMX V2 markets use a **slash separator**: `"BTC/USD"`, `"ETH/USD"`, `"LINK/USD"`.

```
Intent.perp_open(
    market="ETH/USD",       # Slash separator, not dash
    collateral_token="USDC",
    collateral_amount=Decimal("1000"),
    size_usd=Decimal("5000"),
    is_long=True,
    leverage=Decimal("5"),
    protocol="gmx_v2",
)
```

## Supported Operations

| Intent                | Description                                  |
| --------------------- | -------------------------------------------- |
| `Intent.perp_open()`  | Open a leveraged long or short position      |
| `Intent.perp_close()` | Close an existing position (full or partial) |

## Keeper Execution Model

GMX V2 uses a **two-step execution model**:

1. **Order creation**: Your transaction submits an order to the GMX exchange router. This is the transaction the SDK signs and submits.
1. **Keeper execution**: A GMX keeper bot picks up the order and executes the actual position change in a separate transaction.

**Important implications for strategies:**

- `on_intent_executed(success=True)` fires when the order creation TX confirms (step 1), **not** when the keeper executes the position (step 2).
- There is a delay (typically a few seconds) between order creation and keeper execution.
- `get_all_positions()` may not reflect the new position immediately after `on_intent_executed` fires. Poll position state before relying on it.

## Minimum Position Size

GMX V2 enforces a minimum position size of approximately **$11 net of fees**. Orders below this threshold are silently rejected by the keeper with no on-chain error. The order creation TX will still succeed, but the keeper will not execute the position.

## Collateral Tokens

Collateral tokens vary by chain:

| Chain     | Supported Collateral |
| --------- | -------------------- |
| Arbitrum  | USDC, USDT           |
| Avalanche | USDC, USDT           |

Collateral token approvals are handled automatically by the intent compiler.

## Known Limitations

- **Keeper delay**: Position state is not immediately available after order creation. Allow a few seconds before querying positions.
- **Silent rejections**: Orders below the minimum size are rejected without an on-chain error. Verify position creation by checking position state after the keeper delay.
- **Price impact**: Large positions relative to pool open interest may experience significant price impact. GMX V2 uses a price impact model that charges more for positions that increase imbalance.

## API Reference

## almanak.framework.connectors.gmx_v2

GMX v2 Connector.

This module provides an adapter for interacting with GMX v2 perpetuals protocol, supporting position management, order execution, and event parsing.

GMX v2 is a decentralized perpetual exchange supporting:

- Long and short positions with leverage
- Multiple collateral types
- Limit and market orders
- Position sizing and management

Supported chains:

- Arbitrum
- Avalanche

Example

from almanak.framework.connectors.gmx_v2 import GMXv2Adapter, GMXv2Config

config = GMXv2Config( chain="arbitrum", wallet_address="0x...", ) adapter = GMXv2Adapter(config)

### Open a position

result = adapter.open_position( market="ETH/USD", collateral_token="USDC", collateral_amount=Decimal("1000"), size_delta_usd=Decimal("5000"), is_long=True, )

### GMXv2Adapter

```
GMXv2Adapter(
    config: GMXv2Config,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for GMX v2 perpetuals protocol.

This adapter provides methods for:

- Opening and closing positions
- Increasing and decreasing position size
- Managing limit orders and stop losses
- Querying position and market data
- Parsing transaction receipts

Example

config = GMXv2Config( chain="arbitrum", wallet_address="0x...", ) adapter = GMXv2Adapter(config)

#### Open a long position

result = adapter.open_position( market="ETH/USD", collateral_token="USDC", collateral_amount=Decimal("1000"), size_delta_usd=Decimal("5000"), is_long=True, )

#### Check position

position = adapter.get_position( market="ETH/USD", collateral_token="USDC", is_long=True, )

#### Close position

result = adapter.close_position( market="ETH/USD", collateral_token="USDC", is_long=True, size_delta_usd=position.size_in_usd, )

Initialize the adapter.

Parameters:

| Name             | Type            | Description                  | Default                                                   |
| ---------------- | --------------- | ---------------------------- | --------------------------------------------------------- |
| `config`         | `GMXv2Config`   | GMX v2 adapter configuration | *required*                                                |
| `token_resolver` | \`TokenResolver | None\`                       | Optional TokenResolver instance. If None, uses singleton. |

#### open_position

```
open_position(
    market: str,
    collateral_token: str,
    collateral_amount: Decimal,
    size_delta_usd: Decimal,
    is_long: bool,
    acceptable_price: Decimal | None = None,
    trigger_price: Decimal | None = None,
) -> OrderResult
```

Open a new position or increase existing position.

Parameters:

| Name                | Type      | Description                                           | Default                                           |
| ------------------- | --------- | ----------------------------------------------------- | ------------------------------------------------- |
| `market`            | `str`     | Market identifier (e.g., "ETH/USD") or market address | *required*                                        |
| `collateral_token`  | `str`     | Token symbol or address for collateral                | *required*                                        |
| `collateral_amount` | `Decimal` | Amount of collateral in token decimals                | *required*                                        |
| `size_delta_usd`    | `Decimal` | Position size in USD (will be scaled to 30 decimals)  | *required*                                        |
| `is_long`           | `bool`    | True for long, False for short                        | *required*                                        |
| `acceptable_price`  | \`Decimal | None\`                                                | Maximum (long) or minimum (short) execution price |
| `trigger_price`     | \`Decimal | None\`                                                | Trigger price for limit orders                    |

Returns:

| Type          | Description                    |
| ------------- | ------------------------------ |
| `OrderResult` | OrderResult with order details |

#### close_position

```
close_position(
    market: str,
    collateral_token: str,
    is_long: bool,
    size_delta_usd: Decimal | None = None,
    receive_token: str | None = None,
    acceptable_price: Decimal | None = None,
    trigger_price: Decimal | None = None,
) -> OrderResult
```

Close a position or decrease position size.

Parameters:

| Name               | Type      | Description                            | Default                                               |
| ------------------ | --------- | -------------------------------------- | ----------------------------------------------------- |
| `market`           | `str`     | Market identifier or address           | *required*                                            |
| `collateral_token` | `str`     | Token symbol or address for collateral | *required*                                            |
| `is_long`          | `bool`    | Position direction                     | *required*                                            |
| `size_delta_usd`   | \`Decimal | None\`                                 | Amount to close in USD (None = close entire position) |
| `receive_token`    | \`str     | None\`                                 | Token to receive (defaults to collateral_token)       |
| `acceptable_price` | \`Decimal | None\`                                 | Minimum (long) or maximum (short) execution price     |
| `trigger_price`    | \`Decimal | None\`                                 | Trigger price for limit orders                        |

Returns:

| Type          | Description                    |
| ------------- | ------------------------------ |
| `OrderResult` | OrderResult with order details |

#### increase_position

```
increase_position(
    market: str,
    collateral_token: str,
    is_long: bool,
    collateral_delta: Decimal,
    size_delta_usd: Decimal,
    acceptable_price: Decimal | None = None,
    trigger_price: Decimal | None = None,
) -> OrderResult
```

Increase an existing position.

This is an alias for open_position with an existing position.

Parameters:

| Name               | Type      | Description                  | Default                                           |
| ------------------ | --------- | ---------------------------- | ------------------------------------------------- |
| `market`           | `str`     | Market identifier or address | *required*                                        |
| `collateral_token` | `str`     | Token symbol or address      | *required*                                        |
| `is_long`          | `bool`    | Position direction           | *required*                                        |
| `collateral_delta` | `Decimal` | Additional collateral to add | *required*                                        |
| `size_delta_usd`   | `Decimal` | Additional size in USD       | *required*                                        |
| `acceptable_price` | \`Decimal | None\`                       | Maximum (long) or minimum (short) execution price |
| `trigger_price`    | \`Decimal | None\`                       | Trigger price for limit orders                    |

Returns:

| Type          | Description                    |
| ------------- | ------------------------------ |
| `OrderResult` | OrderResult with order details |

#### decrease_position

```
decrease_position(
    market: str,
    collateral_token: str,
    is_long: bool,
    size_delta_usd: Decimal,
    collateral_delta: Decimal = Decimal("0"),
    receive_token: str | None = None,
    acceptable_price: Decimal | None = None,
    trigger_price: Decimal | None = None,
) -> OrderResult
```

Decrease an existing position.

This is similar to close_position but for partial closes.

Parameters:

| Name               | Type      | Description                  | Default                                           |
| ------------------ | --------- | ---------------------------- | ------------------------------------------------- |
| `market`           | `str`     | Market identifier or address | *required*                                        |
| `collateral_token` | `str`     | Token symbol or address      | *required*                                        |
| `is_long`          | `bool`    | Position direction           | *required*                                        |
| `size_delta_usd`   | `Decimal` | Size to reduce in USD        | *required*                                        |
| `collateral_delta` | `Decimal` | Collateral to withdraw       | `Decimal('0')`                                    |
| `receive_token`    | \`str     | None\`                       | Token to receive                                  |
| `acceptable_price` | \`Decimal | None\`                       | Minimum (long) or maximum (short) execution price |
| `trigger_price`    | \`Decimal | None\`                       | Trigger price for limit orders                    |

Returns:

| Type          | Description                    |
| ------------- | ------------------------------ |
| `OrderResult` | OrderResult with order details |

#### get_position

```
get_position(
    market: str, collateral_token: str, is_long: bool
) -> GMXv2Position | None
```

Get position details.

Parameters:

| Name               | Type   | Description                  | Default    |
| ------------------ | ------ | ---------------------------- | ---------- |
| `market`           | `str`  | Market identifier or address | *required* |
| `collateral_token` | `str`  | Token symbol or address      | *required* |
| `is_long`          | `bool` | Position direction           | *required* |

Returns:

| Type            | Description |
| --------------- | ----------- |
| \`GMXv2Position | None\`      |

#### get_all_positions

```
get_all_positions() -> list[GMXv2Position]
```

Get all open positions from in-memory state.

Returns:

| Type                  | Description                                               |
| --------------------- | --------------------------------------------------------- |
| `list[GMXv2Position]` | List of all open positions (in-memory only, not on-chain) |

#### get_positions_onchain

```
get_positions_onchain(rpc_url: str) -> list[GMXv2Position]
```

Read all open positions for this wallet directly from on-chain state.

Uses the GMX V2 SyntheticsReader contract to query the DataStore for all positions belonging to the configured wallet address. Includes fallback mechanisms for when Reader methods revert (e.g. after GMX contract upgrades).

Parameters:

| Name      | Type  | Description                           | Default    |
| --------- | ----- | ------------------------------------- | ---------- |
| `rpc_url` | `str` | RPC endpoint URL for on-chain queries | *required* |

Returns:

| Type                  | Description                                   |
| --------------------- | --------------------------------------------- |
| `list[GMXv2Position]` | List of GMXv2Position objects read from chain |

Raises:

| Type         | Description                                      |
| ------------ | ------------------------------------------------ |
| `ValueError` | If the chain is not supported for on-chain reads |

#### get_positions_as_teardown_summary

```
get_positions_as_teardown_summary(
    rpc_url: str, strategy_id: str
) -> TeardownPositionSummary
```

Read on-chain positions and return as TeardownPositionSummary.

This is the primary method for integrating with the teardown system. It reads positions directly from chain and converts them to the PositionInfo format used by get_open_positions().

Parameters:

| Name          | Type  | Description                           | Default    |
| ------------- | ----- | ------------------------------------- | ---------- |
| `rpc_url`     | `str` | RPC endpoint URL for on-chain queries | *required* |
| `strategy_id` | `str` | Strategy identifier for the summary   | *required* |

Returns:

| Type                      | Description                                         |
| ------------------------- | --------------------------------------------------- |
| `TeardownPositionSummary` | TeardownPositionSummary with on-chain position data |

#### cancel_order

```
cancel_order(order_key: str) -> OrderResult
```

Cancel a pending order.

Parameters:

| Name        | Type  | Description         | Default    |
| ----------- | ----- | ------------------- | ---------- |
| `order_key` | `str` | Order key to cancel | *required* |

Returns:

| Type          | Description                            |
| ------------- | -------------------------------------- |
| `OrderResult` | OrderResult indicating success/failure |

#### get_order

```
get_order(order_key: str) -> GMXv2Order | None
```

Get order details.

Parameters:

| Name        | Type  | Description          | Default    |
| ----------- | ----- | -------------------- | ---------- |
| `order_key` | `str` | Order key to look up | *required* |

Returns:

| Type         | Description |
| ------------ | ----------- |
| \`GMXv2Order | None\`      |

#### get_all_orders

```
get_all_orders() -> list[GMXv2Order]
```

Get all pending orders.

Returns:

| Type               | Description                |
| ------------------ | -------------------------- |
| `list[GMXv2Order]` | List of all pending orders |

#### set_position

```
set_position(position: GMXv2Position) -> None
```

Set a position for testing.

Parameters:

| Name       | Type            | Description     | Default    |
| ---------- | --------------- | --------------- | ---------- |
| `position` | `GMXv2Position` | Position to set | *required* |

#### clear_positions

```
clear_positions() -> None
```

Clear all positions.

#### clear_orders

```
clear_orders() -> None
```

Clear all orders.

#### clear_all

```
clear_all() -> None
```

Clear all state.

### GMXv2Config

```
GMXv2Config(
    chain: str,
    wallet_address: str,
    default_slippage_bps: int = 50,
    execution_fee: int | None = None,
    referral_code: bytes = b"\x00" * 32,
)
```

Configuration for GMXv2Adapter.

Attributes:

| Name                   | Type    | Description                                                    |
| ---------------------- | ------- | -------------------------------------------------------------- |
| `chain`                | `str`   | Target blockchain (arbitrum or avalanche)                      |
| `wallet_address`       | `str`   | Address executing transactions                                 |
| `default_slippage_bps` | `int`   | Default slippage tolerance in basis points (default 50 = 0.5%) |
| `execution_fee`        | \`int   | None\`                                                         |
| `referral_code`        | `bytes` | Optional referral code for fee discounts                       |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration and set defaults.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### GMXv2Order

```
GMXv2Order(
    order_key: str,
    market: str,
    initial_collateral_token: str,
    order_type: GMXv2OrderType,
    is_long: bool,
    size_delta_usd: Decimal,
    initial_collateral_delta_amount: Decimal,
    trigger_price: Decimal | None = None,
    acceptable_price: Decimal | None = None,
    execution_fee: int = 0,
    callback_gas_limit: int = 0,
    is_frozen: bool = False,
    created_at: datetime = (lambda: datetime.now(UTC))(),
    updated_at: datetime = (lambda: datetime.now(UTC))(),
)
```

Represents a GMX v2 order.

Attributes:

| Name                              | Type             | Description                      |
| --------------------------------- | ---------------- | -------------------------------- |
| `order_key`                       | `str`            | Unique identifier for the order  |
| `market`                          | `str`            | Market address                   |
| `initial_collateral_token`        | `str`            | Collateral token for the order   |
| `order_type`                      | `GMXv2OrderType` | Type of order                    |
| `is_long`                         | `bool`           | Position direction               |
| `size_delta_usd`                  | `Decimal`        | Size change in USD (30 decimals) |
| `initial_collateral_delta_amount` | `Decimal`        | Collateral amount change         |
| `trigger_price`                   | \`Decimal        | None\`                           |
| `acceptable_price`                | \`Decimal        | None\`                           |
| `execution_fee`                   | `int`            | Fee paid to keeper               |
| `callback_gas_limit`              | `int`            | Gas limit for callback execution |
| `is_frozen`                       | `bool`           | Whether order is frozen          |
| `created_at`                      | `datetime`       | Order creation timestamp         |
| `updated_at`                      | `datetime`       | Last update timestamp            |

#### is_increase

```
is_increase: bool
```

Check if order increases position size.

#### is_decrease

```
is_decrease: bool
```

Check if order decreases position size.

#### is_market_order

```
is_market_order: bool
```

Check if order is a market order.

#### is_limit_order

```
is_limit_order: bool
```

Check if order is a limit order.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> GMXv2Order
```

Create from dictionary.

### GMXv2OrderType

Bases: `Enum`

GMX v2 order types.

#### to_int

```
to_int() -> int
```

Convert to GMX v2 order type integer.

### GMXv2Position

```
GMXv2Position(
    position_key: str,
    market: str,
    collateral_token: str,
    size_in_usd: Decimal,
    size_in_tokens: Decimal,
    collateral_amount: Decimal,
    entry_price: Decimal,
    is_long: bool,
    realized_pnl: Decimal = Decimal("0"),
    unrealized_pnl: Decimal = Decimal("0"),
    leverage: Decimal = Decimal("1"),
    liquidation_price: Decimal | None = None,
    funding_fee_amount: Decimal = Decimal("0"),
    borrowing_fee_amount: Decimal = Decimal("0"),
    last_updated: datetime = (lambda: datetime.now(UTC))(),
)
```

Represents an open GMX v2 position.

Attributes:

| Name                   | Type       | Description                                    |
| ---------------------- | ---------- | ---------------------------------------------- |
| `position_key`         | `str`      | Unique identifier for the position             |
| `market`               | `str`      | Market address                                 |
| `collateral_token`     | `str`      | Token used as collateral                       |
| `size_in_usd`          | `Decimal`  | Position size in USD (30 decimals)             |
| `size_in_tokens`       | `Decimal`  | Position size in index tokens (token decimals) |
| `collateral_amount`    | `Decimal`  | Collateral amount in token decimals            |
| `entry_price`          | `Decimal`  | Average entry price (30 decimals)              |
| `is_long`              | `bool`     | True for long, False for short                 |
| `realized_pnl`         | `Decimal`  | Realized PnL (30 decimals)                     |
| `unrealized_pnl`       | `Decimal`  | Unrealized PnL (30 decimals)                   |
| `leverage`             | `Decimal`  | Current leverage (size / collateral)           |
| `liquidation_price`    | \`Decimal  | None\`                                         |
| `funding_fee_amount`   | `Decimal`  | Accumulated funding fees                       |
| `borrowing_fee_amount` | `Decimal`  | Accumulated borrowing fees                     |
| `last_updated`         | `datetime` | Timestamp of last update                       |

#### side

```
side: GMXv2PositionSide
```

Get position side.

#### total_fees

```
total_fees: Decimal
```

Get total accumulated fees.

#### net_pnl

```
net_pnl: Decimal
```

Get net PnL after fees.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> GMXv2Position
```

Create from dictionary.

### GMXv2PositionSide

Bases: `Enum`

Position side (long/short).

### GMXv2Event

```
GMXv2Event(
    event_type: GMXv2EventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)
```

Parsed GMX v2 event.

Attributes:

| Name               | Type             | Description                              |
| ------------------ | ---------------- | ---------------------------------------- |
| `event_type`       | `GMXv2EventType` | Type of event                            |
| `event_name`       | `str`            | Name of event (e.g., "PositionIncrease") |
| `log_index`        | `int`            | Index of log in transaction              |
| `transaction_hash` | `str`            | Transaction hash                         |
| `block_number`     | `int`            | Block number                             |
| `contract_address` | `str`            | Contract that emitted event              |
| `data`             | `dict[str, Any]` | Parsed event data                        |
| `raw_topics`       | `list[str]`      | Raw event topics                         |
| `raw_data`         | `str`            | Raw event data                           |
| `timestamp`        | `datetime`       | Event timestamp                          |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> GMXv2Event
```

Create from dictionary.

### GMXv2EventType

Bases: `Enum`

GMX v2 event types.

### GMXv2ReceiptParser

```
GMXv2ReceiptParser(**kwargs: Any)
```

Parser for GMX v2 transaction receipts.

This parser extracts and decodes GMX v2 events from transaction receipts, providing structured data for position updates, order fills, and other protocol events.

SUPPORTED_EXTRACTIONS declares the extraction fields this parser can provide. Used by ResultEnricher to warn when expected fields are unsupported.

Example

parser = GMXv2ReceiptParser()

#### Parse a receipt dict (from web3.py)

result = parser.parse_receipt(receipt)

if result.success: for event in result.events: print(f"Event: {event.event_name}")

```
for increase in result.position_increases:
    print(f"Position increased: size=${increase.size_in_usd}")
```

Initialize the parser.

Parameters:

| Name       | Type  | Description                                      | Default |
| ---------- | ----- | ------------------------------------------------ | ------- |
| `**kwargs` | `Any` | Additional arguments (ignored for compatibility) | `{}`    |

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name      | Type             | Description                                                                        | Default    |
| --------- | ---------------- | ---------------------------------------------------------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict containing 'logs', 'transactionHash', 'blockNumber', etc. | *required* |

Returns:

| Type          | Description                                |
| ------------- | ------------------------------------------ |
| `ParseResult` | ParseResult with extracted events and data |

#### parse_logs

```
parse_logs(logs: list[dict[str, Any]]) -> list[GMXv2Event]
```

Parse a list of logs.

Parameters:

| Name   | Type                   | Description       | Default    |
| ------ | ---------------------- | ----------------- | ---------- |
| `logs` | `list[dict[str, Any]]` | List of log dicts | *required* |

Returns:

| Type               | Description           |
| ------------------ | --------------------- |
| `list[GMXv2Event]` | List of parsed events |

#### is_gmx_event

```
is_gmx_event(topic: str | bytes) -> bool
```

Check if a topic is a known GMX v2 event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                           |
| ------ | ------------------------------------- |
| `bool` | True if topic is a known GMX v2 event |

#### get_event_type

```
get_event_type(topic: str | bytes) -> GMXv2EventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type             | Description           |
| ---------------- | --------------------- |
| `GMXv2EventType` | Event type or UNKNOWN |

#### extract_swap_amounts

```
extract_swap_amounts(receipt: dict[str, Any]) -> Any
```

Extract swap amounts from transaction receipt.

GMX V2 swaps are executed through orders, not traditional swap events. For GMX orders:

- amount_in = initial_collateral_delta_amount (collateral deposited)
- amount_out = size_delta_usd (position size in USD, scaled by 1e30)
- effective_price represents the leverage ratio

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description                                               |
| ----- | --------------------------------------------------------- |
| `Any` | SwapAmounts dataclass if swap order found, None otherwise |

#### extract_position_id

```
extract_position_id(receipt: dict[str, Any]) -> str | None
```

Extract position ID (key) from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

#### extract_size_delta

```
extract_size_delta(
    receipt: dict[str, Any],
) -> Decimal | None
```

Extract size delta (in USD) from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### extract_collateral

```
extract_collateral(
    receipt: dict[str, Any],
) -> Decimal | None
```

Extract collateral amount from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### extract_entry_price

```
extract_entry_price(
    receipt: dict[str, Any],
) -> Decimal | None
```

Extract entry price from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### extract_leverage

```
extract_leverage(receipt: dict[str, Any]) -> Decimal | None
```

Extract leverage from transaction receipt.

Leverage is calculated as size_in_usd / (collateral_amount * collateral_token_price).

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### extract_realized_pnl

```
extract_realized_pnl(
    receipt: dict[str, Any],
) -> Decimal | None
```

Extract realized PnL from transaction receipt.

Only available for position decreases (closing/reducing positions).

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### extract_exit_price

```
extract_exit_price(
    receipt: dict[str, Any],
) -> Decimal | None
```

Extract exit price from transaction receipt.

Only available for position decreases (closing/reducing positions).

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

#### extract_fees_paid

```
extract_fees_paid(receipt: dict[str, Any]) -> int | None
```

Extract fees paid from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

### GMXV2SDK

```
GMXV2SDK(rpc_url: str, chain: str = 'arbitrum')
```

SDK for interacting with GMX V2 perpetuals on Arbitrum.

This SDK builds transactions for creating orders using the ExchangeRouter's multicall function which atomically:

1. Sends collateral to OrderVault (via sendWnt or sendTokens)
1. Creates the order

Initialize GMX V2 SDK.

Parameters:

| Name      | Type  | Description                                        | Default      |
| --------- | ----- | -------------------------------------------------- | ------------ |
| `rpc_url` | `str` | RPC endpoint URL                                   | *required*   |
| `chain`   | `str` | Target chain (only 'arbitrum' supported currently) | `'arbitrum'` |

#### get_account_position_count

```
get_account_position_count(account: str) -> int
```

Get the number of open positions for an account.

Tries SyntheticsReader.getAccountPositionCount first. If it reverts (e.g. after a GMX contract upgrade removed the function), falls back to a DataStore getBytes32Count query using the account position list key.

Parameters:

| Name      | Type  | Description             | Default    |
| --------- | ----- | ----------------------- | ---------- |
| `account` | `str` | Wallet address to query | *required* |

Returns:

| Type  | Description                                       |
| ----- | ------------------------------------------------- |
| `int` | Number of open positions (0 if both methods fail) |

#### get_account_positions

```
get_account_positions(account: str) -> list[dict]
```

Read all open positions for an account.

Tries on-chain Reader contract first, then falls back to GMX REST API when Reader calls revert (common after GMX contract upgrades).

Parameters:

| Name      | Type  | Description             | Default    |
| --------- | ----- | ----------------------- | ---------- |
| `account` | `str` | Wallet address to query | *required* |

Returns:

| Type         | Description                                                                |
| ------------ | -------------------------------------------------------------------------- |
| `list[dict]` | List of position dicts with keys: account, market, collateral_token,       |
| `list[dict]` | size_in_usd, size_in_tokens, collateral_amount, borrowing_factor,          |
| `list[dict]` | funding_fee_amount_per_size, is_long, increased_at_time, decreased_at_time |

#### get_market_address

```
get_market_address(index_token_symbol: str) -> str
```

Get GMX V2 market address for an index token.

Parameters:

| Name                 | Type  | Description    | Default    |
| -------------------- | ----- | -------------- | ---------- |
| `index_token_symbol` | `str` | "ETH" or "BTC" | *required* |

Returns:

| Type  | Description    |
| ----- | -------------- |
| `str` | Market address |

#### get_execution_fee

```
get_execution_fee(
    order_type: str = "increase", multiplier: float = 1.5
) -> int
```

Calculate execution fee for GMX order dynamically.

GMX V2 validates: executionFee >= adjustedGasLimit * tx.gasprice where adjustedGasLimit = baseGasLimit + orderGasLimit * multiplierFactor (callbackGasLimit = 0 for our orders).

Parameters:

| Name         | Type    | Description                                                                                            | Default      |
| ------------ | ------- | ------------------------------------------------------------------------------------------------------ | ------------ |
| `order_type` | `str`   | "increase" or "decrease" to select appropriate gas limit                                               | `'increase'` |
| `multiplier` | `float` | Safety multiplier on top of the adjusted gas limit (default 1.5x for testing, use 2.0x for production) | `1.5`        |

Returns:

| Type  | Description          |
| ----- | -------------------- |
| `int` | Execution fee in wei |

#### build_increase_order_multicall

```
build_increase_order_multicall(
    params: GMXV2OrderParams,
) -> GMXV2TransactionData
```

Build a multicall transaction to create an increase order.

This combines:

1. sendWnt or sendTokens (collateral to OrderVault)
1. createOrder

Parameters:

| Name     | Type               | Description      | Default    |
| -------- | ------------------ | ---------------- | ---------- |
| `params` | `GMXV2OrderParams` | Order parameters | *required* |

Returns:

| Type                   | Description                          |
| ---------------------- | ------------------------------------ |
| `GMXV2TransactionData` | Transaction data ready for execution |

#### build_decrease_order_multicall

```
build_decrease_order_multicall(
    params: GMXV2OrderParams,
) -> GMXV2TransactionData
```

Build a multicall transaction to create a decrease order.

For decrease orders, no collateral needs to be sent to OrderVault. Only the execution fee is needed, sent via sendWnt.

Parameters:

| Name     | Type               | Description      | Default    |
| -------- | ------------------ | ---------------- | ---------- |
| `params` | `GMXV2OrderParams` | Order parameters | *required* |

Returns:

| Type                   | Description                          |
| ---------------------- | ------------------------------------ |
| `GMXV2TransactionData` | Transaction data ready for execution |

### DecreasePositionSwapType

Bases: `IntEnum`

GMX V2 Decrease Position Swap Types

### GMXV2OrderParams

```
GMXV2OrderParams(
    from_address: str,
    market: str,
    initial_collateral_token: str,
    initial_collateral_delta_amount: int,
    size_delta_usd: int,
    is_long: bool,
    acceptable_price: int,
    execution_fee: int,
    trigger_price: int = 0,
    referral_code: bytes = b"\x00" * 32,
)
```

Parameters for creating a GMX V2 order.

### GMXV2TransactionData

```
GMXV2TransactionData(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
)
```

Transaction data returned by the SDK.

### OrderType

Bases: `IntEnum`

GMX V2 Order Types

### get_gmx_v2_sdk

```
get_gmx_v2_sdk(
    rpc_url: str, chain: str = "arbitrum"
) -> GMXV2SDK
```

Factory function to create a GMX V2 SDK instance.

# Hyperliquid

Connector for Hyperliquid perpetuals exchange.

## almanak.framework.connectors.hyperliquid

Hyperliquid Connector.

This module provides an adapter for interacting with Hyperliquid perpetual futures exchange, supporting order management, position queries, and L1/L2 message signing.

Hyperliquid is a decentralized perpetual futures exchange supporting:

- Long and short positions with up to 50x leverage
- Limit and market orders with various time-in-force options
- Cross and isolated margin modes
- REST API and WebSocket for real-time data

Supported networks:

- Mainnet (api.hyperliquid.xyz)
- Testnet (api.hyperliquid-testnet.xyz)

Example

from almanak.framework.connectors.hyperliquid import HyperliquidAdapter, HyperliquidConfig

config = HyperliquidConfig( network="mainnet", wallet_address="0x...", private_key="0x...", ) adapter = HyperliquidAdapter(config)

### Place a limit order

result = adapter.place_order( asset="ETH", is_buy=True, size=Decimal("0.1"), price=Decimal("2000"), )

### Check open orders

orders = adapter.get_open_orders()

### Get position

position = adapter.get_position("ETH")

### CancelResult

```
CancelResult(
    success: bool,
    cancelled_orders: list[str] = list(),
    failed_orders: list[str] = list(),
    error: str | None = None,
    response: dict[str, Any] | None = None,
)
```

Result of canceling one or more orders.

Attributes:

| Name               | Type             | Description                             |
| ------------------ | ---------------- | --------------------------------------- |
| `success`          | `bool`           | Whether operation succeeded             |
| `cancelled_orders` | `list[str]`      | List of cancelled order IDs             |
| `failed_orders`    | `list[str]`      | List of order IDs that failed to cancel |
| `error`            | \`str            | None\`                                  |
| `response`         | \`dict[str, Any] | None\`                                  |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### EIP712Signer

```
EIP712Signer(
    private_key: str, chain_id: int, is_mainnet: bool = True
)
```

EIP-712 typed message signer for Hyperliquid.

This class handles the cryptographic signing of messages for both L1 and L2 operations on Hyperliquid. It uses EIP-712 structured data hashing.

The signing process:

1. Construct the EIP-712 typed data structure
1. Hash the domain separator
1. Hash the message struct
1. Sign the combined hash with the private key

Initialize the signer.

Parameters:

| Name          | Type   | Description                                         | Default    |
| ------------- | ------ | --------------------------------------------------- | ---------- |
| `private_key` | `str`  | Hex-encoded private key (with or without 0x prefix) | *required* |
| `chain_id`    | `int`  | Chain ID for EIP-712 domain                         | *required* |
| `is_mainnet`  | `bool` | Whether this is mainnet (L1) or testnet (L2)        | `True`     |

#### sign_l1_action

```
sign_l1_action(
    action: dict[str, Any],
    nonce: int,
    vault_address: str | None = None,
) -> str
```

Sign an L1 action for mainnet.

L1 signing uses a specific EIP-712 structure with:

- source: The signing wallet address
- connectionId: Always the zero address for direct signing
- nonce: Unique identifier to prevent replay

Parameters:

| Name            | Type             | Description    | Default                |
| --------------- | ---------------- | -------------- | ---------------------- |
| `action`        | `dict[str, Any]` | Action payload | *required*             |
| `nonce`         | `int`            | Unique nonce   | *required*             |
| `vault_address` | \`str            | None\`         | Optional vault address |

Returns:

| Type  | Description           |
| ----- | --------------------- |
| `str` | Hex-encoded signature |

#### sign_l2_action

```
sign_l2_action(action: dict[str, Any], nonce: int) -> str
```

Sign an L2 action for testnet.

L2 signing uses a simpler structure that's more gas-efficient for the testnet environment.

Parameters:

| Name     | Type             | Description    | Default    |
| -------- | ---------------- | -------------- | ---------- |
| `action` | `dict[str, Any]` | Action payload | *required* |
| `nonce`  | `int`            | Unique nonce   | *required* |

Returns:

| Type  | Description           |
| ----- | --------------------- |
| `str` | Hex-encoded signature |

### ExternalSigner

```
ExternalSigner(sign_callback: SignCallback)
```

External signer that delegates to a callback.

This allows using hardware wallets, custodians, or other external signing solutions.

Initialize with signing callback.

Parameters:

| Name            | Type           | Description                                             | Default    |
| --------------- | -------------- | ------------------------------------------------------- | ---------- |
| `sign_callback` | `SignCallback` | Function that signs (action, nonce, is_l1) -> signature | *required* |

#### sign_l1_action

```
sign_l1_action(
    action: dict[str, Any],
    nonce: int,
    vault_address: str | None = None,
) -> str
```

Sign an L1 action using external signer.

#### sign_l2_action

```
sign_l2_action(action: dict[str, Any], nonce: int) -> str
```

Sign an L2 action using external signer.

### HyperliquidAdapter

```
HyperliquidAdapter(
    config: HyperliquidConfig,
    signer: MessageSigner | None = None,
)
```

Adapter for Hyperliquid perpetual futures exchange.

This adapter provides methods for:

- Placing limit and market orders
- Canceling orders by ID or client ID
- Querying positions and open orders
- Managing leverage and margin settings

Example

config = HyperliquidConfig( network="mainnet", wallet_address="0x...", private_key="0x...", ) adapter = HyperliquidAdapter(config)

#### Place a limit buy order

result = adapter.place_order( asset="ETH", is_buy=True, size=Decimal("0.1"), price=Decimal("2000"), )

#### Check open orders

orders = adapter.get_open_orders()

#### Cancel order

cancel_result = adapter.cancel_order(order_id=result.order_id)

Initialize the adapter.

Parameters:

| Name     | Type                | Description                       | Default                                                            |
| -------- | ------------------- | --------------------------------- | ------------------------------------------------------------------ |
| `config` | `HyperliquidConfig` | Hyperliquid adapter configuration | *required*                                                         |
| `signer` | \`MessageSigner     | None\`                            | Optional custom message signer (uses EIP712Signer if not provided) |

#### place_order

```
place_order(
    asset: str,
    is_buy: bool,
    size: Decimal,
    price: Decimal,
    order_type: HyperliquidOrderType = HyperliquidOrderType.LIMIT,
    time_in_force: HyperliquidTimeInForce = HyperliquidTimeInForce.GTC,
    reduce_only: bool = False,
    client_id: str | None = None,
    slippage_bps: int | None = None,
) -> OrderResult
```

Place a new order.

Parameters:

| Name            | Type                     | Description                                                 | Default                              |
| --------------- | ------------------------ | ----------------------------------------------------------- | ------------------------------------ |
| `asset`         | `str`                    | Asset symbol (e.g., "ETH", "BTC")                           | *required*                           |
| `is_buy`        | `bool`                   | True for buy, False for sell                                | *required*                           |
| `size`          | `Decimal`                | Order size in asset units                                   | *required*                           |
| `price`         | `Decimal`                | Limit price (for market orders, used as slippage reference) | *required*                           |
| `order_type`    | `HyperliquidOrderType`   | Order type (limit or market)                                | `LIMIT`                              |
| `time_in_force` | `HyperliquidTimeInForce` | Time in force option                                        | `GTC`                                |
| `reduce_only`   | `bool`                   | Whether order can only reduce position                      | `False`                              |
| `client_id`     | \`str                    | None\`                                                      | Optional client-assigned order ID    |
| `slippage_bps`  | \`int                    | None\`                                                      | Slippage tolerance for market orders |

Returns:

| Type          | Description                    |
| ------------- | ------------------------------ |
| `OrderResult` | OrderResult with order details |

#### cancel_order

```
cancel_order(
    order_id: str | None = None,
    client_id: str | None = None,
    asset: str | None = None,
) -> CancelResult
```

Cancel an existing order.

Parameters:

| Name        | Type  | Description | Default                                |
| ----------- | ----- | ----------- | -------------------------------------- |
| `order_id`  | \`str | None\`      | Exchange-assigned order ID             |
| `client_id` | \`str | None\`      | Client-assigned order ID               |
| `asset`     | \`str | None\`      | Asset symbol (required with client_id) |

Returns:

| Type           | Description                             |
| -------------- | --------------------------------------- |
| `CancelResult` | CancelResult indicating success/failure |

#### cancel_all_orders

```
cancel_all_orders(asset: str | None = None) -> CancelResult
```

Cancel all open orders.

Parameters:

| Name    | Type  | Description | Default                     |
| ------- | ----- | ----------- | --------------------------- |
| `asset` | \`str | None\`      | Optional asset to filter by |

Returns:

| Type           | Description                                |
| -------------- | ------------------------------------------ |
| `CancelResult` | CancelResult with list of cancelled orders |

#### get_order

```
get_order(order_id: str) -> HyperliquidOrder | None
```

Get order by ID.

Parameters:

| Name       | Type  | Description         | Default    |
| ---------- | ----- | ------------------- | ---------- |
| `order_id` | `str` | Order ID to look up | *required* |

Returns:

| Type               | Description |
| ------------------ | ----------- |
| \`HyperliquidOrder | None\`      |

#### get_open_orders

```
get_open_orders(
    asset: str | None = None,
) -> list[HyperliquidOrder]
```

Get all open orders.

Parameters:

| Name    | Type  | Description | Default                     |
| ------- | ----- | ----------- | --------------------------- |
| `asset` | \`str | None\`      | Optional asset to filter by |

Returns:

| Type                     | Description         |
| ------------------------ | ------------------- |
| `list[HyperliquidOrder]` | List of open orders |

#### get_position

```
get_position(asset: str) -> HyperliquidPosition | None
```

Get position for an asset.

Parameters:

| Name    | Type  | Description  | Default    |
| ------- | ----- | ------------ | ---------- |
| `asset` | `str` | Asset symbol | *required* |

Returns:

| Type                  | Description |
| --------------------- | ----------- |
| \`HyperliquidPosition | None\`      |

#### get_all_positions

```
get_all_positions() -> list[HyperliquidPosition]
```

Get all open positions.

Returns:

| Type                        | Description                              |
| --------------------------- | ---------------------------------------- |
| `list[HyperliquidPosition]` | List of all positions with non-zero size |

#### set_leverage

```
set_leverage(asset: str, leverage: int) -> bool
```

Set leverage for an asset.

Parameters:

| Name       | Type  | Description            | Default    |
| ---------- | ----- | ---------------------- | ---------- |
| `asset`    | `str` | Asset symbol           | *required* |
| `leverage` | `int` | Target leverage (1-50) | *required* |

Returns:

| Type   | Description        |
| ------ | ------------------ |
| `bool` | True if successful |

#### get_leverage

```
get_leverage(asset: str) -> int
```

Get current leverage for an asset.

Parameters:

| Name    | Type  | Description  | Default    |
| ------- | ----- | ------------ | ---------- |
| `asset` | `str` | Asset symbol | *required* |

Returns:

| Type  | Description                          |
| ----- | ------------------------------------ |
| `int` | Current leverage setting (default 1) |

#### set_position

```
set_position(position: HyperliquidPosition) -> None
```

Set a position for testing.

Parameters:

| Name       | Type                  | Description     | Default    |
| ---------- | --------------------- | --------------- | ---------- |
| `position` | `HyperliquidPosition` | Position to set | *required* |

#### clear_positions

```
clear_positions() -> None
```

Clear all positions.

#### clear_orders

```
clear_orders() -> None
```

Clear all orders.

#### clear_all

```
clear_all() -> None
```

Clear all state.

### HyperliquidConfig

```
HyperliquidConfig(
    network: str,
    wallet_address: str,
    private_key: str | None = None,
    default_slippage_bps: int = 50,
    vault_address: str | None = None,
    agent_address: str | None = None,
)
```

Configuration for HyperliquidAdapter.

Attributes:

| Name                   | Type  | Description                                                    |
| ---------------------- | ----- | -------------------------------------------------------------- |
| `network`              | `str` | Target network (mainnet or testnet)                            |
| `wallet_address`       | `str` | Ethereum address for the account                               |
| `private_key`          | \`str | None\`                                                         |
| `default_slippage_bps` | `int` | Default slippage tolerance in basis points (default 50 = 0.5%) |
| `vault_address`        | \`str | None\`                                                         |
| `agent_address`        | \`str | None\`                                                         |

#### api_url

```
api_url: str
```

Get API URL for configured network.

#### ws_url

```
ws_url: str
```

Get WebSocket URL for configured network.

#### chain_id

```
chain_id: int
```

Get chain ID for configured network.

#### eip712_domain

```
eip712_domain: dict[str, Any]
```

Get EIP-712 domain for configured network.

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### HyperliquidMarginMode

Bases: `Enum`

Margin mode options.

### HyperliquidNetwork

Bases: `Enum`

Hyperliquid network environments.

### HyperliquidOrder

```
HyperliquidOrder(
    order_id: str,
    client_id: str | None,
    asset: str,
    side: HyperliquidOrderSide,
    size: Decimal,
    price: Decimal,
    order_type: HyperliquidOrderType = HyperliquidOrderType.LIMIT,
    time_in_force: HyperliquidTimeInForce = HyperliquidTimeInForce.GTC,
    reduce_only: bool = False,
    status: HyperliquidOrderStatus = HyperliquidOrderStatus.OPEN,
    filled_size: Decimal = Decimal("0"),
    avg_fill_price: Decimal | None = None,
    created_at: datetime = (lambda: datetime.now(UTC))(),
    updated_at: datetime = (lambda: datetime.now(UTC))(),
)
```

Represents a Hyperliquid order.

Attributes:

| Name             | Type                     | Description                            |
| ---------------- | ------------------------ | -------------------------------------- |
| `order_id`       | `str`                    | Exchange-assigned order ID             |
| `client_id`      | \`str                    | None\`                                 |
| `asset`          | `str`                    | Asset symbol                           |
| `side`           | `HyperliquidOrderSide`   | Order side (buy/sell)                  |
| `size`           | `Decimal`                | Order size                             |
| `price`          | `Decimal`                | Limit price                            |
| `order_type`     | `HyperliquidOrderType`   | Order type (limit/market)              |
| `time_in_force`  | `HyperliquidTimeInForce` | Time in force option                   |
| `reduce_only`    | `bool`                   | Whether order can only reduce position |
| `status`         | `HyperliquidOrderStatus` | Current order status                   |
| `filled_size`    | `Decimal`                | Amount already filled                  |
| `avg_fill_price` | \`Decimal                | None\`                                 |
| `created_at`     | `datetime`               | Order creation timestamp               |
| `updated_at`     | `datetime`               | Last update timestamp                  |

#### remaining_size

```
remaining_size: Decimal
```

Get remaining unfilled size.

#### is_buy

```
is_buy: bool
```

Check if order is a buy.

#### is_sell

```
is_sell: bool
```

Check if order is a sell.

#### is_open

```
is_open: bool
```

Check if order is still open.

#### is_filled

```
is_filled: bool
```

Check if order is fully filled.

#### fill_percentage

```
fill_percentage: Decimal
```

Get fill percentage.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> HyperliquidOrder
```

Create from dictionary.

### HyperliquidOrderSide

Bases: `Enum`

Order side (buy/sell).

### HyperliquidOrderStatus

Bases: `Enum`

Order status values.

### HyperliquidOrderType

Bases: `Enum`

Hyperliquid order types.

### HyperliquidPosition

```
HyperliquidPosition(
    asset: str,
    size: Decimal,
    entry_price: Decimal,
    mark_price: Decimal = Decimal("0"),
    liquidation_price: Decimal | None = None,
    unrealized_pnl: Decimal = Decimal("0"),
    realized_pnl: Decimal = Decimal("0"),
    margin_used: Decimal = Decimal("0"),
    leverage: Decimal = Decimal("1"),
    margin_mode: HyperliquidMarginMode = HyperliquidMarginMode.CROSS,
    max_leverage: int = 50,
    last_updated: datetime = (lambda: datetime.now(UTC))(),
)
```

Represents an open Hyperliquid position.

Attributes:

| Name                | Type                    | Description                                           |
| ------------------- | ----------------------- | ----------------------------------------------------- |
| `asset`             | `str`                   | Asset symbol (e.g., "ETH")                            |
| `size`              | `Decimal`               | Position size (positive for long, negative for short) |
| `entry_price`       | `Decimal`               | Average entry price                                   |
| `mark_price`        | `Decimal`               | Current mark price                                    |
| `liquidation_price` | \`Decimal               | None\`                                                |
| `unrealized_pnl`    | `Decimal`               | Unrealized profit/loss                                |
| `realized_pnl`      | `Decimal`               | Realized profit/loss                                  |
| `margin_used`       | `Decimal`               | Margin allocated to position                          |
| `leverage`          | `Decimal`               | Current leverage                                      |
| `margin_mode`       | `HyperliquidMarginMode` | Cross or isolated margin                              |
| `max_leverage`      | `int`                   | Maximum allowed leverage for asset                    |
| `last_updated`      | `datetime`              | Timestamp of last update                              |

#### side

```
side: HyperliquidPositionSide
```

Get position side.

#### is_long

```
is_long: bool
```

Check if position is long.

#### is_short

```
is_short: bool
```

Check if position is short.

#### notional_value

```
notional_value: Decimal
```

Get notional value of position.

#### net_pnl

```
net_pnl: Decimal
```

Get net PnL (realized + unrealized).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> HyperliquidPosition
```

Create from dictionary.

### HyperliquidPositionSide

Bases: `Enum`

Position side (long/short).

### HyperliquidTimeInForce

Bases: `Enum`

Time in force options for orders.

### MessageSigner

Bases: `Protocol`

Protocol for message signing implementations.

#### sign_l1_action

```
sign_l1_action(
    action: dict[str, Any],
    nonce: int,
    vault_address: str | None = None,
) -> str
```

Sign an L1 action.

L1 actions are used for mainnet and include:

- Order placement
- Order cancellation
- Withdrawal requests

Parameters:

| Name            | Type             | Description                 | Default                |
| --------------- | ---------------- | --------------------------- | ---------------------- |
| `action`        | `dict[str, Any]` | Action payload to sign      | *required*             |
| `nonce`         | `int`            | Unique nonce for the action | *required*             |
| `vault_address` | \`str            | None\`                      | Optional vault address |

Returns:

| Type  | Description           |
| ----- | --------------------- |
| `str` | Hex-encoded signature |

#### sign_l2_action

```
sign_l2_action(action: dict[str, Any], nonce: int) -> str
```

Sign an L2 action.

L2 actions are used for testnet and some mainnet operations. The signing scheme is slightly different from L1.

Parameters:

| Name     | Type             | Description                 | Default    |
| -------- | ---------------- | --------------------------- | ---------- |
| `action` | `dict[str, Any]` | Action payload to sign      | *required* |
| `nonce`  | `int`            | Unique nonce for the action | *required* |

Returns:

| Type  | Description           |
| ----- | --------------------- |
| `str` | Hex-encoded signature |

### OrderResult

```
OrderResult(
    success: bool,
    order_id: str | None = None,
    client_id: str | None = None,
    order: HyperliquidOrder | None = None,
    error: str | None = None,
    response: dict[str, Any] | None = None,
)
```

Result of placing or canceling an order.

Attributes:

| Name        | Type               | Description                 |
| ----------- | ------------------ | --------------------------- |
| `success`   | `bool`             | Whether operation succeeded |
| `order_id`  | \`str              | None\`                      |
| `client_id` | \`str              | None\`                      |
| `order`     | \`HyperliquidOrder | None\`                      |
| `error`     | \`str              | None\`                      |
| `response`  | \`dict[str, Any]   | None\`                      |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SignedAction

```
SignedAction(
    action: dict[str, Any],
    signature: str,
    nonce: int,
    vault_address: str | None = None,
)
```

A signed action ready for submission to Hyperliquid.

Attributes:

| Name            | Type             | Description            |
| --------------- | ---------------- | ---------------------- |
| `action`        | `dict[str, Any]` | The action payload     |
| `signature`     | `str`            | EIP-712 signature      |
| `nonce`         | `int`            | Nonce used for signing |
| `vault_address` | \`str            | None\`                 |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

# Kraken

Connector for Kraken centralized exchange.

## almanak.framework.connectors.kraken

Kraken CEX Connector.

This module provides the Kraken CEX adapter for executing trades, deposits, and withdrawals on Kraken exchange.

Supported operations:

- Spot trading (market orders)
- Multi-chain withdrawals (Arbitrum, Optimism, Ethereum)
- Deposit tracking

Key features:

- Idempotent execution with userref/refid tracking
- Crash recovery for pending operations
- Exponential backoff polling
- Token/chain resolution for stack-v2 compatibility

Example

from almanak.framework.connectors.kraken import ( KrakenAdapter, KrakenConfig, KrakenCredentials, ExecutionContext, )

### Setup

credentials = KrakenCredentials.from_env() config = KrakenConfig(credentials=credentials) adapter = KrakenAdapter(config)

### Compile intent

context = ExecutionContext( chain="arbitrum", wallet_address="0x...", token_decimals={"USDC": 6, "ETH": 18}, )

### From an intent with venue="kraken"

bundle = adapter.compile_intent(intent, context)

### Execute (typically done by orchestrator)

for action in bundle.actions: key, result_id = await adapter.execute_action(action, context) details = await adapter.resolve_action(action, key, context)

### Lower-level SDK usage

from almanak.framework.connectors.kraken import KrakenSDK

sdk = KrakenSDK(credentials)

### Get balance

balance = sdk.get_balance("USDC", chain="arbitrum")

### Execute swap

userref = sdk.generate_userref() txid = sdk.swap( asset_in="USDC", asset_out="ETH", amount_in=1000_000000, # 1000 USDC decimals_in=6, userref=userref, )

### Check status

status = sdk.get_swap_status(txid, userref)

### ActionBundle

```
ActionBundle(
    actions: list[CEXAction],
    venue_type: VenueType,
    exchange: str = "kraken",
    continue_on_failure: bool = False,
    description: str = "",
    estimated_gas: int = 0,
)
```

Bundle of actions to execute.

For CEX operations, this typically contains a single action, but supports multiple for future batch operations.

#### to_dict

```
to_dict() -> dict
```

Serialize for state persistence.

### ActionType

Bases: `StrEnum`

CEX action types.

### CEXAction

```
CEXAction(
    id: str,
    type: ActionType,
    exchange: str,
    asset_in: str | None = None,
    asset_out: str | None = None,
    amount_in: int | None = None,
    decimals_in: int | None = None,
    decimals_out: int | None = None,
    asset: str | None = None,
    amount: int | None = None,
    decimals: int | None = None,
    chain: str | None = None,
    to_address: str | None = None,
    tx_hash: str | None = None,
    from_chain: str | None = None,
    userref: int | None = None,
    metadata: dict = dict(),
)
```

A single CEX action to execute.

Actions are the atomic units of work in CEX execution.

#### to_dict

```
to_dict() -> dict
```

Serialize for state persistence.

### ExecutionContext

```
ExecutionContext(
    chain: str = "ethereum",
    wallet_address: str = "",
    strategy_id: str = "",
    token_decimals: dict[str, int] = dict(),
)
```

Context for CEX execution.

Contains runtime information needed during execution.

#### get_decimals

```
get_decimals(token: str, default: int = 18) -> int
```

Get decimals for a token.

### KrakenAdapter

```
KrakenAdapter(
    config: KrakenConfig | None = None,
    sdk: KrakenSDK | None = None,
    token_resolver: KrakenTokenResolver | None = None,
    chain_mapper: KrakenChainMapper | None = None,
)
```

Adapter for compiling intents into CEX ActionBundles.

This adapter handles the translation from abstract intents (SwapIntent, WithdrawIntent, DepositIntent) into concrete CEX actions with validation.

Example

adapter = KrakenAdapter(config, sdk)

#### Compile swap intent

intent = SwapIntent( from_token="USDC", to_token="ETH", amount=Decimal("1000"), venue="kraken", ) bundle = adapter.compile_intent(intent, context)

#### Execute (handled by orchestrator)

result = await adapter.execute_action(bundle.actions[0], context)

Initialize adapter.

Parameters:

| Name             | Type                  | Description | Default                                                      |
| ---------------- | --------------------- | ----------- | ------------------------------------------------------------ |
| `config`         | \`KrakenConfig        | None\`      | Kraken configuration                                         |
| `sdk`            | \`KrakenSDK           | None\`      | KrakenSDK instance. If not provided, will be created lazily. |
| `token_resolver` | \`KrakenTokenResolver | None\`      | Custom token resolver                                        |
| `chain_mapper`   | \`KrakenChainMapper   | None\`      | Custom chain mapper                                          |

#### sdk

```
sdk: KrakenSDK
```

Get or create SDK instance.

#### receipt_resolver

```
receipt_resolver: KrakenReceiptResolver
```

Get or create receipt resolver.

#### compile_intent

```
compile_intent(
    intent: Any, context: ExecutionContext
) -> ActionBundle
```

Compile an intent into an ActionBundle.

Parameters:

| Name      | Type               | Description           | Default    |
| --------- | ------------------ | --------------------- | ---------- |
| `intent`  | `Any`              | The intent to compile | *required* |
| `context` | `ExecutionContext` | Execution context     | *required* |

Returns:

| Type           | Description                      |
| -------------- | -------------------------------- |
| `ActionBundle` | ActionBundle ready for execution |

Raises:

| Type         | Description                     |
| ------------ | ------------------------------- |
| `ValueError` | If intent type is not supported |

#### execute_action

```
execute_action(
    action: CEXAction, context: ExecutionContext
) -> tuple[CEXIdempotencyKey, str]
```

Execute a single CEX action.

Parameters:

| Name      | Type               | Description           | Default    |
| --------- | ------------------ | --------------------- | ---------- |
| `action`  | `CEXAction`        | The action to execute | *required* |
| `context` | `ExecutionContext` | Execution context     | *required* |

Returns:

| Type                            | Description                           |
| ------------------------------- | ------------------------------------- |
| `CEXIdempotencyKey`             | Tuple of (idempotency_key, result_id) |
| `str`                           | For swaps: result_id is txid          |
| `tuple[CEXIdempotencyKey, str]` | For withdrawals: result_id is refid   |
| `tuple[CEXIdempotencyKey, str]` | For deposits: result_id is tx_hash    |

#### resolve_action

```
resolve_action(
    action: CEXAction,
    key: CEXIdempotencyKey,
    context: ExecutionContext,
) -> ExecutionDetails
```

Wait for action completion and return result.

Parameters:

| Name      | Type                | Description                    | Default    |
| --------- | ------------------- | ------------------------------ | ---------- |
| `action`  | `CEXAction`         | The action that was executed   | *required* |
| `key`     | `CEXIdempotencyKey` | Idempotency key from execution | *required* |
| `context` | `ExecutionContext`  | Execution context              | *required* |

Returns:

| Type               | Description                            |
| ------------------ | -------------------------------------- |
| `ExecutionDetails` | ExecutionDetails with operation result |

### VenueType

Bases: `StrEnum`

Execution venue type.

### KrakenAPIError

```
KrakenAPIError(errors: list[str])
```

Bases: `KrakenError`

Generic Kraken API error with error codes.

Wraps errors returned directly from the Kraken API.

### KrakenAuthenticationError

Bases: `KrakenError`

Authentication failed with Kraken API.

Raised when:

- API key is invalid or expired
- API secret is incorrect
- Insufficient permissions for the requested operation

### KrakenChainNotSupportedError

```
KrakenChainNotSupportedError(chain: str, operation: str)
```

Bases: `KrakenError`

Chain is not supported for the requested operation.

### KrakenDepositError

Bases: `KrakenError`

Deposit operation failed or not found.

### KrakenError

Bases: `Exception`

Base exception for all Kraken-related errors.

### KrakenInsufficientFundsError

```
KrakenInsufficientFundsError(
    message: str, asset: str, requested: str, available: str
)
```

Bases: `KrakenError`

Insufficient balance for the requested operation.

Raised when trying to trade or withdraw more than available balance.

### KrakenMinimumOrderError

```
KrakenMinimumOrderError(
    message: str, pair: str, amount: str, minimum: str
)
```

Bases: `KrakenError`

Order amount is below Kraken's minimum.

Different trading pairs have different minimum order sizes.

### KrakenOrderCancelledError

```
KrakenOrderCancelledError(
    order_id: str, reason: str | None = None
)
```

Bases: `KrakenOrderError`

Order was cancelled before completion.

### KrakenOrderError

Bases: `KrakenError`

Order placement or query failed.

### KrakenOrderNotFoundError

```
KrakenOrderNotFoundError(
    order_id: str, userref: int | None = None
)
```

Bases: `KrakenOrderError`

Order with given ID not found.

### KrakenRateLimitError

```
KrakenRateLimitError(
    message: str, retry_after: int | None = None
)
```

Bases: `KrakenError`

Rate limit exceeded on Kraken API.

Kraken enforces strict rate limits. This error includes information about when to retry.

### KrakenTimeoutError

```
KrakenTimeoutError(
    operation: str,
    timeout_seconds: int,
    identifier: str | None = None,
)
```

Bases: `KrakenError`

Operation timed out waiting for completion.

### KrakenUnknownAssetError

```
KrakenUnknownAssetError(asset: str)
```

Bases: `KrakenError`

Asset is not supported or not found on Kraken.

### KrakenUnknownPairError

```
KrakenUnknownPairError(pair: str)
```

Bases: `KrakenError`

Trading pair does not exist on Kraken.

### KrakenWithdrawalAddressNotWhitelistedError

```
KrakenWithdrawalAddressNotWhitelistedError(
    address: str, asset: str, chain: str
)
```

Bases: `KrakenWithdrawalError`

Withdrawal address is not whitelisted on the Kraken account.

For security, Kraken requires withdrawal addresses to be pre-approved in the account settings.

### KrakenWithdrawalError

Bases: `KrakenError`

Withdrawal operation failed.

### KrakenWithdrawalLimitExceededError

```
KrakenWithdrawalLimitExceededError(
    message: str, amount: str, limit: str
)
```

Bases: `KrakenWithdrawalError`

Withdrawal exceeds daily or account limits.

### CEXIdempotencyKey

```
CEXIdempotencyKey(
    action_id: str,
    exchange: str,
    operation_type: CEXOperationType,
    userref: int | None = None,
    refid: str | None = None,
    order_id: str | None = None,
    status: str = "pending",
    created_at: datetime = (lambda: datetime.now(UTC))(),
    last_poll: datetime | None = None,
)
```

Tracks CEX operation for idempotency and crash recovery.

This is persisted in ExecutionSession to enable:

- Resuming pending operations after restart
- Avoiding duplicate orders with the same userref
- Tracking withdrawal refids for status polling

Attributes:

| Name             | Type               | Description                                    |
| ---------------- | ------------------ | ---------------------------------------------- |
| `action_id`      | `str`              | Unique identifier for the action in the bundle |
| `exchange`       | `str`              | Exchange name (e.g., "kraken")                 |
| `operation_type` | `CEXOperationType` | Type of operation (swap, withdraw, deposit)    |
| `userref`        | \`int              | None\`                                         |
| `refid`          | \`str              | None\`                                         |
| `order_id`       | \`str              | None\`                                         |
| `status`         | `str`              | Current status of the operation                |
| `created_at`     | `datetime`         | When the operation was initiated               |
| `last_poll`      | \`datetime         | None\`                                         |

#### to_dict

```
to_dict() -> dict
```

Serialize for state persistence.

#### from_dict

```
from_dict(data: dict) -> CEXIdempotencyKey
```

Deserialize from state.

### CEXOperationType

Bases: `StrEnum`

Type of CEX operation.

### CEXRiskConfig

Bases: `BaseModel`

CEX-specific risk parameters.

Used by RiskGuard to validate CEX operations.

#### lowercase_chains

```
lowercase_chains(v: list[str]) -> list[str]
```

Normalize chain names to lowercase.

### KrakenBalance

Bases: `BaseModel`

Balance information for an asset on Kraken.

#### from_kraken_response

```
from_kraken_response(
    asset: str, data: dict
) -> KrakenBalance
```

Create from Kraken balance response.

### KrakenConfig

Bases: `BaseModel`

Configuration for Kraken connector.

Example

config = KrakenConfig( credentials=KrakenCredentials.from_env(), default_slippage_bps=50, )

#### get_credentials

```
get_credentials() -> KrakenCredentials
```

Get credentials, loading from env if not configured.

### KrakenCredentials

Bases: `BaseModel`

Kraken API credentials.

Security notes:

- Credentials are stored using SecretStr to prevent accidental logging
- Use from_env() to load from environment variables
- Never commit credentials to version control

#### from_env

```
from_env(
    key_env: str = "KRAKEN_API_KEY",
    secret_env: str = "KRAKEN_API_SECRET",
) -> KrakenCredentials
```

Load credentials from environment variables.

Parameters:

| Name         | Type  | Description                              | Default               |
| ------------ | ----- | ---------------------------------------- | --------------------- |
| `key_env`    | `str` | Environment variable name for API key    | `'KRAKEN_API_KEY'`    |
| `secret_env` | `str` | Environment variable name for API secret | `'KRAKEN_API_SECRET'` |

Returns:

| Type                | Description                |
| ------------------- | -------------------------- |
| `KrakenCredentials` | KrakenCredentials instance |

Raises:

| Type         | Description                          |
| ------------ | ------------------------------------ |
| `ValueError` | If environment variables are not set |

#### model_post_init

```
model_post_init(__context: Any) -> None
```

Validate that credentials are not empty.

### KrakenDepositStatus

Bases: `StrEnum`

Kraken deposit status values.

### KrakenMarketInfo

Bases: `BaseModel`

Information about a Kraken trading pair.

Contains precision, minimum sizes, and fee information needed for order validation.

#### from_kraken_response

```
from_kraken_response(
    pair: str, data: dict
) -> KrakenMarketInfo
```

Create from Kraken API response.

Parameters:

| Name   | Type   | Description                       | Default    |
| ------ | ------ | --------------------------------- | ---------- |
| `pair` | `str`  | Trading pair name                 | *required* |
| `data` | `dict` | Response from get_asset_pairs API | *required* |

Returns:

| Type               | Description               |
| ------------------ | ------------------------- |
| `KrakenMarketInfo` | KrakenMarketInfo instance |

#### get_min_order_base

```
get_min_order_base(decimals: int) -> int
```

Get minimum order size in base asset wei units.

Parameters:

| Name       | Type  | Description    | Default    |
| ---------- | ----- | -------------- | ---------- |
| `decimals` | `int` | Token decimals | *required* |

Returns:

| Type  | Description          |
| ----- | -------------------- |
| `int` | Minimum order in wei |

#### get_min_cost_quote

```
get_min_cost_quote(decimals: int) -> int
```

Get minimum order cost in quote asset wei units.

Parameters:

| Name       | Type  | Description    | Default    |
| ---------- | ----- | -------------- | ---------- |
| `decimals` | `int` | Token decimals | *required* |

Returns:

| Type  | Description         |
| ----- | ------------------- |
| `int` | Minimum cost in wei |

### KrakenOrderStatus

Bases: `StrEnum`

Kraken order status values.

### KrakenWithdrawStatus

Bases: `StrEnum`

Kraken withdrawal status values.

### ExecutionDetails

```
ExecutionDetails(
    success: bool,
    venue: str,
    operation_type: str,
    amounts_in: list[TokenAmount] = list(),
    amounts_out: list[TokenAmount] = list(),
    fees: list[TokenAmount] = list(),
    source_id: str = "",
    timestamp: datetime | None = None,
    cex_metadata: dict | None = None,
)
```

Standardized execution result for CEX and on-chain operations.

This provides a common interface for strategies to process results regardless of whether execution happened on-chain or CEX.

#### to_dict

```
to_dict() -> dict
```

Serialize to dict for state persistence.

#### from_dict

```
from_dict(data: dict) -> ExecutionDetails
```

Deserialize from dict.

### KrakenReceiptResolver

```
KrakenReceiptResolver(
    sdk: KrakenSDK, config: KrakenConfig | None = None
)
```

Resolves CEX operation results with polling and retry logic.

This class handles:

- Polling for operation completion with exponential backoff
- Converting raw API responses to ExecutionDetails
- Detecting stuck operations and generating alerts

Example

resolver = KrakenReceiptResolver(sdk, config)

#### Poll for swap completion

details = await resolver.resolve_swap( txid="OXXXXX-XXXXX", userref=12345, asset_in="USDC", asset_out="ETH", decimals_in=6, decimals_out=18, )

#### Poll for withdrawal completion

details = await resolver.resolve_withdrawal( refid="FXXXXX-XXXXX", asset="ETH", chain="arbitrum", decimals=18, )

Initialize receipt resolver.

Parameters:

| Name     | Type           | Description        | Default                                         |
| -------- | -------------- | ------------------ | ----------------------------------------------- |
| `sdk`    | `KrakenSDK`    | KrakenSDK instance | *required*                                      |
| `config` | \`KrakenConfig | None\`             | Optional configuration for timeouts and polling |

#### resolve_swap

```
resolve_swap(
    txid: str,
    userref: int,
    asset_in: str,
    asset_out: str,
    decimals_in: int,
    decimals_out: int,
    chain: str = "ethereum",
    idempotency_key: CEXIdempotencyKey | None = None,
) -> ExecutionDetails
```

Poll for swap completion and return execution details.

Parameters:

| Name              | Type                | Description                     | Default                                  |
| ----------------- | ------------------- | ------------------------------- | ---------------------------------------- |
| `txid`            | `str`               | Order transaction ID            | *required*                               |
| `userref`         | `int`               | Order reference for idempotency | *required*                               |
| `asset_in`        | `str`               | Input asset symbol              | *required*                               |
| `asset_out`       | `str`               | Output asset symbol             | *required*                               |
| `decimals_in`     | `int`               | Input asset decimals            | *required*                               |
| `decimals_out`    | `int`               | Output asset decimals           | *required*                               |
| `chain`           | `str`               | Chain for token resolution      | `'ethereum'`                             |
| `idempotency_key` | \`CEXIdempotencyKey | None\`                          | Optional key for tracking last poll time |

Returns:

| Type               | Description                       |
| ------------------ | --------------------------------- |
| `ExecutionDetails` | ExecutionDetails with swap result |

Raises:

| Type                 | Description            |
| -------------------- | ---------------------- |
| `KrakenTimeoutError` | If operation times out |

#### resolve_withdrawal

```
resolve_withdrawal(
    refid: str,
    asset: str,
    chain: str,
    decimals: int,
    to_address: str,
    amount: int,
    idempotency_key: CEXIdempotencyKey | None = None,
) -> ExecutionDetails
```

Poll for withdrawal completion and return execution details.

Parameters:

| Name              | Type                | Description                    | Default                   |
| ----------------- | ------------------- | ------------------------------ | ------------------------- |
| `refid`           | `str`               | Kraken withdrawal reference ID | *required*                |
| `asset`           | `str`               | Asset symbol                   | *required*                |
| `chain`           | `str`               | Target chain                   | *required*                |
| `decimals`        | `int`               | Asset decimals                 | *required*                |
| `to_address`      | `str`               | Destination address            | *required*                |
| `amount`          | `int`               | Withdrawal amount in wei       | *required*                |
| `idempotency_key` | \`CEXIdempotencyKey | None\`                         | Optional key for tracking |

Returns:

| Type               | Description                             |
| ------------------ | --------------------------------------- |
| `ExecutionDetails` | ExecutionDetails with withdrawal result |

Raises:

| Type                 | Description            |
| -------------------- | ---------------------- |
| `KrakenTimeoutError` | If operation times out |

#### resolve_deposit

```
resolve_deposit(
    tx_hash: str,
    asset: str,
    chain: str,
    decimals: int,
    amount: int,
    idempotency_key: CEXIdempotencyKey | None = None,
) -> ExecutionDetails
```

Poll for deposit confirmation on Kraken.

Parameters:

| Name              | Type                | Description                          | Default                   |
| ----------------- | ------------------- | ------------------------------------ | ------------------------- |
| `tx_hash`         | `str`               | On-chain transaction hash of deposit | *required*                |
| `asset`           | `str`               | Asset symbol                         | *required*                |
| `chain`           | `str`               | Source chain                         | *required*                |
| `decimals`        | `int`               | Asset decimals                       | *required*                |
| `amount`          | `int`               | Deposit amount in wei                | *required*                |
| `idempotency_key` | \`CEXIdempotencyKey | None\`                               | Optional key for tracking |

Returns:

| Type               | Description                          |
| ------------------ | ------------------------------------ |
| `ExecutionDetails` | ExecutionDetails with deposit result |

Raises:

| Type                 | Description            |
| -------------------- | ---------------------- |
| `KrakenTimeoutError` | If operation times out |

#### resume_operation

```
resume_operation(
    key: CEXIdempotencyKey, **context
) -> ExecutionDetails | None
```

Resume a pending operation after restart.

Uses the idempotency key to check operation status and either return the result or resume polling.

Parameters:

| Name        | Type                | Description                                      | Default    |
| ----------- | ------------------- | ------------------------------------------------ | ---------- |
| `key`       | `CEXIdempotencyKey` | Idempotency key from persisted state             | *required* |
| `**context` | `Any`               | Additional context (asset names, decimals, etc.) | `{}`       |

Returns:

| Type               | Description |
| ------------------ | ----------- |
| \`ExecutionDetails | None\`      |

### TokenAmount

```
TokenAmount(token: str, amount: int, decimals: int = 18)
```

Amount of a specific token.

### KrakenSDK

```
KrakenSDK(
    credentials: KrakenCredentials | None = None,
    config: KrakenConfig | None = None,
    token_resolver: KrakenTokenResolver | None = None,
    chain_mapper: KrakenChainMapper | None = None,
)
```

SDK for Kraken exchange operations.

Wraps the python-kraken-sdk library with additional functionality:

- Token resolution (stack-v2 symbols -> Kraken symbols)
- Chain mapping for deposits/withdrawals
- Amount validation and precision handling
- Status polling for async operations

Thread Safety

This class is NOT thread-safe. Use separate instances per thread or implement proper synchronization.

Example

sdk = KrakenSDK(KrakenCredentials.from_env())

#### Get available balance

balances = sdk.get_balances(["USDC", "ETH"]) print(f"USDC available: {balances['USDC'].available}")

#### Execute a swap

userref = sdk.generate_userref() txid = sdk.swap( asset_in="USDC", asset_out="ETH", amount_in=1000_000000, # 1000 USDC decimals_in=6, userref=userref, )

Initialize Kraken SDK.

Parameters:

| Name             | Type                  | Description | Default                                              |
| ---------------- | --------------------- | ----------- | ---------------------------------------------------- |
| `credentials`    | \`KrakenCredentials   | None\`      | API credentials. If not provided, loads from env.    |
| `config`         | \`KrakenConfig        | None\`      | SDK configuration. Uses defaults if not provided.    |
| `token_resolver` | \`KrakenTokenResolver | None\`      | Custom token resolver. Uses default if not provided. |
| `chain_mapper`   | \`KrakenChainMapper   | None\`      | Custom chain mapper. Uses default if not provided.   |

#### get_all_balances

```
get_all_balances() -> dict[str, KrakenBalance]
```

Get all account balances.

Returns:

| Type                       | Description                                       |
| -------------------------- | ------------------------------------------------- |
| `dict[str, KrakenBalance]` | Dict mapping Kraken asset symbol to KrakenBalance |

#### get_balances

```
get_balances(
    assets: list[str], chain: str = "ethereum"
) -> dict[str, KrakenBalance]
```

Get balance information for specified assets.

Parameters:

| Name     | Type        | Description                                   | Default      |
| -------- | ----------- | --------------------------------------------- | ------------ |
| `assets` | `list[str]` | List of token symbols (e.g., ["USDC", "ETH"]) | *required*   |
| `chain`  | `str`       | Chain for token resolution                    | `'ethereum'` |

Returns:

| Type                       | Description                                |
| -------------------------- | ------------------------------------------ |
| `dict[str, KrakenBalance]` | Dict mapping asset symbol to KrakenBalance |

#### get_balance

```
get_balance(
    asset: str, chain: str = "ethereum"
) -> KrakenBalance
```

Get balance for a single asset.

Parameters:

| Name    | Type  | Description                | Default      |
| ------- | ----- | -------------------------- | ------------ |
| `asset` | `str` | Token symbol               | *required*   |
| `chain` | `str` | Chain for token resolution | `'ethereum'` |

Returns:

| Type            | Description                 |
| --------------- | --------------------------- |
| `KrakenBalance` | KrakenBalance for the asset |

#### get_market_info

```
get_market_info(
    base_asset: str,
    quote_asset: str,
    chain: str = "ethereum",
) -> KrakenMarketInfo
```

Get market information for a trading pair.

Parameters:

| Name          | Type  | Description                | Default      |
| ------------- | ----- | -------------------------- | ------------ |
| `base_asset`  | `str` | Base token symbol          | *required*   |
| `quote_asset` | `str` | Quote token symbol         | *required*   |
| `chain`       | `str` | Chain for token resolution | `'ethereum'` |

Returns:

| Type               | Description                        |
| ------------------ | ---------------------------------- |
| `KrakenMarketInfo` | KrakenMarketInfo with pair details |

Raises:

| Type                     | Description           |
| ------------------------ | --------------------- |
| `KrakenUnknownPairError` | If pair doesn't exist |

#### market_exists

```
market_exists(
    base_asset: str,
    quote_asset: str,
    chain: str = "ethereum",
) -> bool
```

Check if a trading pair exists.

Also checks the inverse pair (quote/base).

Parameters:

| Name          | Type  | Description                | Default      |
| ------------- | ----- | -------------------------- | ------------ |
| `base_asset`  | `str` | Base token symbol          | *required*   |
| `quote_asset` | `str` | Quote token symbol         | *required*   |
| `chain`       | `str` | Chain for token resolution | `'ethereum'` |

Returns:

| Type   | Description                               |
| ------ | ----------------------------------------- |
| `bool` | True if pair exists (in either direction) |

#### is_market_inverted

```
is_market_inverted(
    asset_in: str, asset_out: str, chain: str = "ethereum"
) -> bool
```

Check if market pair is inverted from asset_in/asset_out order.

Kraken markets have a specific base/quote ordering. This checks if the natural order (asset_in first) matches Kraken's order.

Parameters:

| Name        | Type  | Description                | Default      |
| ----------- | ----- | -------------------------- | ------------ |
| `asset_in`  | `str` | Input asset symbol         | *required*   |
| `asset_out` | `str` | Output asset symbol        | *required*   |
| `chain`     | `str` | Chain for token resolution | `'ethereum'` |

Returns:

| Type   | Description                                                   |
| ------ | ------------------------------------------------------------- |
| `bool` | True if Kraken's base/quote is opposite to asset_in/asset_out |

#### generate_userref

```
generate_userref() -> int
```

Generate a unique userref for order idempotency.

The userref is a 32-bit signed integer that identifies an order for idempotency. It must be persisted before submitting the order.

Returns:

| Type  | Description            |
| ----- | ---------------------- |
| `int` | Unique userref (int32) |

#### validate_swap_amount

```
validate_swap_amount(
    asset_in: str,
    asset_out: str,
    amount_in: int,
    decimals_in: int,
    chain: str = "ethereum",
) -> int
```

Validate and floor swap amount to Kraken precision.

Parameters:

| Name          | Type  | Description                | Default      |
| ------------- | ----- | -------------------------- | ------------ |
| `asset_in`    | `str` | Input asset symbol         | *required*   |
| `asset_out`   | `str` | Output asset symbol        | *required*   |
| `amount_in`   | `int` | Amount in wei units        | *required*   |
| `decimals_in` | `int` | Decimals of input asset    | *required*   |
| `chain`       | `str` | Chain for token resolution | `'ethereum'` |

Returns:

| Type  | Description                                   |
| ----- | --------------------------------------------- |
| `int` | Floored amount that meets Kraken requirements |

Raises:

| Type                           | Description                |
| ------------------------------ | -------------------------- |
| `KrakenMinimumOrderError`      | If amount is below minimum |
| `KrakenInsufficientFundsError` | If balance is insufficient |

#### swap

```
swap(
    asset_in: str,
    asset_out: str,
    amount_in: int,
    decimals_in: int,
    userref: int,
    chain: str = "ethereum",
    deadline: int | None = None,
) -> str
```

Execute a market swap on Kraken.

Parameters:

| Name          | Type  | Description                            | Default                     |
| ------------- | ----- | -------------------------------------- | --------------------------- |
| `asset_in`    | `str` | Input asset symbol (e.g., "USDC")      | *required*                  |
| `asset_out`   | `str` | Output asset symbol (e.g., "ETH")      | *required*                  |
| `amount_in`   | `int` | Amount in wei units                    | *required*                  |
| `decimals_in` | `int` | Decimals of input asset                | *required*                  |
| `userref`     | `int` | Unique order reference for idempotency | *required*                  |
| `chain`       | `str` | Chain for token resolution             | `'ethereum'`                |
| `deadline`    | \`int | None\`                                 | Optional deadline timestamp |

Returns:

| Type  | Description                 |
| ----- | --------------------------- |
| `str` | Order transaction ID (txid) |

Raises:

| Type                           | Description                |
| ------------------------------ | -------------------------- |
| `KrakenMinimumOrderError`      | If amount is below minimum |
| `KrakenInsufficientFundsError` | If balance is insufficient |
| `KrakenAPIError`               | If API call fails          |

#### get_swap_status

```
get_swap_status(txid: str, userref: int) -> str
```

Get status of a swap order.

Parameters:

| Name      | Type  | Description          | Default    |
| --------- | ----- | -------------------- | ---------- |
| `txid`    | `str` | Order transaction ID | *required* |
| `userref` | `int` | Order reference      | *required* |

Returns:

| Type  | Description                                    |
| ----- | ---------------------------------------------- |
| `str` | Status string: "pending", "success", "failed", |
| `str` | "partial", "cancelled", "unknown"              |

#### get_swap_result

```
get_swap_result(
    txid: str,
    userref: int,
    asset_in: str,
    asset_out: str,
    decimals_in: int,
    decimals_out: int,
    chain: str = "ethereum",
) -> dict[str, Any]
```

Get detailed result of a completed swap.

Parameters:

| Name           | Type  | Description                | Default      |
| -------------- | ----- | -------------------------- | ------------ |
| `txid`         | `str` | Order transaction ID       | *required*   |
| `userref`      | `int` | Order reference            | *required*   |
| `asset_in`     | `str` | Input asset symbol         | *required*   |
| `asset_out`    | `str` | Output asset symbol        | *required*   |
| `decimals_in`  | `int` | Input asset decimals       | *required*   |
| `decimals_out` | `int` | Output asset decimals      | *required*   |
| `chain`        | `str` | Chain for token resolution | `'ethereum'` |

Returns:

| Type             | Description                                                     |
| ---------------- | --------------------------------------------------------------- |
| `dict[str, Any]` | Dict with: amount_in, amount_out, fee, average_price, timestamp |

#### get_withdrawal_addresses

```
get_withdrawal_addresses(
    asset: str, chain: str
) -> set[str]
```

Get whitelisted withdrawal addresses.

Parameters:

| Name    | Type  | Description  | Default    |
| ------- | ----- | ------------ | ---------- |
| `asset` | `str` | Asset symbol | *required* |
| `chain` | `str` | Target chain | *required* |

Returns:

| Type       | Description                                |
| ---------- | ------------------------------------------ |
| `set[str]` | Set of whitelisted addresses (checksummed) |

#### get_withdrawal_key

```
get_withdrawal_key(
    asset: str, chain: str, address: str
) -> str
```

Get Kraken withdrawal key for an address.

Kraken requires a "key" (label) for withdrawals, not the address.

Parameters:

| Name      | Type  | Description         | Default    |
| --------- | ----- | ------------------- | ---------- |
| `asset`   | `str` | Asset symbol        | *required* |
| `chain`   | `str` | Target chain        | *required* |
| `address` | `str` | Destination address | *required* |

Returns:

| Type  | Description           |
| ----- | --------------------- |
| `str` | Kraken withdrawal key |

Raises:

| Type                                         | Description          |
| -------------------------------------------- | -------------------- |
| `KrakenWithdrawalAddressNotWhitelistedError` | If address not found |

#### withdraw

```
withdraw(
    asset: str,
    chain: str,
    amount: int,
    decimals: int,
    to_address: str,
) -> str
```

Initiate a withdrawal from Kraken.

Parameters:

| Name         | Type  | Description                               | Default    |
| ------------ | ----- | ----------------------------------------- | ---------- |
| `asset`      | `str` | Asset symbol                              | *required* |
| `chain`      | `str` | Target chain                              | *required* |
| `amount`     | `int` | Amount in wei units                       | *required* |
| `decimals`   | `int` | Asset decimals                            | *required* |
| `to_address` | `str` | Destination address (must be whitelisted) | *required* |

Returns:

| Type  | Description                     |
| ----- | ------------------------------- |
| `str` | Withdrawal reference ID (refid) |

Raises:

| Type                                         | Description                |
| -------------------------------------------- | -------------------------- |
| `KrakenWithdrawalAddressNotWhitelistedError` | If address not whitelisted |
| `KrakenInsufficientFundsError`               | If balance insufficient    |

#### get_withdrawal_status

```
get_withdrawal_status(
    asset: str,
    chain: str,
    refid: str | None = None,
    tx_hash: str | None = None,
) -> str | None
```

Get status of a withdrawal.

Parameters:

| Name      | Type  | Description  | Default                                  |
| --------- | ----- | ------------ | ---------------------------------------- |
| `asset`   | `str` | Asset symbol | *required*                               |
| `chain`   | `str` | Target chain | *required*                               |
| `refid`   | \`str | None\`       | Kraken reference ID (from withdraw())    |
| `tx_hash` | \`str | None\`       | On-chain transaction hash (if available) |

Returns:

| Name     | Type  | Description |
| -------- | ----- | ----------- |
| `Status` | \`str | None\`      |

#### get_withdrawal_tx_hash

```
get_withdrawal_tx_hash(
    asset: str, chain: str, refid: str
) -> str | None
```

Get on-chain transaction hash for a withdrawal.

Parameters:

| Name    | Type  | Description         | Default    |
| ------- | ----- | ------------------- | ---------- |
| `asset` | `str` | Asset symbol        | *required* |
| `chain` | `str` | Target chain        | *required* |
| `refid` | `str` | Kraken reference ID | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

#### get_deposit_addresses

```
get_deposit_addresses(asset: str, chain: str) -> set[str]
```

Get Kraken deposit addresses for an asset.

Parameters:

| Name    | Type  | Description  | Default    |
| ------- | ----- | ------------ | ---------- |
| `asset` | `str` | Asset symbol | *required* |
| `chain` | `str` | Source chain | *required* |

Returns:

| Type       | Description                            |
| ---------- | -------------------------------------- |
| `set[str]` | Set of deposit addresses (checksummed) |

#### get_deposit_status

```
get_deposit_status(
    tx_hash: str,
    asset: str | None = None,
    chain: str | None = None,
) -> str | None
```

Get status of a deposit by transaction hash.

Parameters:

| Name      | Type  | Description               | Default                      |
| --------- | ----- | ------------------------- | ---------------------------- |
| `tx_hash` | `str` | On-chain transaction hash | *required*                   |
| `asset`   | \`str | None\`                    | Optional asset for filtering |
| `chain`   | \`str | None\`                    | Optional chain for filtering |

Returns:

| Name     | Type  | Description |
| -------- | ----- | ----------- |
| `Status` | \`str | None\`      |

### KrakenChainMapper

Maps stack-v2 chains to Kraken deposit/withdrawal method names.

Kraken uses specific method strings for deposits and withdrawals that include the chain name and sometimes the asset.

Example

mapper = KrakenChainMapper()

#### Get deposit method

method = mapper.get_deposit_method("arbitrum", "ETH")

#### Returns: "ETH - Arbitrum One (Unified)"

#### Get withdrawal method

method = mapper.get_withdraw_method("arbitrum")

#### Returns: "Arbitrum One"

#### get_deposit_method

```
get_deposit_method(chain: str, asset: str) -> str
```

Get Kraken deposit method name for a chain and asset.

Parameters:

| Name    | Type  | Description                        | Default    |
| ------- | ----- | ---------------------------------- | ---------- |
| `chain` | `str` | Chain name (e.g., "arbitrum")      | *required* |
| `asset` | `str` | Asset symbol (e.g., "ETH", "USDC") | *required* |

Returns:

| Type  | Description                  |
| ----- | ---------------------------- |
| `str` | Kraken deposit method string |

Raises:

| Type                           | Description               |
| ------------------------------ | ------------------------- |
| `KrakenChainNotSupportedError` | If chain is not supported |

#### get_withdraw_method

```
get_withdraw_method(chain: str) -> str
```

Get Kraken withdrawal method name for a chain.

Parameters:

| Name    | Type  | Description                   | Default    |
| ------- | ----- | ----------------------------- | ---------- |
| `chain` | `str` | Chain name (e.g., "arbitrum") | *required* |

Returns:

| Type  | Description                     |
| ----- | ------------------------------- |
| `str` | Kraken withdrawal method string |

Raises:

| Type                           | Description               |
| ------------------------------ | ------------------------- |
| `KrakenChainNotSupportedError` | If chain is not supported |

#### get_supported_chains

```
get_supported_chains() -> list[str]
```

Get list of supported chains for deposits/withdrawals.

#### chain_from_network

```
chain_from_network(network_string: str) -> str | None
```

Parse Kraken network string to chain name.

Used when parsing deposit/withdrawal status responses.

Parameters:

| Name             | Type  | Description                             | Default    |
| ---------------- | ----- | --------------------------------------- | ---------- |
| `network_string` | `str` | Kraken network string from API response | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

#### parse_deposit_method

```
parse_deposit_method(
    method_string: str, expected_asset: str | None = None
) -> str | None
```

Parse deposit method string to extract chain.

Parameters:

| Name             | Type  | Description                                                  | Default                               |
| ---------------- | ----- | ------------------------------------------------------------ | ------------------------------------- |
| `method_string`  | `str` | Kraken deposit method (e.g., "ETH - Arbitrum One (Unified)") | *required*                            |
| `expected_asset` | \`str | None\`                                                       | If provided, verify the asset matches |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

### KrakenTokenResolver

Maps stack-v2 tokens to Kraken symbols.

Kraken uses non-standard symbols for some assets:

- ETH -> XETH (internal)
- BTC -> XXBT (internal)
- USD -> ZUSD (for some pairs)

This resolver handles the mapping in both directions.

Example

resolver = KrakenTokenResolver()

#### Convert to Kraken format

kraken_sym = resolver.to_kraken_symbol("arbitrum", "ETH") # "ETH" kraken_sym = resolver.to_kraken_symbol("arbitrum", "USDC.e") # "USDC"

#### Convert from Kraken format

standard = resolver.from_kraken_symbol("XETH") # "ETH"

#### to_kraken_symbol

```
to_kraken_symbol(chain: str, token: str) -> str
```

Convert stack-v2 token to Kraken symbol.

Parameters:

| Name    | Type  | Description                          | Default    |
| ------- | ----- | ------------------------------------ | ---------- |
| `chain` | `str` | Blockchain name (e.g., "arbitrum")   | *required* |
| `token` | `str` | Token symbol (e.g., "USDC.e", "ETH") | *required* |

Returns:

| Type  | Description                 |
| ----- | --------------------------- |
| `str` | Kraken symbol for the token |

#### from_kraken_symbol

```
from_kraken_symbol(symbol: str) -> str
```

Convert Kraken symbol to standard token symbol.

Parameters:

| Name     | Type  | Description                          | Default    |
| -------- | ----- | ------------------------------------ | ---------- |
| `symbol` | `str` | Kraken symbol (e.g., "XETH", "XXBT") | *required* |

Returns:

| Type  | Description           |
| ----- | --------------------- |
| `str` | Standard token symbol |

#### get_trading_pair

```
get_trading_pair(
    base_token: str,
    quote_token: str,
    chain: str = "ethereum",
) -> str
```

Get Kraken trading pair symbol.

Parameters:

| Name          | Type  | Description                | Default      |
| ------------- | ----- | -------------------------- | ------------ |
| `base_token`  | `str` | Base asset symbol          | *required*   |
| `quote_token` | `str` | Quote asset symbol         | *required*   |
| `chain`       | `str` | Chain for token resolution | `'ethereum'` |

Returns:

| Type  | Description                   |
| ----- | ----------------------------- |
| `str` | Trading pair (e.g., "ETHUSD") |

# Lagoon

Connector for Lagoon protocol.

## almanak.framework.connectors.lagoon

Lagoon Vault Connector.

This module provides a low-level SDK and adapter for interacting with Lagoon vault contracts (ERC-7540) through the gateway's RPC service.

Supported operations:

- Read vault state (total assets, pending deposits/redemptions, share price)
- Read storage slots (proposed total assets, silo address)
- Verify vault contract version
- Build ActionBundles for vault write operations (propose, settle)
- Deploy new Lagoon vaults via factory contracts

Example

from almanak.framework.connectors.lagoon import LagoonVaultSDK, LagoonVaultAdapter

sdk = LagoonVaultSDK(gateway_client, chain="ethereum") adapter = LagoonVaultAdapter(sdk)

from almanak.framework.connectors.lagoon import LagoonVaultDeployer, VaultDeployParams

deployer = LagoonVaultDeployer()

### LagoonVaultAdapter

```
LagoonVaultAdapter(
    sdk: LagoonVaultSDK, token_resolver=None
)
```

Adapter that converts vault params into ActionBundles.

The adapter builds ActionBundles from vault operation params using the LagoonVaultSDK to construct unsigned transactions. It does NOT execute transactions -- the VaultLifecycleManager passes bundles to the ExecutionOrchestrator for execution.

Parameters:

| Name  | Type             | Description                                          | Default    |
| ----- | ---------------- | ---------------------------------------------------- | ---------- |
| `sdk` | `LagoonVaultSDK` | A LagoonVaultSDK instance for building transactions. | *required* |

#### build_propose_valuation_bundle

```
build_propose_valuation_bundle(
    params: UpdateTotalAssetsParams,
) -> ActionBundle
```

Build an ActionBundle for proposing a new vault valuation.

Parameters:

| Name     | Type                      | Description                                                                                 | Default    |
| -------- | ------------------------- | ------------------------------------------------------------------------------------------- | ---------- |
| `params` | `UpdateTotalAssetsParams` | Parameters containing vault address, valuator address, and the proposed total assets value. | *required* |

Returns:

| Type           | Description                                     |
| -------------- | ----------------------------------------------- |
| `ActionBundle` | ActionBundle with a single propose transaction. |

#### build_settle_deposit_bundle

```
build_settle_deposit_bundle(
    params: SettleDepositParams,
) -> ActionBundle
```

Build an ActionBundle for settling pending deposits.

Parameters:

| Name     | Type                  | Description                                                                                   | Default    |
| -------- | --------------------- | --------------------------------------------------------------------------------------------- | ---------- |
| `params` | `SettleDepositParams` | Parameters containing vault address, safe address, and the total assets value for settlement. | *required* |

Returns:

| Type           | Description                                            |
| -------------- | ------------------------------------------------------ |
| `ActionBundle` | ActionBundle with a single settle deposit transaction. |

#### build_settle_redeem_bundle

```
build_settle_redeem_bundle(
    params: SettleRedeemParams,
) -> ActionBundle
```

Build an ActionBundle for settling pending redemptions.

Parameters:

| Name     | Type                 | Description                                                                                   | Default    |
| -------- | -------------------- | --------------------------------------------------------------------------------------------- | ---------- |
| `params` | `SettleRedeemParams` | Parameters containing vault address, safe address, and the total assets value for settlement. | *required* |

Returns:

| Type           | Description                                           |
| -------------- | ----------------------------------------------------- |
| `ActionBundle` | ActionBundle with a single settle redeem transaction. |

### LagoonVaultDeployer

```
LagoonVaultDeployer(gateway_client=None)
```

Deploys new Lagoon vaults via factory contracts.

Uses the Lagoon OptinProxyFactory with proper ABI-encoded calldata. The factory function signature is: createVaultProxy(address logic, address owner, uint256 delay, InitStruct init, bytes32 salt)

Where InitStruct is

(address underlying, string name, string symbol, address admin, address safe, address feeReceiver, address valuationManager, address whitelistManager, uint16 managementRate, uint16 performanceRate, bool enableWhitelist, uint256 rateUpdateCooldown)

Parameters:

| Name             | Type | Description                                                              | Default |
| ---------------- | ---- | ------------------------------------------------------------------------ | ------- |
| `gateway_client` |      | Gateway client for RPC calls (needed to read vault logic from registry). | `None`  |

#### get_factory_address

```
get_factory_address(chain: str) -> str
```

Look up the factory address for a chain.

Returns:

| Type  | Description               |
| ----- | ------------------------- |
| `str` | Factory contract address. |

Raises:

| Type         | Description                    |
| ------------ | ------------------------------ |
| `ValueError` | If chain has no known factory. |

#### build_deploy_vault_tx

```
build_deploy_vault_tx(
    params: VaultDeployParams,
) -> dict[str, Any]
```

Build an unsigned transaction to deploy a new vault proxy.

Uses eth_abi for proper ABI encoding of the createVaultProxy call.

Parameters:

| Name     | Type                | Description                  | Default    |
| -------- | ------------------- | ---------------------------- | ---------- |
| `params` | `VaultDeployParams` | Vault deployment parameters. | *required* |

Returns:

| Type             | Description                                                               |
| ---------------- | ------------------------------------------------------------------------- |
| `dict[str, Any]` | Unsigned transaction dict with keys: to, from, data, value, gas_estimate. |

Raises:

| Type         | Description                                        |
| ------------ | -------------------------------------------------- |
| `ValueError` | If chain is unsupported or parameters are invalid. |

#### build_deploy_vault_bundle

```
build_deploy_vault_bundle(
    params: VaultDeployParams,
) -> ActionBundle
```

Build an ActionBundle for vault deployment.

Parameters:

| Name     | Type                | Description                  | Default    |
| -------- | ------------------- | ---------------------------- | ---------- |
| `params` | `VaultDeployParams` | Vault deployment parameters. | *required* |

Returns:

| Type           | Description                                   |
| -------------- | --------------------------------------------- |
| `ActionBundle` | ActionBundle wrapping the deploy transaction. |

#### parse_deploy_receipt

```
parse_deploy_receipt(
    receipt: dict[str, Any],
) -> VaultDeployResult
```

Parse a deployment transaction receipt to extract the vault address.

Looks for ProxyDeployed event in logs. The vault address is in the event data (first 32 bytes), not in topics.

Parameters:

| Name      | Type             | Description                                                        | Default    |
| --------- | ---------------- | ------------------------------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs', 'transactionHash', 'status'. | *required* |

Returns:

| Type                | Description                                     |
| ------------------- | ----------------------------------------------- |
| `VaultDeployResult` | VaultDeployResult with extracted vault address. |

#### build_approve_underlying_tx

```
build_approve_underlying_tx(
    underlying_token_address: str,
    vault_address: str,
    safe_address: str,
    *,
    approval_amount: int | None = None,
) -> dict[str, Any]
```

Build an ERC20 approve transaction for the Safe to allow vault redemptions.

Post-deployment: the Safe calls `underlying.approve(vault, amount)` so the vault can pull tokens during redemption settlement.

Security rationale for MAX_UINT256 default: This is the standard ERC-4626 vault approval pattern. The vault contract is the trust boundary -- if it is compromised, the approval amount is moot because the vault already has custody of deposited funds. A scoped approval would require re-approving before every settlement cycle, adding gas cost and operational complexity with no meaningful security improvement. Callers who want a tighter bound can pass `approval_amount` to cap the approval.

Parameters:

| Name                       | Type  | Description                                         | Default                                                                                   |
| -------------------------- | ----- | --------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `underlying_token_address` | `str` | The ERC20 token the vault manages.                  | *required*                                                                                |
| `vault_address`            | `str` | The newly deployed vault address (spender).         | *required*                                                                                |
| `safe_address`             | `str` | The Safe wallet address (token holder / tx sender). | *required*                                                                                |
| `approval_amount`          | \`int | None\`                                              | Optional cap on the approval amount. Defaults to MAX_UINT256 (standard ERC-4626 pattern). |

Returns:

| Type             | Description                |
| ---------------- | -------------------------- |
| `dict[str, Any]` | Unsigned transaction dict. |

#### build_post_deploy_bundle

```
build_post_deploy_bundle(
    underlying_token_address: str,
    vault_address: str,
    safe_address: str,
) -> ActionBundle
```

Build an ActionBundle for post-deployment approval.

Parameters:

| Name                       | Type  | Description                        | Default    |
| -------------------------- | ----- | ---------------------------------- | ---------- |
| `underlying_token_address` | `str` | The ERC20 token the vault manages. | *required* |
| `vault_address`            | `str` | The newly deployed vault address.  | *required* |
| `safe_address`             | `str` | The Safe wallet address.           | *required* |

Returns:

| Type           | Description                                    |
| -------------- | ---------------------------------------------- |
| `ActionBundle` | ActionBundle wrapping the approve transaction. |

#### get_default_logic

```
get_default_logic(
    chain: str, factory_address: str | None = None
) -> str
```

Read the default vault logic address from the factory's registry.

Calls registry() on the factory, then defaultLogic() on the registry.

Parameters:

| Name              | Type  | Description       | Default                                                |
| ----------------- | ----- | ----------------- | ------------------------------------------------------ |
| `chain`           | `str` | Chain identifier. | *required*                                             |
| `factory_address` | \`str | None\`            | Override factory address (uses chain default if None). |

Returns:

| Type  | Description                           |
| ----- | ------------------------------------- |
| `str` | Default vault implementation address. |

Raises:

| Type           | Description                                            |
| -------------- | ------------------------------------------------------ |
| `RuntimeError` | If gateway client is not configured or RPC call fails. |

### VaultDeployParams

```
VaultDeployParams(
    chain: str,
    underlying_token_address: str,
    name: str,
    symbol: str,
    safe_address: str,
    admin_address: str,
    fee_receiver_address: str,
    deployer_address: str,
    logic_address: str | None = None,
    valuation_manager_address: str | None = None,
    whitelist_manager_address: str | None = None,
    management_rate_bps: int = 200,
    performance_rate_bps: int = 2000,
    enable_whitelist: bool = False,
    rate_update_cooldown: int = 86400,
    deploy_delay: int = 86400,
    salt: bytes | None = None,
)
```

Parameters for deploying a new Lagoon vault.

### VaultDeployResult

```
VaultDeployResult(
    success: bool,
    vault_address: str | None = None,
    transaction_hash: str | None = None,
    error: str | None = None,
)
```

Result of parsing a vault deployment receipt.

### LagoonReceiptParser

```
LagoonReceiptParser(chain: str = 'ethereum', **kwargs: Any)
```

Parser for Lagoon vault transaction receipts.

Handles three vault settlement event types:

- SettleDeposit: deposit settlement with epoch, assets, and shares data
- SettleRedeem: redemption settlement with epoch, assets, and shares data
- NewTotalAssets: valuation update with new total assets value

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> LagoonParseResult
```

Parse a transaction receipt for Lagoon vault events.

Parameters:

| Name      | Type             | Description                                                   | Default    |
| --------- | ---------------- | ------------------------------------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs', 'transactionHash', etc. | *required* |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `LagoonParseResult` | LagoonParseResult with extracted events |

#### parse_event

```
parse_event(
    log: dict[str, Any],
) -> (
    SettleDepositEventData
    | SettleRedeemEventData
    | NewTotalAssetsEventData
    | None
)
```

Parse a single log entry for Lagoon vault events.

### LagoonVaultSDK

```
LagoonVaultSDK(gateway_client, chain: str)
```

Low-level SDK for reading Lagoon vault state via gateway RPC calls.

All RPC calls are routed through the gateway client's RPC service. This SDK handles ABI encoding/decoding and provides typed return values.

Parameters:

| Name             | Type  | Description                                 | Default    |
| ---------------- | ----- | ------------------------------------------- | ---------- |
| `gateway_client` |       | Connected gateway client with RPC service   | *required* |
| `chain`          | `str` | Chain identifier (e.g., "ethereum", "base") | *required* |

#### get_total_assets

```
get_total_assets(vault_address: str) -> int
```

Read the vault's total assets (totalAssets()).

Returns:

| Type  | Description                                           |
| ----- | ----------------------------------------------------- |
| `int` | Total assets in underlying token units (raw integer). |

#### get_pending_deposits

```
get_pending_deposits(
    vault_address: str, request_id_num: int = 0
) -> int
```

Read pending deposit requests for the vault.

Uses ERC-7540 pendingDepositRequest(uint256,address) with requestId=0 and the vault address as the controller.

Returns:

| Type  | Description                                               |
| ----- | --------------------------------------------------------- |
| `int` | Pending deposits in underlying token units (raw integer). |

#### get_pending_redemptions

```
get_pending_redemptions(
    vault_address: str, request_id_num: int = 0
) -> int
```

Read pending redemption requests for the vault.

Uses ERC-7540 pendingRedeemRequest(uint256,address) with requestId=0 and the vault address as the controller.

Returns:

| Type  | Description                                       |
| ----- | ------------------------------------------------- |
| `int` | Pending redemptions in share units (raw integer). |

#### get_share_price

```
get_share_price(vault_address: str) -> Decimal
```

Get the current share price by converting 1 share to assets.

Uses convertToAssets(1e18) to get the value of one full share in underlying token units.

Returns:

| Type      | Description                                                             |
| --------- | ----------------------------------------------------------------------- |
| `Decimal` | Share price as a Decimal (assets per share, normalized to 18 decimals). |

#### convert_to_assets

```
convert_to_assets(vault_address: str, shares: int) -> int
```

Convert a share amount to underlying asset units via on-chain call.

Uses the ERC-4626 convertToAssets(uint256) function directly with the given share amount, avoiding precision loss from intermediate division.

Parameters:

| Name            | Type  | Description                              | Default    |
| --------------- | ----- | ---------------------------------------- | ---------- |
| `vault_address` | `str` | Vault contract address.                  | *required* |
| `shares`        | `int` | Share amount in raw units (18 decimals). | *required* |

Returns:

| Type  | Description                                                            |
| ----- | ---------------------------------------------------------------------- |
| `int` | Equivalent underlying asset amount in raw units (underlying decimals). |

#### get_underlying_balance

```
get_underlying_balance(
    vault_address: str, wallet_address: str
) -> int
```

Read the underlying token balance of a wallet in the vault context.

Uses balanceOf(address) on the vault to read share balance, then could be converted to underlying. Returns raw share balance.

Returns:

| Type  | Description                  |
| ----- | ---------------------------- |
| `int` | Share balance (raw integer). |

#### get_proposed_total_assets

```
get_proposed_total_assets(vault_address: str) -> int
```

Read the proposed total assets via direct storage slot read.

This value is set during the propose phase of settlement and represents the valuator's proposed total asset value.

Returns:

| Type  | Description                                                    |
| ----- | -------------------------------------------------------------- |
| `int` | Proposed total assets in underlying token units (raw integer). |

#### get_silo_address

```
get_silo_address(vault_address: str) -> str
```

Read the silo contract address via direct storage slot read.

The silo is a helper contract that holds deposited assets during the settlement process.

Returns:

| Type  | Description                                        |
| ----- | -------------------------------------------------- |
| `str` | Silo contract address as a checksummed hex string. |

#### get_underlying_token_address

```
get_underlying_token_address(vault_address: str) -> str
```

Read the vault's underlying token address (ERC-4626 asset()).

Returns:

| Type  | Description                                        |
| ----- | -------------------------------------------------- |
| `str` | Underlying token contract address as a hex string. |

#### get_roles_storage

```
get_roles_storage(vault_address: str) -> dict
```

Read the vault's RolesStorage via getRolesStorage().

Returns a dict with the vault's role addresses, matching Lagoon v0.5.0: whitelistManager, feeReceiver, safe, feeRegistry, valuationManager

Returns:

| Type   | Description                                                                         |
| ------ | ----------------------------------------------------------------------------------- |
| `dict` | Dict with keys: whitelistManager, feeReceiver, safe, feeRegistry, valuationManager. |

#### get_valuation_manager

```
get_valuation_manager(vault_address: str) -> str
```

Read the vault's valuation manager address.

Convenience method that calls getRolesStorage() and extracts the valuationManager. This is the address authorized to call updateNewTotalAssets().

Returns:

| Type  | Description                                |
| ----- | ------------------------------------------ |
| `str` | Valuation manager address as a hex string. |

#### get_curator

```
get_curator(vault_address: str) -> str
```

Read the vault's curator (Safe) address.

Convenience method that calls getRolesStorage() and extracts the Safe address. This is the address that owns the vault and can call settleDeposit/settleRedeem.

Returns:

| Type  | Description                             |
| ----- | --------------------------------------- |
| `str` | Curator (Safe) address as a hex string. |

#### verify_version

```
verify_version(
    vault_address: str, expected_version: VaultVersion
) -> None
```

Verify the on-chain vault version matches the expected version.

Reads the version() string from the vault contract and compares it to the expected VaultVersion. Raises ValueError on mismatch.

Parameters:

| Name               | Type           | Description                           | Default    |
| ------------------ | -------------- | ------------------------------------- | ---------- |
| `vault_address`    | `str`          | The vault contract address.           | *required* |
| `expected_version` | `VaultVersion` | The expected VaultVersion enum value. | *required* |

Raises:

| Type         | Description                             |
| ------------ | --------------------------------------- |
| `ValueError` | If the on-chain version does not match. |

#### build_update_total_assets_tx

```
build_update_total_assets_tx(
    vault_address: str,
    valuator_address: str,
    new_total_assets: int,
) -> dict
```

Build an unsigned transaction for updateNewTotalAssets(uint256).

This is called by the valuator to propose a new total asset valuation during the settlement process.

Parameters:

| Name               | Type  | Description                                          | Default    |
| ------------------ | ----- | ---------------------------------------------------- | ---------- |
| `vault_address`    | `str` | The vault contract address.                          | *required* |
| `valuator_address` | `str` | The valuator's address (tx sender).                  | *required* |
| `new_total_assets` | `int` | The proposed total assets in underlying token units. | *required* |

Returns:

| Type   | Description                                                               |
| ------ | ------------------------------------------------------------------------- |
| `dict` | Unsigned transaction dict with keys: to, from, data, value, gas_estimate. |

#### build_settle_deposit_tx

```
build_settle_deposit_tx(
    vault_address: str, safe_address: str, total_assets: int
) -> dict
```

Build an unsigned transaction for settleDeposit(uint256).

This is called by the safe (vault owner) to settle pending deposits after the valuator has proposed a new total asset value.

Parameters:

| Name            | Type  | Description                            | Default    |
| --------------- | ----- | -------------------------------------- | ---------- |
| `vault_address` | `str` | The vault contract address.            | *required* |
| `safe_address`  | `str` | The safe wallet address (tx sender).   | *required* |
| `total_assets`  | `int` | The total assets value for settlement. | *required* |

Returns:

| Type   | Description                                                               |
| ------ | ------------------------------------------------------------------------- |
| `dict` | Unsigned transaction dict with keys: to, from, data, value, gas_estimate. |

#### build_settle_redeem_tx

```
build_settle_redeem_tx(
    vault_address: str, safe_address: str, total_assets: int
) -> dict
```

Build an unsigned transaction for settleRedeem(uint256).

This is called by the safe (vault owner) to settle pending redemptions after deposits have been settled.

Parameters:

| Name            | Type  | Description                            | Default    |
| --------------- | ----- | -------------------------------------- | ---------- |
| `vault_address` | `str` | The vault contract address.            | *required* |
| `safe_address`  | `str` | The safe wallet address (tx sender).   | *required* |
| `total_assets`  | `int` | The total assets value for settlement. | *required* |

Returns:

| Type   | Description                                                               |
| ------ | ------------------------------------------------------------------------- |
| `dict` | Unsigned transaction dict with keys: to, from, data, value, gas_estimate. |

#### build_approve_deposit_tx

```
build_approve_deposit_tx(
    underlying_token: str,
    vault_address: str,
    depositor: str,
    amount: int,
) -> dict
```

Build an ERC20 approve tx so the vault can pull underlying tokens.

The depositor must approve the vault to spend `amount` of the underlying token before calling requestDeposit.

Parameters:

| Name               | Type  | Description                                      | Default    |
| ------------------ | ----- | ------------------------------------------------ | ---------- |
| `underlying_token` | `str` | Address of the underlying ERC20 token.           | *required* |
| `vault_address`    | `str` | The vault contract address (spender).            | *required* |
| `depositor`        | `str` | The depositor address (tx sender / token owner). | *required* |
| `amount`           | `int` | Amount in raw underlying units to approve.       | *required* |

Returns:

| Type   | Description                |
| ------ | -------------------------- |
| `dict` | Unsigned transaction dict. |

#### build_request_deposit_tx

```
build_request_deposit_tx(
    vault_address: str, depositor: str, amount: int
) -> dict
```

Build an ERC-7540 requestDeposit(uint256,address,address) tx.

Calls vault.requestDeposit(assets, controller=depositor, owner=depositor). The depositor must have approved the vault for `amount` of underlying first.

Parameters:

| Name            | Type  | Description                                         | Default    |
| --------------- | ----- | --------------------------------------------------- | ---------- |
| `vault_address` | `str` | The vault contract address.                         | *required* |
| `depositor`     | `str` | The depositor address (controller and owner).       | *required* |
| `amount`        | `int` | Amount of underlying tokens to deposit (raw units). | *required* |

Returns:

| Type   | Description                |
| ------ | -------------------------- |
| `dict` | Unsigned transaction dict. |

# Lido

Connector for Lido liquid staking protocol.

## almanak.framework.connectors.lido

Lido Connector.

This module provides an adapter for interacting with Lido liquid staking protocol.

Lido is a decentralized liquid staking protocol supporting:

- Stake ETH to receive stETH
- Wrap stETH to wstETH (non-rebasing)
- Unwrap wstETH to stETH

Supported chains:

- Ethereum (full staking + wrap/unwrap)
- Arbitrum, Optimism, Polygon (wstETH only)

Example

from almanak.framework.connectors.lido import LidoAdapter, LidoConfig

config = LidoConfig( chain="ethereum", wallet_address="0x...", ) adapter = LidoAdapter(config)

### Stake ETH to receive stETH

result = adapter.stake(amount=Decimal("1.0"))

### Wrap stETH to wstETH

result = adapter.wrap(amount=Decimal("1.0"))

### LidoAdapter

```
LidoAdapter(
    config: LidoConfig,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Lido liquid staking protocol.

This adapter provides methods for interacting with Lido:

- Stake ETH to receive stETH
- Wrap stETH to wstETH (non-rebasing)
- Unwrap wstETH back to stETH

Note: stETH is a rebasing token - balances change daily as rewards accrue. wstETH is non-rebasing and preferred for DeFi integrations.

Example

config = LidoConfig( chain="ethereum", wallet_address="0x...", ) adapter = LidoAdapter(config)

#### Stake ETH to get stETH

result = adapter.stake(Decimal("1.0"))

#### Wrap stETH to wstETH

result = adapter.wrap(Decimal("1.0"))

#### Unwrap wstETH back to stETH

result = adapter.unwrap(Decimal("1.0"))

Initialize the adapter.

Parameters:

| Name             | Type            | Description           | Default                                                   |
| ---------------- | --------------- | --------------------- | --------------------------------------------------------- |
| `config`         | `LidoConfig`    | Adapter configuration | *required*                                                |
| `token_resolver` | \`TokenResolver | None\`                | Optional TokenResolver instance. If None, uses singleton. |

#### stake

```
stake(
    amount: Decimal,
    referral: str = "0x0000000000000000000000000000000000000000",
) -> TransactionResult
```

Build a stake transaction to receive stETH.

Stakes ETH to the Lido stETH contract and receives stETH in return. Only available on Ethereum mainnet.

Parameters:

| Name       | Type      | Description                              | Default                                        |
| ---------- | --------- | ---------------------------------------- | ---------------------------------------------- |
| `amount`   | `Decimal` | Amount of ETH to stake                   | *required*                                     |
| `referral` | `str`     | Referral address (default: zero address) | `'0x0000000000000000000000000000000000000000'` |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### wrap

```
wrap(amount: Decimal) -> TransactionResult
```

Build a wrap transaction to convert stETH to wstETH.

Parameters:

| Name     | Type      | Description             | Default    |
| -------- | --------- | ----------------------- | ---------- |
| `amount` | `Decimal` | Amount of stETH to wrap | *required* |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### unwrap

```
unwrap(amount: Decimal) -> TransactionResult
```

Build an unwrap transaction to convert wstETH to stETH.

Parameters:

| Name     | Type      | Description                | Default    |
| -------- | --------- | -------------------------- | ---------- |
| `amount` | `Decimal` | Amount of wstETH to unwrap | *required* |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### request_withdrawal

```
request_withdrawal(
    amounts: list[Decimal], owner: str | None = None
) -> TransactionResult
```

Build a withdrawal request transaction.

Requests stETH withdrawal from the Lido withdrawal queue. Each amount in the list creates a separate withdrawal request. Only available on Ethereum mainnet.

Note: Requires prior approval of stETH to the withdrawal queue contract.

Parameters:

| Name      | Type            | Description                       | Default                                                          |
| --------- | --------------- | --------------------------------- | ---------------------------------------------------------------- |
| `amounts` | `list[Decimal]` | List of stETH amounts to withdraw | *required*                                                       |
| `owner`   | \`str           | None\`                            | Address to own the withdrawal requests (default: wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### claim_withdrawals

```
claim_withdrawals(
    request_ids: list[int], hints: list[int] | None = None
) -> TransactionResult
```

Build a claim withdrawals transaction.

Claims finalized withdrawal requests, sending ETH to msg.sender. Only available on Ethereum mainnet.

Note: Request IDs are returned when calling requestWithdrawals(). Hints can be obtained from findCheckpointHints() on the withdrawal queue, or pass None/empty list for the contract to compute them (higher gas).

Parameters:

| Name          | Type        | Description                             | Default                                                       |
| ------------- | ----------- | --------------------------------------- | ------------------------------------------------------------- |
| `request_ids` | `list[int]` | List of withdrawal request IDs to claim | *required*                                                    |
| `hints`       | \`list[int] | None\`                                  | Checkpoint hints for each request ID (optional, improves gas) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### compile_stake_intent

```
compile_stake_intent(
    intent: StakeIntent, market_snapshot: Any | None = None
) -> ActionBundle
```

Compile a StakeIntent to an ActionBundle.

This method converts a high-level StakeIntent into executable transaction data. It handles the receive_wrapped flag:

- If receive_wrapped=True: stake ETH -> stETH, then wrap stETH -> wstETH
- If receive_wrapped=False: stake ETH -> stETH only

Parameters:

| Name              | Type          | Description                | Default                                  |
| ----------------- | ------------- | -------------------------- | ---------------------------------------- |
| `intent`          | `StakeIntent` | The StakeIntent to compile | *required*                               |
| `market_snapshot` | \`Any         | None\`                     | Optional market data (not used for Lido) |

Returns:

| Type           | Description                                          |
| -------------- | ---------------------------------------------------- |
| `ActionBundle` | ActionBundle containing transaction(s) for execution |

Raises:

| Type         | Description                                        |
| ------------ | -------------------------------------------------- |
| `ValueError` | If amount="all" is not resolved before compilation |

#### compile_unstake_intent

```
compile_unstake_intent(
    intent: UnstakeIntent,
    market_snapshot: Any | None = None,
) -> ActionBundle
```

Compile an UnstakeIntent to an ActionBundle.

This method converts a high-level UnstakeIntent into executable transaction data. It handles the token_in type:

- If token_in is wstETH: unwrap wstETH -> stETH first, then request withdrawal
- If token_in is stETH: request withdrawal directly

Note: This only initiates the withdrawal request. Actual ETH claiming happens separately after the withdrawal is finalized (claim_withdrawals).

Parameters:

| Name              | Type            | Description                  | Default                                  |
| ----------------- | --------------- | ---------------------------- | ---------------------------------------- |
| `intent`          | `UnstakeIntent` | The UnstakeIntent to compile | *required*                               |
| `market_snapshot` | \`Any           | None\`                       | Optional market data (not used for Lido) |

Returns:

| Type           | Description                                          |
| -------------- | ---------------------------------------------------- |
| `ActionBundle` | ActionBundle containing transaction(s) for execution |

Raises:

| Type         | Description                                        |
| ------------ | -------------------------------------------------- |
| `ValueError` | If amount="all" is not resolved before compilation |

### LidoConfig

```
LidoConfig(chain: str, wallet_address: str)
```

Configuration for Lido adapter.

Attributes:

| Name             | Type  | Description                                                |
| ---------------- | ----- | ---------------------------------------------------------- |
| `chain`          | `str` | Blockchain network (ethereum, arbitrum, optimism, polygon) |
| `wallet_address` | `str` | User wallet address                                        |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

### TransactionResult

```
TransactionResult(
    success: bool,
    tx_data: dict[str, Any] | None = None,
    gas_estimate: int = 0,
    description: str = "",
    error: str | None = None,
)
```

Result of a transaction build operation.

Attributes:

| Name           | Type             | Description                 |
| -------------- | ---------------- | --------------------------- |
| `success`      | `bool`           | Whether operation succeeded |
| `tx_data`      | \`dict[str, Any] | None\`                      |
| `gas_estimate` | `int`            | Estimated gas               |
| `description`  | `str`            | Human-readable description  |
| `error`        | \`str            | None\`                      |

### LidoEventType

Bases: `Enum`

Lido event types.

### LidoReceiptParser

```
LidoReceiptParser(chain: str = 'ethereum', **kwargs: Any)
```

Parser for Lido transaction receipts.

Refactored to use base infrastructure utilities for hex decoding and event registry management. Handles multiple contracts (stETH, wstETH, withdrawal queue) with different event types.

Initialize the parser.

Parameters:

| Name       | Type  | Description                                                | Default      |
| ---------- | ----- | ---------------------------------------------------------- | ------------ |
| `chain`    | `str` | Blockchain network (ethereum, arbitrum, optimism, polygon) | `'ethereum'` |
| `**kwargs` | `Any` | Additional arguments (ignored for compatibility)           | `{}`         |

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name      | Type             | Description              | Default    |
| --------- | ---------------- | ------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict | *required* |

Returns:

| Type          | Description                       |
| ------------- | --------------------------------- |
| `ParseResult` | ParseResult with extracted events |

#### parse_stake

```
parse_stake(log: dict[str, Any]) -> StakeEventData | None
```

Parse a Submitted (stake) event from a single log entry.

#### parse_wrap

```
parse_wrap(log: dict[str, Any]) -> WrapEventData | None
```

Parse a wrap event (Transfer from zero address) from a single log entry.

#### parse_unwrap

```
parse_unwrap(log: dict[str, Any]) -> UnwrapEventData | None
```

Parse an unwrap event (Transfer to zero address) from a single log entry.

#### parse_withdrawal_requested

```
parse_withdrawal_requested(
    log: dict[str, Any],
) -> WithdrawalRequestedEventData | None
```

Parse a WithdrawalRequested event from a single log entry.

#### parse_withdrawals_claimed

```
parse_withdrawals_claimed(
    log: dict[str, Any],
) -> WithdrawalClaimedEventData | None
```

Parse a WithdrawalClaimed event from a single log entry.

#### is_lido_event

```
is_lido_event(topic: str | bytes) -> bool
```

Check if a topic is a known Lido event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                         |
| ------ | ----------------------------------- |
| `bool` | True if topic is a known Lido event |

#### get_event_type

```
get_event_type(
    topic: str | bytes, log: dict[str, Any] | None = None
) -> LidoEventType
```

Get the event type for a topic.

Parameters:

| Name    | Type             | Description | Default                                                            |
| ------- | ---------------- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str            | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |
| `log`   | \`dict[str, Any] | None\`      | Optional log dict for disambiguating Transfer events               |

Returns:

| Type            | Description           |
| --------------- | --------------------- |
| `LidoEventType` | Event type or UNKNOWN |

#### extract_stake_amount

```
extract_stake_amount(receipt: dict[str, Any]) -> int | None
```

Extract stake amount from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_shares_received

```
extract_shares_received(
    receipt: dict[str, Any],
) -> int | None
```

Extract stETH/wstETH shares received from transaction receipt.

When staking ETH, user receives stETH. When wrapping stETH, user receives wstETH.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_wsteth_received

```
extract_wsteth_received(
    receipt: dict[str, Any],
) -> int | None
```

Extract wstETH amount received from a stake-with-wrap transaction.

When staking with receive_wrapped=True, the TX emits a Transfer (mint) event on the wstETH contract. This method extracts that amount so strategy authors don't need to estimate the stETH->wstETH exchange rate.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_unstake_amount

```
extract_unstake_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract unstake amount from transaction receipt.

This is the amount of stETH requested for withdrawal.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_underlying_received

```
extract_underlying_received(
    receipt: dict[str, Any],
) -> int | None
```

Extract underlying ETH received from withdrawal claim.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

### ParseResult

```
ParseResult(
    success: bool,
    stakes: list[StakeEventData] = list(),
    wraps: list[WrapEventData] = list(),
    unwraps: list[UnwrapEventData] = list(),
    withdrawal_requests: list[
        WithdrawalRequestedEventData
    ] = list(),
    withdrawal_claims: list[
        WithdrawalClaimedEventData
    ] = list(),
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
)
```

Result of parsing a receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### StakeEventData

```
StakeEventData(
    sender: str, amount: Decimal, referral: str = ""
)
```

Parsed data from Submitted event (stake operation).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### UnwrapEventData

```
UnwrapEventData(
    from_address: str,
    to_address: str,
    amount: Decimal,
    token: str = "",
)
```

Parsed data from unwrap operation (Transfer event on wstETH).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### WithdrawalClaimedEventData

```
WithdrawalClaimedEventData(
    request_id: int,
    owner: str,
    receiver: str,
    amount_of_eth: Decimal,
)
```

Parsed data from WithdrawalClaimed event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### WithdrawalRequestedEventData

```
WithdrawalRequestedEventData(
    request_id: int,
    requestor: str,
    owner: str,
    amount_of_steth: Decimal,
    amount_of_shares: Decimal,
)
```

Parsed data from WithdrawalRequested event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### WrapEventData

```
WrapEventData(
    from_address: str,
    to_address: str,
    amount: Decimal,
    token: str = "",
)
```

Parsed data from wrap operation (Transfer event on wstETH).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

# Meteora

Connector for Meteora protocol.

## almanak.framework.connectors.meteora

Meteora DLMM concentrated liquidity connector.

Provides LP operations on Meteora DLMM pools on Solana:

- Open concentrated liquidity positions (discrete bins)
- Close positions (remove liquidity + close account)

Unlike Raydium CLMM (NFT positions, continuous ticks), Meteora DLMM uses:

- Discrete price bins instead of continuous ticks
- Non-transferable Keypair-based position accounts (not NFTs)
- SpotBalanced strategy for even liquidity distribution

### MeteoraAdapter

```
MeteoraAdapter(
    config: MeteoraConfig,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Meteora DLMM integration with the Intent system.

Converts LP intents to ActionBundles containing serialized Solana VersionedTransactions built from Meteora DLMM instructions.

Example

config = MeteoraConfig(wallet_address="your-solana-pubkey") adapter = MeteoraAdapter(config)

intent = LPOpenIntent( protocol="meteora_dlmm", pool="pool-address", amount0=Decimal("1"), amount1=Decimal("150"), range_lower=Decimal("100"), range_upper=Decimal("200"), ) bundle = adapter.compile_lp_open_intent(intent)

#### compile_lp_open_intent

```
compile_lp_open_intent(
    intent: LPOpenIntent,
) -> ActionBundle
```

Compile an LPOpenIntent to an ActionBundle.

Builds Meteora DLMM initializePosition + addLiquidityByStrategy, serializes into a VersionedTransaction, and wraps in an ActionBundle.

Parameters:

| Name     | Type           | Description                  | Default    |
| -------- | -------------- | ---------------------------- | ---------- |
| `intent` | `LPOpenIntent` | The LPOpenIntent to compile. | *required* |

Returns:

| Type           | Description                                               |
| -------------- | --------------------------------------------------------- |
| `ActionBundle` | ActionBundle containing serialized Solana transaction(s). |

#### compile_lp_close_intent

```
compile_lp_close_intent(
    intent: LPCloseIntent,
) -> ActionBundle
```

Compile an LPCloseIntent to an ActionBundle.

Removes all liquidity and closes the position.

Parameters:

| Name     | Type            | Description                   | Default    |
| -------- | --------------- | ----------------------------- | ---------- |
| `intent` | `LPCloseIntent` | The LPCloseIntent to compile. | *required* |

Returns:

| Type           | Description                                               |
| -------------- | --------------------------------------------------------- |
| `ActionBundle` | ActionBundle containing serialized Solana transaction(s). |

### MeteoraConfig

```
MeteoraConfig(
    wallet_address: str,
    rpc_url: str = "",
    default_strategy_type: int = 6,
)
```

Configuration for Meteora adapter.

Attributes:

| Name                    | Type | Description                                     |
| ----------------------- | ---- | ----------------------------------------------- |
| `wallet_address`        |      | Solana wallet public key (Base58).              |
| `rpc_url`               |      | Solana RPC endpoint URL (for position queries). |
| `default_strategy_type` |      | Default strategy type (6=SpotBalanced).         |

### MeteoraAPIError

```
MeteoraAPIError(
    message: str, status_code: int = 0, endpoint: str = ""
)
```

Bases: `MeteoraError`

Error communicating with the Meteora DLMM API.

### MeteoraError

Bases: `Exception`

Base exception for Meteora operations.

### MeteoraPoolError

Bases: `MeteoraError`

Error with pool state or operations.

### MeteoraPositionError

Bases: `MeteoraError`

Error with position state or operations.

### MeteoraBin

```
MeteoraBin(
    bin_id: int,
    amount_x: int = 0,
    amount_y: int = 0,
    price: float = 0.0,
)
```

A single bin in a DLMM pool.

Attributes:

| Name       | Type    | Description                              |
| ---------- | ------- | ---------------------------------------- |
| `bin_id`   | `int`   | Bin identifier.                          |
| `amount_x` | `int`   | Amount of token X in smallest units.     |
| `amount_y` | `int`   | Amount of token Y in smallest units.     |
| `price`    | `float` | Price at this bin (token Y per token X). |

### MeteoraPool

```
MeteoraPool(
    address: str,
    mint_x: str,
    mint_y: str,
    symbol_x: str = "",
    symbol_y: str = "",
    decimals_x: int = 9,
    decimals_y: int = 6,
    bin_step: int = 10,
    active_bin_id: int = 0,
    current_price: float = 0.0,
    tvl: float = 0.0,
    reserve_x: str = "0",
    reserve_y: str = "0",
    fee_bps: int = 0,
    vault_x: str = "",
    vault_y: str = "",
    oracle_address: str = "",
    raw_response: dict[str, Any] = dict(),
)
```

Meteora DLMM pool information.

Can be constructed from the DLMM API response.

Attributes:

| Name             | Type             | Description                                   |
| ---------------- | ---------------- | --------------------------------------------- |
| `address`        | `str`            | Pool (lb_pair) account address (Base58).      |
| `mint_x`         | `str`            | Token X mint address.                         |
| `mint_y`         | `str`            | Token Y mint address.                         |
| `symbol_x`       | `str`            | Token X symbol (e.g., "SOL").                 |
| `symbol_y`       | `str`            | Token Y symbol (e.g., "USDC").                |
| `decimals_x`     | `int`            | Token X decimals.                             |
| `decimals_y`     | `int`            | Token Y decimals.                             |
| `bin_step`       | `int`            | Bin step in basis points.                     |
| `active_bin_id`  | `int`            | Currently active bin ID.                      |
| `current_price`  | `float`          | Current price of token X in terms of token Y. |
| `tvl`            | `float`          | Total value locked in USD.                    |
| `reserve_x`      | `str`            | Token X reserve in pool.                      |
| `reserve_y`      | `str`            | Token Y reserve in pool.                      |
| `fee_bps`        | `int`            | Base fee in basis points.                     |
| `vault_x`        | `str`            | Token X vault address.                        |
| `vault_y`        | `str`            | Token Y vault address.                        |
| `oracle_address` | `str`            | Oracle PDA address.                           |
| `raw_response`   | `dict[str, Any]` | Full API response dict.                       |

#### from_api_response

```
from_api_response(data: dict[str, Any]) -> MeteoraPool
```

Create from Meteora DLMM API response.

Works with both /pair/{address} and /pair/all_with_pagination items.

### MeteoraPosition

```
MeteoraPosition(
    position_address: str,
    lb_pair: str,
    owner: str = "",
    lower_bin_id: int = 0,
    upper_bin_id: int = 0,
    bins: list[MeteoraBin] = list(),
    total_x: int = 0,
    total_y: int = 0,
)
```

Meteora DLMM position state (on-chain).

Unlike Raydium CLMM (NFT-based), Meteora positions are non-transferable program accounts identified by their address.

Attributes:

| Name               | Type               | Description                         |
| ------------------ | ------------------ | ----------------------------------- |
| `position_address` | `str`              | Position account address (Base58).  |
| `lb_pair`          | `str`              | Pool address.                       |
| `owner`            | `str`              | Owner wallet address.               |
| `lower_bin_id`     | `int`              | Lower bin ID of the position range. |
| `upper_bin_id`     | `int`              | Upper bin ID of the position range. |
| `bins`             | `list[MeteoraBin]` | List of bins with amounts.          |
| `total_x`          | `int`              | Total token X in position.          |
| `total_y`          | `int`              | Total token Y in position.          |

### MeteoraReceiptParser

```
MeteoraReceiptParser(**kwargs: Any)
```

Parser for Meteora DLMM transaction receipts.

Extracts position IDs, liquidity amounts, and token balances from Solana transaction receipts.

Supports the extraction methods required by ResultEnricher:

- extract_position_id(receipt) -> str | None
- extract_liquidity(receipt) -> dict | None
- extract_lp_close_data(receipt) -> dict | None

Extraction approach:

1. Parse log messages for Meteora program events
1. Use preTokenBalances/postTokenBalances for actual amounts
1. Look for position address in ActionBundle metadata

Initialize MeteoraReceiptParser.

Parameters:

| Name       | Type  | Description                                            | Default |
| ---------- | ----- | ------------------------------------------------------ | ------- |
| `**kwargs` | `Any` | Keyword arguments from receipt_registry (e.g., chain). | `{}`    |

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> dict[str, Any]
```

Parse a receipt for ReceiptParser protocol compatibility.

Parameters:

| Name      | Type             | Description                      | Default    |
| --------- | ---------------- | -------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Solana transaction receipt dict. | *required* |

Returns:

| Type             | Description               |
| ---------------- | ------------------------- |
| `dict[str, Any]` | Dict with parsed LP data. |

#### extract_position_id

```
extract_position_id(receipt: dict[str, Any]) -> str | None
```

Extract the position address from a Meteora LP open receipt.

Meteora positions are Keypair-based accounts (not NFTs). The position address is typically in the ActionBundle metadata, but we also look in the transaction's account keys for new writable accounts.

Parameters:

| Name      | Type             | Description                      | Default    |
| --------- | ---------------- | -------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Solana transaction receipt dict. | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

#### extract_liquidity

```
extract_liquidity(
    receipt: dict[str, Any],
) -> dict[str, Any] | None
```

Extract liquidity data from an LP open/increase receipt.

Uses balance deltas to determine actual deposited amounts.

Parameters:

| Name      | Type             | Description                      | Default    |
| --------- | ---------------- | -------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Solana transaction receipt dict. | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`dict[str, Any] | None\`      |

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> dict[str, Any] | None
```

Extract LP close data from a receipt.

Uses balance deltas to determine received token amounts.

Parameters:

| Name      | Type             | Description                      | Default    |
| --------- | ---------------- | -------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Solana transaction receipt dict. | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`dict[str, Any] | None\`      |

### MeteoraSDK

```
MeteoraSDK(
    wallet_address: str,
    base_url: str = METEORA_API_BASE_URL,
    timeout: int = 30,
)
```

SDK for building Meteora DLMM instructions.

Provides methods to:

- Fetch pool data from the Meteora DLMM API
- Build Solana instructions for LP operations
- Compute PDAs for positions, bin arrays, etc.

Example

sdk = MeteoraSDK(wallet_address="your-pubkey") pool = sdk.get_pool("pool-address") ixs, position_kp = sdk.build_open_position_transaction( pool=pool, lower_bin_id=8388600, upper_bin_id=8388620, amount_x=1_000_000, amount_y=500_000_000, )

#### get_pool

```
get_pool(pool_address: str) -> MeteoraPool
```

Fetch pool information from the Meteora DLMM API.

Parameters:

| Name           | Type  | Description                              | Default    |
| -------------- | ----- | ---------------------------------------- | ---------- |
| `pool_address` | `str` | Pool (lb_pair) account address (Base58). | *required* |

Returns:

| Type          | Description                         |
| ------------- | ----------------------------------- |
| `MeteoraPool` | MeteoraPool with current pool data. |

Raises:

| Type               | Description           |
| ------------------ | --------------------- |
| `MeteoraPoolError` | If pool not found.    |
| `MeteoraAPIError`  | If API request fails. |

#### find_pool

```
find_pool(token_a: str, token_b: str) -> MeteoraPool | None
```

Find a DLMM pool by token pair.

Searches the Meteora API for pools matching the token pair and returns the one with highest TVL.

Parameters:

| Name      | Type  | Description           | Default    |
| --------- | ----- | --------------------- | ---------- |
| `token_a` | `str` | Token A mint address. | *required* |
| `token_b` | `str` | Token B mint address. | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`MeteoraPool | None\`      |

#### get_active_bin

```
get_active_bin(pool_address: str) -> dict[str, Any]
```

Get the active bin for a pool.

Parameters:

| Name           | Type  | Description   | Default    |
| -------------- | ----- | ------------- | ---------- |
| `pool_address` | `str` | Pool address. | *required* |

Returns:

| Type             | Description                           |
| ---------------- | ------------------------------------- |
| `dict[str, Any]` | Dict with bin_id, price, and amounts. |

#### get_position_pda

```
get_position_pda(
    lb_pair: Pubkey,
    base: Pubkey,
    lower_bin_id: int,
    width: int,
) -> Pubkey
```

Derive the position PDA.

seeds: [POSITION_SEED, lb_pair, base, lower_bin_id (i32 LE), width (i32 LE)]

#### get_event_authority_pda

```
get_event_authority_pda() -> Pubkey
```

Derive the event authority PDA.

#### get_oracle_pda

```
get_oracle_pda(lb_pair: Pubkey) -> Pubkey
```

Derive the oracle PDA for a pool.

#### get_position_state

```
get_position_state(
    position_address: str, rpc_url: str
) -> MeteoraPosition
```

Query on-chain position state for a Meteora DLMM position.

Meteora PositionV2 layout (Anchor, 8-byte discriminator): [0:8] discriminator [8:40] lb_pair (Pubkey) [40:72] owner (Pubkey) [72:76] lower_bin_id (i32 LE) [76:80] upper_bin_id (i32 LE)

Parameters:

| Name               | Type  | Description                        | Default    |
| ------------------ | ----- | ---------------------------------- | ---------- |
| `position_address` | `str` | Position account address (Base58). | *required* |
| `rpc_url`          | `str` | Solana RPC endpoint URL.           | *required* |

Returns:

| Type              | Description                          |
| ----------------- | ------------------------------------ |
| `MeteoraPosition` | MeteoraPosition with on-chain state. |

Raises:

| Type                   | Description                                    |
| ---------------------- | ---------------------------------------------- |
| `MeteoraPositionError` | If position account not found or data invalid. |

#### build_initialize_position_ix

```
build_initialize_position_ix(
    lb_pair: Pubkey,
    position_kp: Keypair,
    lower_bin_id: int,
    width: int,
) -> Instruction
```

Build initializePosition instruction.

Parameters:

| Name           | Type      | Description                           | Default    |
| -------------- | --------- | ------------------------------------- | ---------- |
| `lb_pair`      | `Pubkey`  | Pool address.                         | *required* |
| `position_kp`  | `Keypair` | New keypair for the position account. | *required* |
| `lower_bin_id` | `int`     | Lower bin ID.                         | *required* |
| `width`        | `int`     | Number of bins in the position.       | *required* |

Returns:

| Type          | Description         |
| ------------- | ------------------- |
| `Instruction` | Solana instruction. |

#### build_add_liquidity_by_strategy_ix

```
build_add_liquidity_by_strategy_ix(
    pool: MeteoraPool,
    position: Pubkey,
    lower_bin_id: int,
    upper_bin_id: int,
    amount_x: int,
    amount_y: int,
    active_id: int,
    max_active_bin_slippage: int = 5,
    strategy_type: int = STRATEGY_TYPE_SPOT_BALANCED,
) -> Instruction
```

Build addLiquidityByStrategy instruction.

LiquidityParameterByStrategy layout

amount_x: u64 amount_y: u64 active_id: i32 max_active_bin_slippage: i32 strategy_parameters: min_bin_id: i32 max_bin_id: i32 strategy_type: u8 parameters: [u8; 64] (zeroed for SpotBalanced)

Parameters:

| Name                      | Type          | Description                              | Default                       |
| ------------------------- | ------------- | ---------------------------------------- | ----------------------------- |
| `pool`                    | `MeteoraPool` | Pool information.                        | *required*                    |
| `position`                | `Pubkey`      | Position account address.                | *required*                    |
| `lower_bin_id`            | `int`         | Min bin ID for strategy parameters.      | *required*                    |
| `upper_bin_id`            | `int`         | Max bin ID for strategy parameters.      | *required*                    |
| `amount_x`                | `int`         | Amount of token X in smallest units.     | *required*                    |
| `amount_y`                | `int`         | Amount of token Y in smallest units.     | *required*                    |
| `active_id`               | `int`         | Current active bin ID.                   | *required*                    |
| `max_active_bin_slippage` | `int`         | Max slippage in bins.                    | `5`                           |
| `strategy_type`           | `int`         | Strategy type (default: SpotBalanced=6). | `STRATEGY_TYPE_SPOT_BALANCED` |

Returns:

| Type          | Description         |
| ------------- | ------------------- |
| `Instruction` | Solana instruction. |

#### build_remove_liquidity_by_range_ix

```
build_remove_liquidity_by_range_ix(
    pool: MeteoraPool,
    position: Pubkey,
    from_bin_id: int,
    to_bin_id: int,
    bps_to_remove: int = 10000,
) -> Instruction
```

Build removeLiquidityByRange instruction.

Parameters:

| Name            | Type          | Description                                         | Default    |
| --------------- | ------------- | --------------------------------------------------- | ---------- |
| `pool`          | `MeteoraPool` | Pool information.                                   | *required* |
| `position`      | `Pubkey`      | Position account address.                           | *required* |
| `from_bin_id`   | `int`         | Starting bin ID.                                    | *required* |
| `to_bin_id`     | `int`         | Ending bin ID (inclusive).                          | *required* |
| `bps_to_remove` | `int`         | Basis points of liquidity to remove (10000 = 100%). | `10000`    |

Returns:

| Type          | Description         |
| ------------- | ------------------- |
| `Instruction` | Solana instruction. |

#### build_close_position_ix

```
build_close_position_ix(
    lb_pair: Pubkey, position: Pubkey
) -> Instruction
```

Build closePosition instruction.

Parameters:

| Name       | Type     | Description               | Default    |
| ---------- | -------- | ------------------------- | ---------- |
| `lb_pair`  | `Pubkey` | Pool address.             | *required* |
| `position` | `Pubkey` | Position account address. | *required* |

Returns:

| Type          | Description         |
| ------------- | ------------------- |
| `Instruction` | Solana instruction. |

#### build_open_position_transaction

```
build_open_position_transaction(
    pool: MeteoraPool,
    lower_bin_id: int,
    upper_bin_id: int,
    amount_x: int,
    amount_y: int,
    slippage_bps: int = 100,
    strategy_type: int = STRATEGY_TYPE_SPOT_BALANCED,
) -> tuple[list[Instruction], Keypair, dict[str, Any]]
```

Build a complete open position transaction.

Creates initializePosition + addLiquidityByStrategy instructions.

Parameters:

| Name            | Type          | Description                                  | Default                       |
| --------------- | ------------- | -------------------------------------------- | ----------------------------- |
| `pool`          | `MeteoraPool` | Pool information.                            | *required*                    |
| `lower_bin_id`  | `int`         | Lower bin ID.                                | *required*                    |
| `upper_bin_id`  | `int`         | Upper bin ID.                                | *required*                    |
| `amount_x`      | `int`         | Amount of token X in smallest units.         | *required*                    |
| `amount_y`      | `int`         | Amount of token Y in smallest units.         | *required*                    |
| `slippage_bps`  | `int`         | Slippage in basis points (default 100 = 1%). | `100`                         |
| `strategy_type` | `int`         | Strategy type (default: SpotBalanced).       | `STRATEGY_TYPE_SPOT_BALANCED` |

Returns:

| Type                                                | Description                                          |
| --------------------------------------------------- | ---------------------------------------------------- |
| `tuple[list[Instruction], Keypair, dict[str, Any]]` | Tuple of (instructions, position_keypair, metadata). |

#### build_close_position_transaction

```
build_close_position_transaction(
    pool: MeteoraPool, position: MeteoraPosition
) -> tuple[list[Instruction], dict[str, Any]]
```

Build instructions to fully close a position.

removeLiquidityByRange (100%) + closePosition.

Parameters:

| Name       | Type              | Description        | Default    |
| ---------- | ----------------- | ------------------ | ---------- |
| `pool`     | `MeteoraPool`     | Pool information.  | *required* |
| `position` | `MeteoraPosition` | Position to close. | *required* |

Returns:

| Type                                       | Description                        |
| ------------------------------------------ | ---------------------------------- |
| `tuple[list[Instruction], dict[str, Any]]` | Tuple of (instructions, metadata). |

# Morpho Blue

Connector for Morpho Blue lending protocol.

## almanak.framework.connectors.morpho_blue

Morpho Blue Connector.

This module provides adapters and utilities for interacting with Morpho Blue, a permissionless lending protocol that allows creating isolated lending markets.

Morpho Blue Features:

- Isolated lending markets with customizable parameters
- Supply assets to earn yield (lending)
- Supply collateral for borrowing
- Borrow against collateral
- Flash loans
- No intermediary tokens (no aTokens)

Supported Chains:

- Ethereum
- Base

Example

from almanak.framework.connectors.morpho_blue import ( MorphoBlueAdapter, MorphoBlueConfig, MorphoBlueReceiptParser, MorphoBlueSDK, create_adapter_with_prices, ) from decimal import Decimal

### Initialize adapter with prices

config = MorphoBlueConfig( chain="ethereum", wallet_address="0x...", ) prices = {"wstETH": Decimal("2500"), "USDC": Decimal("1")} adapter = create_adapter_with_prices(config, prices)

### Get market info

markets = adapter.get_markets() print(f"Available markets: {len(markets)}")

### Build a supply collateral transaction

result = adapter.supply_collateral( market_id="0xb323495f7e4148be5643a4ea4a8221eef163e4bccfdedc2a6f4696baacbc86cc", amount=Decimal("1.0"), )

### Parse transaction receipts

parser = MorphoBlueReceiptParser() events = parser.parse_receipt(receipt)

### Use SDK for on-chain reads

sdk = MorphoBlueSDK(chain="ethereum") position = sdk.get_position(market_id, user_address) print(f"Supply shares: {position.supply_shares}")

### MorphoBlueAdapter

```
MorphoBlueAdapter(
    config: MorphoBlueConfig,
    price_oracle: PriceOracle | None = None,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Morpho Blue lending protocol.

This adapter provides methods for interacting with Morpho Blue:

- Supply/withdraw assets (lending)
- Supply/withdraw collateral
- Borrow/repay assets
- Health factor calculations
- On-chain position and market state reading (via SDK)

Example

#### Production usage with real prices

config = MorphoBlueConfig( chain="ethereum", wallet_address="0x...", price_provider={"USDC": Decimal("1"), "wstETH": Decimal("3500")}, ) adapter = MorphoBlueAdapter(config)

#### Read on-chain position

position = adapter.get_position_on_chain(market_id) print(f"Collateral: {position.collateral}")

#### Supply collateral

result = adapter.supply_collateral( market_id="0x...", amount=Decimal("1.0"), )

#### For testing only (with placeholder prices)

config = MorphoBlueConfig( chain="ethereum", wallet_address="0x...", allow_placeholder_prices=True, enable_sdk=False, # Disable SDK for unit tests ) adapter = MorphoBlueAdapter(config)

Initialize the adapter.

Parameters:

| Name             | Type               | Description           | Default                                                                                                 |
| ---------------- | ------------------ | --------------------- | ------------------------------------------------------------------------------------------------------- |
| `config`         | `MorphoBlueConfig` | Adapter configuration | *required*                                                                                              |
| `price_oracle`   | \`PriceOracle      | None\`                | Optional price oracle callback. If not provided, uses config.price_provider dict or placeholder prices. |
| `token_resolver` | \`TokenResolver    | None\`                | Optional TokenResolver instance. If None, uses singleton.                                               |

#### sdk

```
sdk: Any
```

Get the SDK instance (lazy initialization).

Returns:

| Type  | Description            |
| ----- | ---------------------- |
| `Any` | MorphoBlueSDK instance |

Raises:

| Type           | Description        |
| -------------- | ------------------ |
| `RuntimeError` | If SDK is disabled |

#### get_position_on_chain

```
get_position_on_chain(
    market_id: str, user: str | None = None
) -> MorphoBluePosition
```

Get user position from on-chain data.

Requires SDK to be enabled.

Parameters:

| Name        | Type  | Description       | Default                                   |
| ----------- | ----- | ----------------- | ----------------------------------------- |
| `market_id` | `str` | Market identifier | *required*                                |
| `user`      | \`str | None\`            | User address (defaults to wallet_address) |

Returns:

| Type                 | Description                                                 |
| -------------------- | ----------------------------------------------------------- |
| `MorphoBluePosition` | MorphoBluePosition with supply, borrow, and collateral data |

#### get_market_state_on_chain

```
get_market_state_on_chain(
    market_id: str,
) -> MorphoBlueMarketState
```

Get market state from on-chain data.

Requires SDK to be enabled.

Parameters:

| Name        | Type  | Description       | Default    |
| ----------- | ----- | ----------------- | ---------- |
| `market_id` | `str` | Market identifier | *required* |

Returns:

| Type                    | Description                                      |
| ----------------------- | ------------------------------------------------ |
| `MorphoBlueMarketState` | MorphoBlueMarketState with current market totals |

#### get_market_params_on_chain

```
get_market_params_on_chain(
    market_id: str,
) -> MorphoBlueMarketParams
```

Get market parameters from on-chain data.

Requires SDK to be enabled.

Parameters:

| Name        | Type  | Description       | Default    |
| ----------- | ----- | ----------------- | ---------- |
| `market_id` | `str` | Market identifier | *required* |

Returns:

| Type                     | Description                                                                 |
| ------------------------ | --------------------------------------------------------------------------- |
| `MorphoBlueMarketParams` | MorphoBlueMarketParams with loan token, collateral token, oracle, IRM, LLTV |

#### discover_markets_on_chain

```
discover_markets_on_chain() -> list[str]
```

Discover all markets from on-chain events.

Requires SDK to be enabled.

Returns:

| Type        | Description                              |
| ----------- | ---------------------------------------- |
| `list[str]` | List of market IDs (bytes32 hex strings) |

#### get_supply_assets_on_chain

```
get_supply_assets_on_chain(
    market_id: str, user: str | None = None
) -> Decimal
```

Get user's supply amount in assets (not shares) from on-chain.

Parameters:

| Name        | Type  | Description       | Default                                   |
| ----------- | ----- | ----------------- | ----------------------------------------- |
| `market_id` | `str` | Market identifier | *required*                                |
| `user`      | \`str | None\`            | User address (defaults to wallet_address) |

Returns:

| Type      | Description                  |
| --------- | ---------------------------- |
| `Decimal` | Supply amount in asset units |

#### get_borrow_assets_on_chain

```
get_borrow_assets_on_chain(
    market_id: str, user: str | None = None
) -> Decimal
```

Get user's borrow amount in assets (not shares) from on-chain.

Parameters:

| Name        | Type  | Description       | Default                                   |
| ----------- | ----- | ----------------- | ----------------------------------------- |
| `market_id` | `str` | Market identifier | *required*                                |
| `user`      | \`str | None\`            | User address (defaults to wallet_address) |

Returns:

| Type      | Description                  |
| --------- | ---------------------------- |
| `Decimal` | Borrow amount in asset units |

#### supply

```
supply(
    market_id: str,
    amount: Decimal,
    on_behalf_of: str | None = None,
    shares_mode: bool = False,
) -> TransactionResult
```

Build a supply transaction for lending assets.

Supplies loan tokens to the market to earn interest.

Parameters:

| Name           | Type      | Description                                                     | Default                                        |
| -------------- | --------- | --------------------------------------------------------------- | ---------------------------------------------- |
| `market_id`    | `str`     | Market identifier                                               | *required*                                     |
| `amount`       | `Decimal` | Amount to supply (in token units) or shares if shares_mode=True | *required*                                     |
| `on_behalf_of` | \`str     | None\`                                                          | Address to credit (defaults to wallet_address) |
| `shares_mode`  | `bool`    | If True, amount represents shares instead of assets             | `False`                                        |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### withdraw

```
withdraw(
    market_id: str,
    amount: Decimal,
    receiver: str | None = None,
    on_behalf_of: str | None = None,
    shares_mode: bool = False,
    withdraw_all: bool = False,
) -> TransactionResult
```

Build a withdraw transaction for withdrawing supplied assets.

Withdraws loan tokens from the market.

Parameters:

| Name           | Type      | Description                                                       | Default                                                |
| -------------- | --------- | ----------------------------------------------------------------- | ------------------------------------------------------ |
| `market_id`    | `str`     | Market identifier                                                 | *required*                                             |
| `amount`       | `Decimal` | Amount to withdraw (in token units) or shares if shares_mode=True | *required*                                             |
| `receiver`     | \`str     | None\`                                                            | Address to receive tokens (defaults to wallet_address) |
| `on_behalf_of` | \`str     | None\`                                                            | Address to debit (defaults to wallet_address)          |
| `shares_mode`  | `bool`    | If True, amount represents shares instead of assets               | `False`                                                |
| `withdraw_all` | `bool`    | If True, withdraws all supplied assets                            | `False`                                                |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### supply_collateral

```
supply_collateral(
    market_id: str,
    amount: Decimal,
    on_behalf_of: str | None = None,
) -> TransactionResult
```

Build a supply collateral transaction.

Supplies collateral tokens to the market for borrowing.

Parameters:

| Name           | Type      | Description                                     | Default                                        |
| -------------- | --------- | ----------------------------------------------- | ---------------------------------------------- |
| `market_id`    | `str`     | Market identifier                               | *required*                                     |
| `amount`       | `Decimal` | Amount of collateral to supply (in token units) | *required*                                     |
| `on_behalf_of` | \`str     | None\`                                          | Address to credit (defaults to wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### withdraw_collateral

```
withdraw_collateral(
    market_id: str,
    amount: Decimal,
    receiver: str | None = None,
    on_behalf_of: str | None = None,
    withdraw_all: bool = False,
) -> TransactionResult
```

Build a withdraw collateral transaction.

Withdraws collateral tokens from the market.

Parameters:

| Name           | Type      | Description                                       | Default                                                |
| -------------- | --------- | ------------------------------------------------- | ------------------------------------------------------ |
| `market_id`    | `str`     | Market identifier                                 | *required*                                             |
| `amount`       | `Decimal` | Amount of collateral to withdraw (in token units) | *required*                                             |
| `receiver`     | \`str     | None\`                                            | Address to receive tokens (defaults to wallet_address) |
| `on_behalf_of` | \`str     | None\`                                            | Address to debit (defaults to wallet_address)          |
| `withdraw_all` | `bool`    | If True, withdraws all collateral                 | `False`                                                |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### borrow

```
borrow(
    market_id: str,
    amount: Decimal,
    receiver: str | None = None,
    on_behalf_of: str | None = None,
    shares_mode: bool = False,
) -> TransactionResult
```

Build a borrow transaction.

Borrows loan tokens from the market against collateral.

Parameters:

| Name           | Type      | Description                                                     | Default                                                |
| -------------- | --------- | --------------------------------------------------------------- | ------------------------------------------------------ |
| `market_id`    | `str`     | Market identifier                                               | *required*                                             |
| `amount`       | `Decimal` | Amount to borrow (in token units) or shares if shares_mode=True | *required*                                             |
| `receiver`     | \`str     | None\`                                                          | Address to receive tokens (defaults to wallet_address) |
| `on_behalf_of` | \`str     | None\`                                                          | Address to debit (defaults to wallet_address)          |
| `shares_mode`  | `bool`    | If True, amount represents shares instead of assets             | `False`                                                |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### repay

```
repay(
    market_id: str,
    amount: Decimal,
    on_behalf_of: str | None = None,
    shares_mode: bool = False,
    repay_all: bool = False,
) -> TransactionResult
```

Build a repay transaction.

Repays borrowed loan tokens to the market.

Parameters:

| Name           | Type      | Description                                                    | Default                                        |
| -------------- | --------- | -------------------------------------------------------------- | ---------------------------------------------- |
| `market_id`    | `str`     | Market identifier                                              | *required*                                     |
| `amount`       | `Decimal` | Amount to repay (in token units) or shares if shares_mode=True | *required*                                     |
| `on_behalf_of` | \`str     | None\`                                                         | Address with debt (defaults to wallet_address) |
| `shares_mode`  | `bool`    | If True, amount represents shares instead of assets            | `False`                                        |
| `repay_all`    | `bool`    | If True, repays full debt                                      | `False`                                        |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### flash_loan

```
flash_loan(
    token: str, amount: Decimal, callback_data: bytes = b""
) -> TransactionResult
```

Build a flash loan transaction.

Borrows assets in a flash loan that must be repaid within the same transaction.

Note: Flash loans require a callback contract to receive and repay the loan. The callback_data is passed to the flash loan receiver.

Parameters:

| Name            | Type      | Description                                 | Default    |
| --------------- | --------- | ------------------------------------------- | ---------- |
| `token`         | `str`     | Token symbol or address to borrow           | *required* |
| `amount`        | `Decimal` | Amount to borrow                            | *required* |
| `callback_data` | `bytes`   | Data passed to flash loan receiver callback | `b''`      |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### liquidate

```
liquidate(
    market_id: str,
    borrower: str,
    seized_assets: Decimal,
    repaid_shares: Decimal | None = None,
    callback_data: bytes = b"",
) -> TransactionResult
```

Build a liquidation transaction.

Liquidates an unhealthy position by repaying debt and seizing collateral.

In Morpho Blue, liquidators specify the amount of collateral to seize, and the protocol calculates how much debt to repay based on the oracle price and liquidation incentive.

Parameters:

| Name            | Type      | Description                                                  | Default                                                            |
| --------------- | --------- | ------------------------------------------------------------ | ------------------------------------------------------------------ |
| `market_id`     | `str`     | Market identifier                                            | *required*                                                         |
| `borrower`      | `str`     | Address of the borrower to liquidate                         | *required*                                                         |
| `seized_assets` | `Decimal` | Amount of collateral to seize (in collateral token units)    | *required*                                                         |
| `repaid_shares` | \`Decimal | None\`                                                       | Optional amount of debt shares to repay (if 0, uses seized_assets) |
| `callback_data` | `bytes`   | Data passed to liquidation callback (for flash liquidations) | `b''`                                                              |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### set_authorization

```
set_authorization(
    authorized: str, is_authorized: bool
) -> TransactionResult
```

Build a set authorization transaction.

Authorizes another address to manage positions on behalf of the caller.

Parameters:

| Name            | Type   | Description                              | Default    |
| --------------- | ------ | ---------------------------------------- | ---------- |
| `authorized`    | `str`  | Address to authorize/deauthorize         | *required* |
| `is_authorized` | `bool` | Whether to grant or revoke authorization | *required* |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### get_market_info

```
get_market_info(market_id: str) -> dict[str, Any] | None
```

Get information about a market.

Parameters:

| Name        | Type  | Description       | Default    |
| ----------- | ----- | ----------------- | ---------- |
| `market_id` | `str` | Market identifier | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`dict[str, Any] | None\`      |

#### get_markets

```
get_markets() -> dict[str, dict[str, Any]]
```

Get all known markets for the current chain.

Returns:

| Type                        | Description                                 |
| --------------------------- | ------------------------------------------- |
| `dict[str, dict[str, Any]]` | Dictionary mapping market_id to market info |

#### get_market_params

```
get_market_params(
    market_id: str,
) -> MorphoBlueMarketParams | None
```

Get market parameters for a market.

Parameters:

| Name        | Type  | Description       | Default    |
| ----------- | ----- | ----------------- | ---------- |
| `market_id` | `str` | Market identifier | *required* |

Returns:

| Type                     | Description |
| ------------------------ | ----------- |
| \`MorphoBlueMarketParams | None\`      |

#### calculate_health_factor

```
calculate_health_factor(
    collateral_amount: Decimal,
    collateral_price_usd: Decimal,
    debt_amount: Decimal,
    debt_price_usd: Decimal,
    lltv: Decimal,
) -> MorphoBlueHealthFactor
```

Calculate health factor for a position.

Health Factor = (Collateral Value * LLTV) / Debt Value

Parameters:

| Name                   | Type      | Description                 | Default    |
| ---------------------- | --------- | --------------------------- | ---------- |
| `collateral_amount`    | `Decimal` | Amount of collateral        | *required* |
| `collateral_price_usd` | `Decimal` | Price of collateral in USD  | *required* |
| `debt_amount`          | `Decimal` | Amount of debt              | *required* |
| `debt_price_usd`       | `Decimal` | Price of debt token in USD  | *required* |
| `lltv`                 | `Decimal` | Liquidation LTV (0-1 scale) | *required* |

Returns:

| Type                     | Description                                   |
| ------------------------ | --------------------------------------------- |
| `MorphoBlueHealthFactor` | MorphoBlueHealthFactor with calculated values |

#### build_approve_transaction

```
build_approve_transaction(
    token: str,
    amount: Decimal | None = None,
    spender: str | None = None,
) -> TransactionResult
```

Build an ERC20 approve transaction.

Parameters:

| Name      | Type      | Description                        | Default                                               |
| --------- | --------- | ---------------------------------- | ----------------------------------------------------- |
| `token`   | `str`     | Token symbol or address to approve | *required*                                            |
| `amount`  | \`Decimal | None\`                             | Amount to approve (None for max)                      |
| `spender` | \`str     | None\`                             | Address to approve (defaults to Morpho Blue contract) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

### MorphoBlueConfig

```
MorphoBlueConfig(
    chain: str,
    wallet_address: str,
    default_slippage_bps: int = 50,
    rpc_url: str | None = None,
    price_provider: dict[str, Decimal] | None = None,
    allow_placeholder_prices: bool = False,
    enable_sdk: bool = True,
)
```

Configuration for Morpho Blue adapter.

Attributes:

| Name                       | Type                 | Description                                                                     |
| -------------------------- | -------------------- | ------------------------------------------------------------------------------- |
| `chain`                    | `str`                | Blockchain network (ethereum, base)                                             |
| `wallet_address`           | `str`                | User wallet address                                                             |
| `default_slippage_bps`     | `int`                | Default slippage tolerance in basis points                                      |
| `rpc_url`                  | \`str                | None\`                                                                          |
| `price_provider`           | \`dict[str, Decimal] | None\`                                                                          |
| `allow_placeholder_prices` | `bool`               | If True, allows using placeholder prices for testing. DO NOT use in production. |
| `enable_sdk`               | `bool`               | If True, initializes the SDK for on-chain reads. Requires RPC access.           |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

### MorphoBlueHealthFactor

```
MorphoBlueHealthFactor(
    collateral_value_usd: Decimal,
    debt_value_usd: Decimal,
    lltv: Decimal,
    health_factor: Decimal,
    max_borrow_usd: Decimal = Decimal("0"),
)
```

Health factor calculation for a Morpho Blue position.

Attributes:

| Name                   | Type      | Description                   |
| ---------------------- | --------- | ----------------------------- |
| `collateral_value_usd` | `Decimal` | Value of collateral in USD    |
| `debt_value_usd`       | `Decimal` | Value of debt in USD          |
| `lltv`                 | `Decimal` | Liquidation LTV of the market |
| `health_factor`        | `Decimal` | Calculated health factor      |
| `max_borrow_usd`       | `Decimal` | Maximum borrowable amount     |

#### is_healthy

```
is_healthy: bool
```

Check if position is healthy (HF >= 1).

#### liquidation_threshold_usd

```
liquidation_threshold_usd: Decimal
```

Get the USD debt level at which liquidation would occur.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### MorphoBlueInterestRateMode

Bases: `IntEnum`

Morpho Blue uses a single interest rate model per market.

Unlike Aave, Morpho Blue markets have a single adaptive interest rate determined by the IRM (Interest Rate Model) contract.

### MorphoBlueMarketParams

```
MorphoBlueMarketParams(
    loan_token: str,
    collateral_token: str,
    oracle: str,
    irm: str,
    lltv: int,
)
```

Market parameters for Morpho Blue.

In Morpho Blue, each market is uniquely identified by these 5 parameters. The market_id is derived as: keccak256(abi.encode(loan_token, collateral_token, oracle, irm, lltv))

Attributes:

| Name               | Type  | Description                                                     |
| ------------------ | ----- | --------------------------------------------------------------- |
| `loan_token`       | `str` | Address of the asset being borrowed                             |
| `collateral_token` | `str` | Address of the collateral asset                                 |
| `oracle`           | `str` | Address of the price oracle                                     |
| `irm`              | `str` | Address of the interest rate model                              |
| `lltv`             | `int` | Liquidation LTV (in 1e18 scale, e.g., 860000000000000000 = 86%) |

#### to_tuple

```
to_tuple() -> tuple[str, str, str, str, int]
```

Convert to tuple for ABI encoding.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### MorphoBlueMarketState

```
MorphoBlueMarketState(
    market_id: str,
    total_supply_assets: Decimal = Decimal("0"),
    total_supply_shares: Decimal = Decimal("0"),
    total_borrow_assets: Decimal = Decimal("0"),
    total_borrow_shares: Decimal = Decimal("0"),
    last_update: int = 0,
    fee: Decimal = Decimal("0"),
)
```

State of a Morpho Blue market.

Attributes:

| Name                  | Type      | Description                           |
| --------------------- | --------- | ------------------------------------- |
| `market_id`           | `str`     | Unique identifier for the market      |
| `total_supply_assets` | `Decimal` | Total assets supplied to the market   |
| `total_supply_shares` | `Decimal` | Total supply shares                   |
| `total_borrow_assets` | `Decimal` | Total assets borrowed from the market |
| `total_borrow_shares` | `Decimal` | Total borrow shares                   |
| `last_update`         | `int`     | Timestamp of last interest accrual    |
| `fee`                 | `Decimal` | Protocol fee (in 1e18 scale)          |

#### utilization

```
utilization: Decimal
```

Calculate market utilization rate.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### MorphoBluePosition

```
MorphoBluePosition(
    market_id: str,
    supply_shares: Decimal = Decimal("0"),
    borrow_shares: Decimal = Decimal("0"),
    collateral: Decimal = Decimal("0"),
)
```

User position in a Morpho Blue market.

Attributes:

| Name            | Type      | Description              |
| --------------- | --------- | ------------------------ |
| `market_id`     | `str`     | Market identifier        |
| `supply_shares` | `Decimal` | User's supply shares     |
| `borrow_shares` | `Decimal` | User's borrow shares     |
| `collateral`    | `Decimal` | User's collateral amount |

#### has_supply

```
has_supply: bool
```

Check if user has supply in this market.

#### has_borrow

```
has_borrow: bool
```

Check if user has borrow in this market.

#### has_collateral

```
has_collateral: bool
```

Check if user has collateral in this market.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### TransactionResult

```
TransactionResult(
    success: bool,
    tx_data: dict[str, Any] | None = None,
    gas_estimate: int = 0,
    description: str = "",
    error: str | None = None,
)
```

Result of a transaction build operation.

Attributes:

| Name           | Type             | Description                 |
| -------------- | ---------------- | --------------------------- |
| `success`      | `bool`           | Whether operation succeeded |
| `tx_data`      | \`dict[str, Any] | None\`                      |
| `gas_estimate` | `int`            | Estimated gas               |
| `description`  | `str`            | Human-readable description  |
| `error`        | \`str            | None\`                      |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### AccrueInterestEventData

```
AccrueInterestEventData(
    market_id: str,
    prev_borrow_rate: Decimal,
    interest: Decimal,
    fee_shares: Decimal,
)
```

Parsed data from AccrueInterest event.

Attributes:

| Name               | Type      | Description                                   |
| ------------------ | --------- | --------------------------------------------- |
| `market_id`        | `str`     | Unique market identifier                      |
| `prev_borrow_rate` | `Decimal` | Previous borrow rate (per second, 1e18 scale) |
| `interest`         | `Decimal` | Total interest accrued                        |
| `fee_shares`       | `Decimal` | Shares minted as protocol fee                 |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### BorrowEventData

```
BorrowEventData(
    market_id: str,
    caller: str,
    on_behalf_of: str,
    receiver: str,
    assets: Decimal,
    shares: Decimal,
)
```

Parsed data from Borrow event.

Attributes:

| Name           | Type      | Description                               |
| -------------- | --------- | ----------------------------------------- |
| `market_id`    | `str`     | Unique market identifier                  |
| `caller`       | `str`     | Address that initiated the borrow         |
| `on_behalf_of` | `str`     | Address that received the debt            |
| `receiver`     | `str`     | Address that received the borrowed assets |
| `assets`       | `Decimal` | Amount of assets borrowed                 |
| `shares`       | `Decimal` | Amount of borrow shares minted            |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### CreateMarketEventData

```
CreateMarketEventData(
    market_id: str,
    loan_token: str,
    collateral_token: str,
    oracle: str,
    irm: str,
    lltv: int,
)
```

Parsed data from CreateMarket event.

Attributes:

| Name               | Type  | Description                        |
| ------------------ | ----- | ---------------------------------- |
| `market_id`        | `str` | Unique market identifier           |
| `loan_token`       | `str` | Address of the loan token          |
| `collateral_token` | `str` | Address of the collateral token    |
| `oracle`           | `str` | Address of the oracle              |
| `irm`              | `str` | Address of the interest rate model |
| `lltv`             | `int` | Liquidation LTV (1e18 scale)       |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### FlashLoanEventData

```
FlashLoanEventData(
    caller: str, token: str, assets: Decimal
)
```

Parsed data from FlashLoan event.

Attributes:

| Name     | Type      | Description                           |
| -------- | --------- | ------------------------------------- |
| `caller` | `str`     | Address that initiated the flash loan |
| `token`  | `str`     | Address of the token borrowed         |
| `assets` | `Decimal` | Amount borrowed                       |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### LiquidateEventData

```
LiquidateEventData(
    market_id: str,
    caller: str,
    borrower: str,
    repaid_assets: Decimal,
    repaid_shares: Decimal,
    seized_assets: Decimal,
    bad_debt_assets: Decimal = Decimal("0"),
    bad_debt_shares: Decimal = Decimal("0"),
)
```

Parsed data from Liquidate event.

Attributes:

| Name              | Type      | Description                              |
| ----------------- | --------- | ---------------------------------------- |
| `market_id`       | `str`     | Unique market identifier                 |
| `caller`          | `str`     | Address that initiated the liquidation   |
| `borrower`        | `str`     | Address of the borrower being liquidated |
| `repaid_assets`   | `Decimal` | Amount of debt repaid                    |
| `repaid_shares`   | `Decimal` | Amount of borrow shares burned           |
| `seized_assets`   | `Decimal` | Amount of collateral seized              |
| `bad_debt_assets` | `Decimal` | Amount of bad debt (if any)              |
| `bad_debt_shares` | `Decimal` | Amount of bad debt shares (if any)       |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### MorphoBlueEvent

```
MorphoBlueEvent(
    event_type: MorphoBlueEventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(tz=None))(),
)
```

Parsed Morpho Blue event.

Attributes:

| Name               | Type                  | Description                    |
| ------------------ | --------------------- | ------------------------------ |
| `event_type`       | `MorphoBlueEventType` | Type of event                  |
| `event_name`       | `str`                 | Name of event (e.g., "Supply") |
| `log_index`        | `int`                 | Index of log in transaction    |
| `transaction_hash` | `str`                 | Transaction hash               |
| `block_number`     | `int`                 | Block number                   |
| `contract_address` | `str`                 | Contract that emitted event    |
| `data`             | `dict[str, Any]`      | Parsed event data              |
| `raw_topics`       | `list[str]`           | Raw event topics               |
| `raw_data`         | `str`                 | Raw event data                 |
| `timestamp`        | `datetime`            | Event timestamp                |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> MorphoBlueEvent
```

Create from dictionary.

### MorphoBlueEventType

Bases: `Enum`

Morpho Blue event types.

### MorphoBlueReceiptParser

```
MorphoBlueReceiptParser(**kwargs: Any)
```

Parser for Morpho Blue transaction receipts.

This parser extracts and decodes events from Morpho Blue transactions, providing structured data for supply, borrow, repay, withdraw, liquidation, and other protocol operations.

Example

parser = MorphoBlueReceiptParser()

#### Parse a receipt

result = parser.parse_receipt(receipt)

if result.success: for event in result.events: print(f"{event.event_name}: {event.data}")

Initialize the parser.

Parameters:

| Name       | Type  | Description                                      | Default |
| ---------- | ----- | ------------------------------------------------ | ------- |
| `**kwargs` | `Any` | Additional arguments (ignored for compatibility) | `{}`    |

#### parse_receipt

```
parse_receipt(
    receipt: dict[str, Any],
    timestamp: datetime | None = None,
) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name        | Type             | Description                                      | Default                       |
| ----------- | ---------------- | ------------------------------------------------ | ----------------------------- |
| `receipt`   | `dict[str, Any]` | Transaction receipt dictionary with 'logs' field | *required*                    |
| `timestamp` | \`datetime       | None\`                                           | Optional timestamp for events |

Returns:

| Type          | Description                    |
| ------------- | ------------------------------ |
| `ParseResult` | ParseResult with parsed events |

#### is_morpho_event

```
is_morpho_event(topic: str | bytes) -> bool
```

Check if a topic is a known Morpho Blue event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                                    |
| ------ | ---------------------------------------------- |
| `bool` | True if the topic is a known Morpho Blue event |

#### get_event_type

```
get_event_type(
    topic_or_name: str | bytes,
) -> MorphoBlueEventType
```

Get event type from topic hash or event name.

Parameters:

| Name            | Type  | Description | Default                                                                          |
| --------------- | ----- | ----------- | -------------------------------------------------------------------------------- |
| `topic_or_name` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) or event name |

Returns:

| Type                  | Description                    |
| --------------------- | ------------------------------ |
| `MorphoBlueEventType` | MorphoBlueEventType enum value |

#### extract_supply_amount

```
extract_supply_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract supply amount (assets) from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_withdraw_amount

```
extract_withdraw_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract withdraw amount (assets) from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_borrow_amount

```
extract_borrow_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract borrow amount (assets) from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_repay_amount

```
extract_repay_amount(receipt: dict[str, Any]) -> int | None
```

Extract repay amount (assets) from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_shares_received

```
extract_shares_received(
    receipt: dict[str, Any],
) -> int | None
```

Extract shares received from supply transaction.

In Morpho Blue, supplying assets mints shares representing the deposit.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_shares_burned

```
extract_shares_burned(
    receipt: dict[str, Any],
) -> int | None
```

Extract shares burned from withdraw or repay transaction.

In Morpho Blue, withdrawing/repaying burns shares.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_a_token_received

```
extract_a_token_received(
    receipt: dict[str, Any],
) -> int | None
```

Extract shares received from SUPPLY event (Morpho equivalent of aTokens).

In Morpho Blue, supplying assets mints shares. This is the Morpho equivalent of Aave's aToken minting, exposed under the standard enrichment field name so ResultEnricher can call it for SUPPLY intents.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_supply_rate

```
extract_supply_rate(_receipt: dict[str, Any]) -> None
```

Extract supply rate from receipt.

Morpho Blue events do not include rate information (rates are derived from market utilization). Returns None; callers should query the market state directly for current rates.

#### extract_supply_collateral_amount

```
extract_supply_collateral_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract collateral supply amount from SUPPLY_COLLATERAL event.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

### ParseResult

```
ParseResult(
    success: bool,
    events: list[MorphoBlueEvent] = list(),
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
)
```

Result of parsing a transaction receipt.

Attributes:

| Name               | Type                    | Description                    |
| ------------------ | ----------------------- | ------------------------------ |
| `success`          | `bool`                  | Whether parsing succeeded      |
| `events`           | `list[MorphoBlueEvent]` | List of parsed events          |
| `error`            | \`str                   | None\`                         |
| `transaction_hash` | `str`                   | Hash of the parsed transaction |
| `block_number`     | `int`                   | Block number                   |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### RepayEventData

```
RepayEventData(
    market_id: str,
    caller: str,
    on_behalf_of: str,
    assets: Decimal,
    shares: Decimal,
)
```

Parsed data from Repay event.

Attributes:

| Name           | Type      | Description                     |
| -------------- | --------- | ------------------------------- |
| `market_id`    | `str`     | Unique market identifier        |
| `caller`       | `str`     | Address that made the repayment |
| `on_behalf_of` | `str`     | Address whose debt was repaid   |
| `assets`       | `Decimal` | Amount of assets repaid         |
| `shares`       | `Decimal` | Amount of borrow shares burned  |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SetAuthorizationEventData

```
SetAuthorizationEventData(
    caller: str, authorized: str, is_authorized: bool
)
```

Parsed data from SetAuthorization event.

Morpho Blue signature: SetAuthorization(address indexed caller, address indexed authorized, bool isAuthorized)

Attributes:

| Name            | Type   | Description                                    |
| --------------- | ------ | ---------------------------------------------- |
| `caller`        | `str`  | Address that called setAuthorization           |
| `authorized`    | `str`  | Address whose authorization status was changed |
| `is_authorized` | `bool` | Whether authorization was granted or revoked   |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SupplyCollateralEventData

```
SupplyCollateralEventData(
    market_id: str,
    caller: str,
    on_behalf_of: str,
    assets: Decimal,
)
```

Parsed data from SupplyCollateral event.

Attributes:

| Name           | Type      | Description                                  |
| -------------- | --------- | -------------------------------------------- |
| `market_id`    | `str`     | Unique market identifier                     |
| `caller`       | `str`     | Address that initiated the collateral supply |
| `on_behalf_of` | `str`     | Address that received the collateral credit  |
| `assets`       | `Decimal` | Amount of collateral supplied                |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SupplyEventData

```
SupplyEventData(
    market_id: str,
    caller: str,
    on_behalf_of: str,
    assets: Decimal,
    shares: Decimal,
)
```

Parsed data from Supply event.

In Morpho Blue, Supply is for lending assets to earn interest.

Attributes:

| Name           | Type      | Description                             |
| -------------- | --------- | --------------------------------------- |
| `market_id`    | `str`     | Unique market identifier                |
| `caller`       | `str`     | Address that initiated the supply       |
| `on_behalf_of` | `str`     | Address that received the supply shares |
| `assets`       | `Decimal` | Amount of assets supplied               |
| `shares`       | `Decimal` | Amount of shares minted                 |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### TransferEventData

```
TransferEventData(
    from_address: str, to_address: str, amount: Decimal
)
```

Parsed data from ERC20 Transfer event.

Attributes:

| Name           | Type      | Description        |
| -------------- | --------- | ------------------ |
| `from_address` | `str`     | Sender address     |
| `to_address`   | `str`     | Recipient address  |
| `amount`       | `Decimal` | Amount transferred |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### WithdrawCollateralEventData

```
WithdrawCollateralEventData(
    market_id: str,
    caller: str,
    on_behalf_of: str,
    receiver: str,
    assets: Decimal,
)
```

Parsed data from WithdrawCollateral event.

Attributes:

| Name           | Type      | Description                            |
| -------------- | --------- | -------------------------------------- |
| `market_id`    | `str`     | Unique market identifier               |
| `caller`       | `str`     | Address that initiated the withdrawal  |
| `on_behalf_of` | `str`     | Address whose collateral was withdrawn |
| `receiver`     | `str`     | Address that received the collateral   |
| `assets`       | `Decimal` | Amount of collateral withdrawn         |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### WithdrawEventData

```
WithdrawEventData(
    market_id: str,
    caller: str,
    on_behalf_of: str,
    receiver: str,
    assets: Decimal,
    shares: Decimal,
)
```

Parsed data from Withdraw event.

In Morpho Blue, Withdraw is for withdrawing supplied (lending) assets.

Attributes:

| Name           | Type      | Description                           |
| -------------- | --------- | ------------------------------------- |
| `market_id`    | `str`     | Unique market identifier              |
| `caller`       | `str`     | Address that initiated the withdrawal |
| `on_behalf_of` | `str`     | Address whose shares were burned      |
| `receiver`     | `str`     | Address that received the assets      |
| `assets`       | `Decimal` | Amount of assets withdrawn            |
| `shares`       | `Decimal` | Amount of shares burned               |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### MarketNotFoundError

```
MarketNotFoundError(market_id: str)
```

Bases: `MorphoBlueSDKError`

Market does not exist or has not been created.

### MorphoBlueSDK

```
MorphoBlueSDK(
    chain: str = "ethereum", rpc_url: str | None = None
)
```

Low-level SDK for Morpho Blue protocol interactions.

This SDK handles direct RPC calls to read on-chain state from Morpho Blue. It uses raw calldata encoding for efficiency and minimal dependencies.

Example

sdk = MorphoBlueSDK(chain="ethereum")

#### Get user position

position = sdk.get_position(market_id, user_address) print(f"Supply shares: {position.supply_shares}") print(f"Borrow shares: {position.borrow_shares}")

#### Get market state

state = sdk.get_market_state(market_id) print(f"Utilization: {state.utilization_percent}%")

#### Discover markets

market_ids = sdk.discover_markets()

Initialize the SDK.

Parameters:

| Name      | Type  | Description                 | Default                                                  |
| --------- | ----- | --------------------------- | -------------------------------------------------------- |
| `chain`   | `str` | Chain name (ethereum, base) | `'ethereum'`                                             |
| `rpc_url` | \`str | None\`                      | Optional RPC URL. If not provided, uses ALCHEMY_API_KEY. |

Raises:

| Type                    | Description               |
| ----------------------- | ------------------------- |
| `UnsupportedChainError` | If chain is not supported |

#### get_position

```
get_position(market_id: str, user: str) -> SDKPosition
```

Get user position in a market.

Calls the `position(bytes32 id, address user)` view function.

Parameters:

| Name        | Type  | Description                            | Default    |
| ----------- | ----- | -------------------------------------- | ---------- |
| `market_id` | `str` | Market identifier (bytes32 hex string) | *required* |
| `user`      | `str` | User address                           | *required* |

Returns:

| Type          | Description                                               |
| ------------- | --------------------------------------------------------- |
| `SDKPosition` | SDKPosition with supply_shares, borrow_shares, collateral |

Raises:

| Type       | Description       |
| ---------- | ----------------- |
| `RPCError` | If RPC call fails |

#### get_supply_shares

```
get_supply_shares(market_id: str, user: str) -> int
```

Get user's supply shares in a market.

Parameters:

| Name        | Type  | Description       | Default    |
| ----------- | ----- | ----------------- | ---------- |
| `market_id` | `str` | Market identifier | *required* |
| `user`      | `str` | User address      | *required* |

Returns:

| Type  | Description                |
| ----- | -------------------------- |
| `int` | Supply shares (1e18 scale) |

#### get_borrow_shares

```
get_borrow_shares(market_id: str, user: str) -> int
```

Get user's borrow shares in a market.

Parameters:

| Name        | Type  | Description       | Default    |
| ----------- | ----- | ----------------- | ---------- |
| `market_id` | `str` | Market identifier | *required* |
| `user`      | `str` | User address      | *required* |

Returns:

| Type  | Description                |
| ----- | -------------------------- |
| `int` | Borrow shares (1e18 scale) |

#### get_collateral

```
get_collateral(market_id: str, user: str) -> int
```

Get user's collateral in a market.

Parameters:

| Name        | Type  | Description       | Default    |
| ----------- | ----- | ----------------- | ---------- |
| `market_id` | `str` | Market identifier | *required* |
| `user`      | `str` | User address      | *required* |

Returns:

| Type  | Description                        |
| ----- | ---------------------------------- |
| `int` | Collateral amount (in token units) |

#### get_market_state

```
get_market_state(market_id: str) -> SDKMarketState
```

Get current state of a market.

Calls the `market(bytes32 id)` view function.

Parameters:

| Name        | Type  | Description                            | Default    |
| ----------- | ----- | -------------------------------------- | ---------- |
| `market_id` | `str` | Market identifier (bytes32 hex string) | *required* |

Returns:

| Type             | Description                                |
| ---------------- | ------------------------------------------ |
| `SDKMarketState` | SDKMarketState with totals and utilization |

Raises:

| Type                  | Description             |
| --------------------- | ----------------------- |
| `MarketNotFoundError` | If market doesn't exist |
| `RPCError`            | If RPC call fails       |

#### get_market_params

```
get_market_params(market_id: str) -> SDKMarketParams
```

Get market parameters.

Calls the `idToMarketParams(bytes32 id)` view function.

Parameters:

| Name        | Type  | Description                            | Default    |
| ----------- | ----- | -------------------------------------- | ---------- |
| `market_id` | `str` | Market identifier (bytes32 hex string) | *required* |

Returns:

| Type              | Description                                                 |
| ----------------- | ----------------------------------------------------------- |
| `SDKMarketParams` | SDKMarketParams with token addresses, oracle, IRM, and LLTV |

Raises:

| Type                  | Description             |
| --------------------- | ----------------------- |
| `MarketNotFoundError` | If market doesn't exist |
| `RPCError`            | If RPC call fails       |

#### get_market_info

```
get_market_info(market_id: str) -> SDKMarketInfo
```

Get complete market information (params + state).

Parameters:

| Name        | Type  | Description       | Default    |
| ----------- | ----- | ----------------- | ---------- |
| `market_id` | `str` | Market identifier | *required* |

Returns:

| Type            | Description                              |
| --------------- | ---------------------------------------- |
| `SDKMarketInfo` | SDKMarketInfo with both params and state |

#### discover_markets

```
discover_markets(
    from_block: int | None = None,
    to_block: int | str = "latest",
    chunk_size: int = 10000,
) -> list[str]
```

Discover all markets by scanning CreateMarket events.

Scans the blockchain for CreateMarket events to find all market IDs. Automatically chunks requests to stay within RPC provider block range limits.

Parameters:

| Name         | Type  | Description                                                                   | Default                                              |
| ------------ | ----- | ----------------------------------------------------------------------------- | ---------------------------------------------------- |
| `from_block` | \`int | None\`                                                                        | Starting block (defaults to Morpho deployment block) |
| `to_block`   | \`int | str\`                                                                         | Ending block (defaults to "latest")                  |
| `chunk_size` | `int` | Max blocks per eth_getLogs request (default 10,000 for Alchemy compatibility) | `10000`                                              |

Returns:

| Type        | Description                              |
| ----------- | ---------------------------------------- |
| `list[str]` | List of market IDs (bytes32 hex strings) |

Raises:

| Type       | Description             |
| ---------- | ----------------------- |
| `RPCError` | If event scanning fails |

#### get_market_count

```
get_market_count() -> int
```

Get the total number of markets created.

Returns:

| Type  | Description                  |
| ----- | ---------------------------- |
| `int` | Number of markets discovered |

#### get_block_number

```
get_block_number() -> int
```

Get current block number.

Returns:

| Type  | Description          |
| ----- | -------------------- |
| `int` | Current block number |

#### get_chain_id

```
get_chain_id() -> int
```

Get chain ID.

Returns:

| Type  | Description |
| ----- | ----------- |
| `int` | Chain ID    |

#### is_connected

```
is_connected() -> bool
```

Check if connected to RPC.

Returns:

| Type   | Description       |
| ------ | ----------------- |
| `bool` | True if connected |

#### shares_to_assets

```
shares_to_assets(
    shares: int, total_assets: int, total_shares: int
) -> int
```

Convert shares to assets.

Uses the Morpho formula: assets = shares * totalAssets / totalShares

Parameters:

| Name           | Type  | Description            | Default    |
| -------------- | ----- | ---------------------- | ---------- |
| `shares`       | `int` | Number of shares       | *required* |
| `total_assets` | `int` | Total assets in market | *required* |
| `total_shares` | `int` | Total shares in market | *required* |

Returns:

| Type  | Description  |
| ----- | ------------ |
| `int` | Asset amount |

#### assets_to_shares

```
assets_to_shares(
    assets: int, total_assets: int, total_shares: int
) -> int
```

Convert assets to shares.

Uses the Morpho formula: shares = assets * totalShares / totalAssets

Parameters:

| Name           | Type  | Description            | Default    |
| -------------- | ----- | ---------------------- | ---------- |
| `assets`       | `int` | Asset amount           | *required* |
| `total_assets` | `int` | Total assets in market | *required* |
| `total_shares` | `int` | Total shares in market | *required* |

Returns:

| Type  | Description      |
| ----- | ---------------- |
| `int` | Number of shares |

#### get_supply_assets

```
get_supply_assets(market_id: str, user: str) -> int
```

Get user's supply amount in assets (not shares).

Parameters:

| Name        | Type  | Description       | Default    |
| ----------- | ----- | ----------------- | ---------- |
| `market_id` | `str` | Market identifier | *required* |
| `user`      | `str` | User address      | *required* |

Returns:

| Type  | Description                  |
| ----- | ---------------------------- |
| `int` | Supply amount in asset units |

#### get_borrow_assets

```
get_borrow_assets(market_id: str, user: str) -> int
```

Get user's borrow amount in assets (not shares).

Parameters:

| Name        | Type  | Description       | Default    |
| ----------- | ----- | ----------------- | ---------- |
| `market_id` | `str` | Market identifier | *required* |
| `user`      | `str` | User address      | *required* |

Returns:

| Type  | Description                  |
| ----- | ---------------------------- |
| `int` | Borrow amount in asset units |

### MorphoBlueSDKError

Bases: `Exception`

Base exception for Morpho Blue SDK errors.

### PositionNotFoundError

```
PositionNotFoundError(market_id: str, user: str)
```

Bases: `MorphoBlueSDKError`

Position does not exist for user in market.

### RPCError

```
RPCError(message: str, method: str)
```

Bases: `MorphoBlueSDKError`

Error making RPC call.

### SDKMarketInfo

```
SDKMarketInfo(
    params: SDKMarketParams, state: SDKMarketState
)
```

Complete market information combining state and params.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SDKMarketParams

```
SDKMarketParams(
    market_id: str,
    loan_token: str,
    collateral_token: str,
    oracle: str,
    irm: str,
    lltv: int,
)
```

Market parameters for a Morpho Blue market (from on-chain read).

These parameters uniquely identify a market. The market_id is derived as: keccak256(abi.encode(loanToken, collateralToken, oracle, irm, lltv))

Attributes:

| Name               | Type  | Description                                       |
| ------------------ | ----- | ------------------------------------------------- |
| `market_id`        | `str` | Market identifier (bytes32 as hex string)         |
| `loan_token`       | `str` | Address of the asset being borrowed               |
| `collateral_token` | `str` | Address of the collateral asset                   |
| `oracle`           | `str` | Address of the price oracle                       |
| `irm`              | `str` | Address of the interest rate model                |
| `lltv`             | `int` | Liquidation LTV (1e18 scale, e.g., 0.86e18 = 86%) |

#### lltv_percent

```
lltv_percent: Decimal
```

LLTV as percentage (0-100).

#### lltv_decimal

```
lltv_decimal: Decimal
```

LLTV as decimal (0-1).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SDKMarketState

```
SDKMarketState(
    market_id: str,
    total_supply_assets: int,
    total_supply_shares: int,
    total_borrow_assets: int,
    total_borrow_shares: int,
    last_update: int,
    fee: int,
)
```

Current state of a Morpho Blue market (from on-chain read).

Attributes:

| Name                  | Type  | Description                                   |
| --------------------- | ----- | --------------------------------------------- |
| `market_id`           | `str` | Market identifier                             |
| `total_supply_assets` | `int` | Total assets supplied to the market           |
| `total_supply_shares` | `int` | Total supply shares                           |
| `total_borrow_assets` | `int` | Total assets borrowed                         |
| `total_borrow_shares` | `int` | Total borrow shares                           |
| `last_update`         | `int` | Timestamp of last interest accrual            |
| `fee`                 | `int` | Protocol fee (1e18 scale, e.g., 0.1e18 = 10%) |

#### utilization

```
utilization: Decimal
```

Calculate market utilization rate (0-1 scale).

#### utilization_percent

```
utilization_percent: Decimal
```

Utilization as percentage (0-100).

#### available_liquidity

```
available_liquidity: int
```

Available liquidity for borrowing.

#### fee_percent

```
fee_percent: Decimal
```

Fee as percentage.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SDKPosition

```
SDKPosition(
    market_id: str,
    user: str,
    supply_shares: int,
    borrow_shares: int,
    collateral: int,
)
```

User position in a Morpho Blue market (from on-chain read).

Attributes:

| Name            | Type  | Description                               |
| --------------- | ----- | ----------------------------------------- |
| `market_id`     | `str` | Market identifier                         |
| `user`          | `str` | User address                              |
| `supply_shares` | `int` | User's supply shares (1e18 scale)         |
| `borrow_shares` | `int` | User's borrow shares (1e18 scale)         |
| `collateral`    | `int` | User's collateral amount (in token units) |

#### has_supply

```
has_supply: bool
```

Check if user has supply position.

#### has_borrow

```
has_borrow: bool
```

Check if user has borrow position.

#### has_collateral

```
has_collateral: bool
```

Check if user has collateral.

#### is_empty

```
is_empty: bool
```

Check if position is empty.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### UnsupportedChainError

```
UnsupportedChainError(chain: str)
```

Bases: `MorphoBlueSDKError`

Chain is not supported by Morpho Blue.

### create_adapter_with_prices

```
create_adapter_with_prices(
    config: MorphoBlueConfig, prices: dict[str, Decimal]
) -> MorphoBlueAdapter
```

Create an adapter with a dictionary of real prices.

This is the recommended way to create an adapter for production use. It ensures accurate health factor calculations by providing real prices.

Parameters:

| Name     | Type                 | Description                              | Default    |
| -------- | -------------------- | ---------------------------------------- | ---------- |
| `config` | `MorphoBlueConfig`   | Adapter configuration                    | *required* |
| `prices` | `dict[str, Decimal]` | Dict mapping token symbols to USD prices | *required* |

Returns:

| Type                | Description                                   |
| ------------------- | --------------------------------------------- |
| `MorphoBlueAdapter` | MorphoBlueAdapter configured with real prices |

Example

prices = { "USDC": Decimal("1.00"), "USDT": Decimal("1.00"), "wstETH": Decimal("3500.00"), "WETH": Decimal("3100.00"), "WBTC": Decimal("98000.00"), } config = MorphoBlueConfig( chain="ethereum", wallet_address="0x...", ) adapter = create_adapter_with_prices(config, prices)

### create_test_adapter

```
create_test_adapter(
    chain: str = "ethereum",
    wallet_address: str = "0x1234567890123456789012345678901234567890",
) -> MorphoBlueAdapter
```

Create a test adapter with placeholder prices and SDK disabled.

For unit testing only. DO NOT use in production.

Parameters:

| Name             | Type  | Description                            | Default                                        |
| ---------------- | ----- | -------------------------------------- | ---------------------------------------------- |
| `chain`          | `str` | Chain name (default: ethereum)         | `'ethereum'`                                   |
| `wallet_address` | `str` | Wallet address (default: test address) | `'0x1234567890123456789012345678901234567890'` |

Returns:

| Type                | Description                              |
| ------------------- | ---------------------------------------- |
| `MorphoBlueAdapter` | MorphoBlueAdapter configured for testing |

# Morpho Vault

Connector for Morpho Vault protocol.

## almanak.framework.connectors.morpho_vault

MetaMorpho Vault Connector.

This module provides adapters and utilities for interacting with MetaMorpho vaults, the ERC-4626 vault layer that aggregates capital across Morpho Blue lending markets.

MetaMorpho Features:

- ERC-4626 compliant vault deposits and redemptions
- Passive yield optimization with curator-managed allocation
- Multi-market capital allocation across Morpho Blue markets
- Transparent share pricing via convertToAssets/convertToShares

Supported Chains:

- Ethereum
- Base

Example

from almanak.framework.connectors.morpho_vault import ( MetaMorphoAdapter, MetaMorphoConfig, MetaMorphoReceiptParser, MetaMorphoSDK, create_test_adapter, ) from decimal import Decimal

### Initialize adapter

config = MetaMorphoConfig(chain="ethereum", wallet_address="0x...") adapter = MetaMorphoAdapter(config, gateway_client=gateway_client)

### Deposit assets

result = adapter.deposit( vault_address="0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB", amount=Decimal("1000"), )

### Redeem all shares

result = adapter.redeem( vault_address="0xBEEF01735c132Ada46AA9aA4c54623cAA92A64CB", shares="all", )

### Parse transaction receipts

parser = MetaMorphoReceiptParser() parse_result = parser.parse_receipt(receipt)

### MetaMorphoAdapter

```
MetaMorphoAdapter(
    config: MetaMorphoConfig,
    gateway_client=None,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for MetaMorpho vault protocol.

Provides high-level methods for depositing into and redeeming from MetaMorpho ERC-4626 vaults, with token resolution and validation.

Example

config = MetaMorphoConfig(chain="ethereum", wallet_address="0x...") adapter = MetaMorphoAdapter(config, gateway_client=client)

#### Get vault info

info = adapter.get_vault_info("0xBEEF...")

#### Deposit

result = adapter.deposit("0xBEEF...", Decimal("1000"))

Initialize the adapter.

Parameters:

| Name             | Type               | Description                                                     | Default                                                   |
| ---------------- | ------------------ | --------------------------------------------------------------- | --------------------------------------------------------- |
| `config`         | `MetaMorphoConfig` | Adapter configuration                                           | *required*                                                |
| `gateway_client` |                    | Gateway client for RPC calls. Required for on-chain operations. | `None`                                                    |
| `token_resolver` | \`TokenResolver    | None\`                                                          | Optional TokenResolver instance. If None, uses singleton. |

#### sdk

```
sdk: MetaMorphoSDK
```

Get the SDK instance (lazy initialization).

#### get_vault_info

```
get_vault_info(vault_address: str) -> VaultInfo
```

Get complete vault information.

Parameters:

| Name            | Type  | Description              | Default    |
| --------------- | ----- | ------------------------ | ---------- |
| `vault_address` | `str` | MetaMorpho vault address | *required* |

Returns:

| Type        | Description                |
| ----------- | -------------------------- |
| `VaultInfo` | VaultInfo with vault state |

#### get_position

```
get_position(
    vault_address: str, user: str | None = None
) -> VaultPosition
```

Get user's position in the vault.

Parameters:

| Name            | Type  | Description              | Default                                   |
| --------------- | ----- | ------------------------ | ----------------------------------------- |
| `vault_address` | `str` | MetaMorpho vault address | *required*                                |
| `user`          | \`str | None\`                   | User address (defaults to wallet_address) |

Returns:

| Type            | Description                          |
| --------------- | ------------------------------------ |
| `VaultPosition` | VaultPosition with shares and assets |

#### deposit

```
deposit(
    vault_address: str, amount: Decimal
) -> TransactionResult
```

Build a deposit transaction for a MetaMorpho vault.

This builds approve + deposit transactions. The approve TX authorizes the vault to pull the exact amount of underlying tokens.

Parameters:

| Name            | Type      | Description                                                               | Default    |
| --------------- | --------- | ------------------------------------------------------------------------- | ---------- |
| `vault_address` | `str`     | MetaMorpho vault address                                                  | *required* |
| `amount`        | `Decimal` | Amount of underlying assets to deposit (in token units, e.g. 1000.0 USDC) | *required* |

Returns:

| Type                | Description                                                          |
| ------------------- | -------------------------------------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data for both approve and deposit |

#### redeem

```
redeem(
    vault_address: str, shares: Decimal | str
) -> TransactionResult
```

Build a redeem transaction for a MetaMorpho vault.

Parameters:

| Name            | Type      | Description              | Default                                            |
| --------------- | --------- | ------------------------ | -------------------------------------------------- |
| `vault_address` | `str`     | MetaMorpho vault address | *required*                                         |
| `shares`        | \`Decimal | str\`                    | Number of shares to redeem, or "all" to redeem all |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### build_approve_transaction

```
build_approve_transaction(
    token: str, amount: Decimal, spender: str
) -> TransactionResult
```

Build an ERC20 approve transaction.

Parameters:

| Name      | Type      | Description             | Default    |
| --------- | --------- | ----------------------- | ---------- |
| `token`   | `str`     | Token symbol or address | *required* |
| `amount`  | `Decimal` | Amount to approve       | *required* |
| `spender` | `str`     | Address to approve      | *required* |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

### MetaMorphoConfig

```
MetaMorphoConfig(chain: str, wallet_address: str)
```

Configuration for MetaMorpho adapter.

Attributes:

| Name             | Type  | Description                         |
| ---------------- | ----- | ----------------------------------- |
| `chain`          | `str` | Blockchain network (ethereum, base) |
| `wallet_address` | `str` | User wallet address                 |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

### TransactionResult

```
TransactionResult(
    success: bool,
    tx_data: dict[str, Any] | None = None,
    gas_estimate: int = 0,
    description: str = "",
    error: str | None = None,
)
```

Result of a transaction build operation.

Attributes:

| Name           | Type             | Description                 |
| -------------- | ---------------- | --------------------------- |
| `success`      | `bool`           | Whether operation succeeded |
| `tx_data`      | \`dict[str, Any] | None\`                      |
| `gas_estimate` | `int`            | Estimated gas               |
| `description`  | `str`            | Human-readable description  |
| `error`        | \`str            | None\`                      |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### MetaMorphoEvent

```
MetaMorphoEvent(
    event_type: MetaMorphoEventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
)
```

Parsed MetaMorpho event.

### MetaMorphoEventType

Bases: `Enum`

MetaMorpho event types.

### MetaMorphoReceiptParser

```
MetaMorphoReceiptParser(**kwargs: Any)
```

Parser for MetaMorpho vault transaction receipts.

Extracts ERC-4626 Deposit/Withdraw events and ERC-20 Transfer/Approval events.

Example

parser = MetaMorphoReceiptParser() result = parser.parse_receipt(receipt) if result.success: for event in result.events: print(f"{event.event_name}: {event.data}")

Initialize the parser.

#### parse_receipt

```
parse_receipt(
    receipt: dict[str, Any],
    timestamp: datetime | None = None,
) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name        | Type             | Description                                      | Default                       |
| ----------- | ---------------- | ------------------------------------------------ | ----------------------------- |
| `receipt`   | `dict[str, Any]` | Transaction receipt dictionary with 'logs' field | *required*                    |
| `timestamp` | \`datetime       | None\`                                           | Optional timestamp for events |

Returns:

| Type          | Description                    |
| ------------- | ------------------------------ |
| `ParseResult` | ParseResult with parsed events |

#### extract_deposit_data

```
extract_deposit_data(
    receipt: dict[str, Any],
) -> dict | None
```

Extract deposit data from transaction receipt.

Called by ResultEnricher for VAULT_DEPOSIT intents.

Returns:

| Type   | Description |
| ------ | ----------- |
| \`dict | None\`      |
| \`dict | None\`      |
| \`dict | None\`      |
| \`dict | None\`      |

#### extract_redeem_data

```
extract_redeem_data(receipt: dict[str, Any]) -> dict | None
```

Extract redeem data from transaction receipt.

Called by ResultEnricher for VAULT_REDEEM intents.

Returns:

| Type   | Description |
| ------ | ----------- |
| \`dict | None\`      |

### ParseResult

```
ParseResult(
    success: bool,
    events: list[MetaMorphoEvent] = list(),
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
)
```

Result of parsing a transaction receipt.

### TransferEventData

```
TransferEventData(
    from_address: str, to_address: str, amount: Decimal
)
```

Parsed data from ERC-20 Transfer event.

### VaultDepositEventData

```
VaultDepositEventData(
    sender: str,
    owner: str,
    assets: Decimal,
    shares: Decimal,
)
```

Parsed data from ERC-4626 Deposit event.

### VaultWithdrawEventData

```
VaultWithdrawEventData(
    sender: str,
    receiver: str,
    owner: str,
    assets: Decimal,
    shares: Decimal,
)
```

Parsed data from ERC-4626 Withdraw event.

### DepositExceedsCapError

Bases: `MetaMorphoSDKError`

Raised when deposit amount exceeds vault's maxDeposit.

### InsufficientSharesError

Bases: `MetaMorphoSDKError`

Raised when redeem amount exceeds user's maxRedeem.

### MetaMorphoSDK

```
MetaMorphoSDK(gateway_client, chain: str)
```

Low-level SDK for reading MetaMorpho vault state via gateway RPC calls.

All RPC calls are routed through the gateway client's RPC service. This SDK handles ABI encoding/decoding and provides typed return values.

Parameters:

| Name             | Type  | Description                                 | Default    |
| ---------------- | ----- | ------------------------------------------- | ---------- |
| `gateway_client` |       | Connected gateway client with RPC service   | *required* |
| `chain`          | `str` | Chain identifier (e.g., "ethereum", "base") | *required* |

#### get_vault_asset

```
get_vault_asset(vault_address: str) -> str
```

Read the vault's underlying asset address (asset()).

#### get_total_assets

```
get_total_assets(vault_address: str) -> int
```

Read the vault's total assets (totalAssets()).

#### get_total_supply

```
get_total_supply(vault_address: str) -> int
```

Read the vault's total share supply (totalSupply()).

#### get_share_price

```
get_share_price(vault_address: str) -> int
```

Get share price as convertToAssets(one_share) in raw underlying units.

#### get_decimals

```
get_decimals(vault_address: str) -> int
```

Read the vault's share decimals (decimals()). Always 18 for MetaMorpho.

#### get_balance_of

```
get_balance_of(vault_address: str, user: str) -> int
```

Read user's share balance in the vault.

#### get_max_deposit

```
get_max_deposit(vault_address: str, receiver: str) -> int
```

Read maximum deposit amount allowed for a receiver.

#### get_max_redeem

```
get_max_redeem(vault_address: str, owner: str) -> int
```

Read maximum shares that can be redeemed by an owner.

#### preview_deposit

```
preview_deposit(vault_address: str, assets: int) -> int
```

Preview how many shares a deposit of `assets` would mint.

#### preview_redeem

```
preview_redeem(vault_address: str, shares: int) -> int
```

Preview how many assets a redemption of `shares` would return.

#### convert_to_assets

```
convert_to_assets(vault_address: str, shares: int) -> int
```

Convert share amount to asset amount.

#### convert_to_shares

```
convert_to_shares(vault_address: str, assets: int) -> int
```

Convert asset amount to share amount.

#### get_curator

```
get_curator(vault_address: str) -> str
```

Read the vault's curator address.

#### get_fee

```
get_fee(vault_address: str) -> int
```

Read the vault's performance fee (WAD scale, 1e18 = 100%).

#### get_timelock

```
get_timelock(vault_address: str) -> int
```

Read the vault's timelock duration in seconds.

#### is_allocator

```
is_allocator(vault_address: str, address: str) -> bool
```

Check if an address is an allocator for the vault.

#### get_supply_queue

```
get_supply_queue(vault_address: str) -> list[str]
```

Read the vault's supply queue (list of market IDs).

#### get_withdraw_queue

```
get_withdraw_queue(vault_address: str) -> list[str]
```

Read the vault's withdraw queue (list of market IDs).

#### get_vault_info

```
get_vault_info(vault_address: str) -> VaultInfo
```

Read complete vault information in multiple RPC calls.

#### get_position

```
get_position(
    vault_address: str, user: str
) -> VaultPosition
```

Read a user's position in the vault.

#### build_deposit_tx

```
build_deposit_tx(
    vault_address: str, assets: int, receiver: str
) -> dict
```

Build an unsigned ERC-4626 deposit(uint256,address) transaction.

Parameters:

| Name            | Type  | Description                                         | Default    |
| --------------- | ----- | --------------------------------------------------- | ---------- |
| `vault_address` | `str` | The MetaMorpho vault address.                       | *required* |
| `assets`        | `int` | Amount of underlying assets to deposit (raw units). | *required* |
| `receiver`      | `str` | Address to receive vault shares.                    | *required* |

Returns:

| Type   | Description                                                               |
| ------ | ------------------------------------------------------------------------- |
| `dict` | Unsigned transaction dict with keys: to, from, data, value, gas_estimate. |

#### build_redeem_tx

```
build_redeem_tx(
    vault_address: str,
    shares: int,
    receiver: str,
    owner: str,
) -> dict
```

Build an unsigned ERC-4626 redeem(uint256,address,address) transaction.

Parameters:

| Name            | Type  | Description                                  | Default    |
| --------------- | ----- | -------------------------------------------- | ---------- |
| `vault_address` | `str` | The MetaMorpho vault address.                | *required* |
| `shares`        | `int` | Number of shares to redeem (raw units).      | *required* |
| `receiver`      | `str` | Address to receive underlying assets.        | *required* |
| `owner`         | `str` | Address that owns the shares being redeemed. | *required* |

Returns:

| Type   | Description                                                               |
| ------ | ------------------------------------------------------------------------- |
| `dict` | Unsigned transaction dict with keys: to, from, data, value, gas_estimate. |

#### build_approve_tx

```
build_approve_tx(
    token_address: str,
    spender: str,
    amount: int,
    owner: str,
) -> dict
```

Build an ERC-20 approve transaction.

Parameters:

| Name            | Type  | Description                                   | Default    |
| --------------- | ----- | --------------------------------------------- | ---------- |
| `token_address` | `str` | The ERC-20 token address.                     | *required* |
| `spender`       | `str` | The address to approve.                       | *required* |
| `amount`        | `int` | Amount to approve (raw units).                | *required* |
| `owner`         | `str` | The address that owns the tokens (tx sender). | *required* |

Returns:

| Type   | Description                |
| ------ | -------------------------- |
| `dict` | Unsigned transaction dict. |

### MetaMorphoSDKError

Bases: `Exception`

Base exception for MetaMorpho SDK errors.

### RPCError

Bases: `MetaMorphoSDKError`

Raised when an RPC call fails.

### UnsupportedChainError

Bases: `MetaMorphoSDKError`

Raised when chain is not supported.

### VaultInfo

```
VaultInfo(
    address: str,
    asset: str,
    total_assets: int,
    total_supply: int,
    share_price: int,
    decimals: int,
    curator: str,
    fee: int,
    timelock: int,
)
```

Information about a MetaMorpho vault.

### VaultMarketConfig

```
VaultMarketConfig(
    market_id: str,
    cap: int,
    enabled: bool,
    removable_at: int,
)
```

Market configuration within a MetaMorpho vault (Phase 2).

### VaultNotFoundError

Bases: `MetaMorphoSDKError`

Raised when vault contract does not exist or returns invalid data.

### VaultPosition

```
VaultPosition(
    vault_address: str, user: str, shares: int, assets: int
)
```

User position in a MetaMorpho vault.

### create_test_adapter

```
create_test_adapter(
    chain: str = "ethereum",
    wallet_address: str = "0x1234567890123456789012345678901234567890",
) -> MetaMorphoAdapter
```

Create a test adapter without gateway client (for unit tests).

For unit testing only. On-chain operations will raise RuntimeError.

Parameters:

| Name             | Type  | Description                            | Default                                        |
| ---------------- | ----- | -------------------------------------- | ---------------------------------------------- |
| `chain`          | `str` | Chain name (default: ethereum)         | `'ethereum'`                                   |
| `wallet_address` | `str` | Wallet address (default: test address) | `'0x1234567890123456789012345678901234567890'` |

Returns:

| Type                | Description                              |
| ------------------- | ---------------------------------------- |
| `MetaMorphoAdapter` | MetaMorphoAdapter configured for testing |

# Orca

Connector for Orca protocol.

## almanak.framework.connectors.orca

Orca Whirlpools concentrated liquidity connector.

Provides LP operations on Orca Whirlpool pools on Solana:

- Open concentrated liquidity positions
- Close positions (decrease liquidity + burn NFT)

Uses the same Q64.64 tick math as Raydium CLMM (reused from connectors/raydium/math.py) and Anchor-style instruction encoding.

Reference: https://github.com/orca-so/whirlpools

### OrcaAdapter

```
OrcaAdapter(
    config: OrcaConfig,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Orca Whirlpools integration with the Intent system.

Converts LP intents to ActionBundles containing serialized Solana VersionedTransactions built from Orca Whirlpool instructions.

#### compile_lp_open_intent

```
compile_lp_open_intent(
    intent: LPOpenIntent,
) -> ActionBundle
```

Compile an LPOpenIntent to an ActionBundle.

Parameters:

| Name     | Type           | Description                  | Default    |
| -------- | -------------- | ---------------------------- | ---------- |
| `intent` | `LPOpenIntent` | The LPOpenIntent to compile. | *required* |

Returns:

| Type           | Description                                               |
| -------------- | --------------------------------------------------------- |
| `ActionBundle` | ActionBundle containing serialized Solana transaction(s). |

#### compile_lp_close_intent

```
compile_lp_close_intent(
    intent: LPCloseIntent,
) -> ActionBundle
```

Compile an LPCloseIntent to an ActionBundle.

Parameters:

| Name     | Type            | Description                   | Default    |
| -------- | --------------- | ----------------------------- | ---------- |
| `intent` | `LPCloseIntent` | The LPCloseIntent to compile. | *required* |

Returns:

| Type           | Description                                               |
| -------------- | --------------------------------------------------------- |
| `ActionBundle` | ActionBundle containing serialized Solana transaction(s). |

### OrcaConfig

```
OrcaConfig(wallet_address: str, rpc_url: str = '')
```

Configuration for Orca adapter.

Attributes:

| Name             | Type | Description                                     |
| ---------------- | ---- | ----------------------------------------------- |
| `wallet_address` |      | Solana wallet public key (Base58).              |
| `rpc_url`        |      | Solana RPC endpoint URL (for position queries). |

### OrcaAPIError

```
OrcaAPIError(
    message: str, status_code: int = 0, endpoint: str = ""
)
```

Bases: `OrcaError`

Error communicating with the Orca API.

### OrcaConfigError

```
OrcaConfigError(message: str, parameter: str = '')
```

Bases: `OrcaError`

Invalid Orca configuration.

### OrcaError

Bases: `Exception`

Base exception for Orca operations.

### OrcaPoolError

Bases: `OrcaError`

Error with pool state or operations.

### OrcaPool

```
OrcaPool(
    address: str,
    mint_a: str,
    mint_b: str,
    symbol_a: str = "",
    symbol_b: str = "",
    decimals_a: int = 9,
    decimals_b: int = 6,
    tick_spacing: int = 64,
    current_price: float = 0.0,
    tvl: float = 0.0,
    vault_a: str = "",
    vault_b: str = "",
    fee_rate: int = 3000,
    tick_current_index: int = 0,
    sqrt_price: str = "0",
    oracle_address: str = "",
    raw_response: dict[str, Any] = dict(),
)
```

Orca Whirlpool pool information.

Constructed from the Orca API response or on-chain data.

Attributes:

| Name                 | Type    | Description                                   |
| -------------------- | ------- | --------------------------------------------- |
| `address`            | `str`   | Whirlpool account address (Base58).           |
| `mint_a`             | `str`   | Token A mint address.                         |
| `mint_b`             | `str`   | Token B mint address.                         |
| `symbol_a`           | `str`   | Token A symbol (e.g., "SOL").                 |
| `symbol_b`           | `str`   | Token B symbol (e.g., "USDC").                |
| `decimals_a`         | `int`   | Token A decimals.                             |
| `decimals_b`         | `int`   | Token B decimals.                             |
| `tick_spacing`       | `int`   | Tick spacing for this pool.                   |
| `current_price`      | `float` | Current price of token A in terms of token B. |
| `tvl`                | `float` | Total value locked in USD.                    |
| `vault_a`            | `str`   | Token A vault address.                        |
| `vault_b`            | `str`   | Token B vault address.                        |
| `fee_rate`           | `int`   | Fee rate in basis points.                     |
| `tick_current_index` | `int`   | Current tick index.                           |
| `sqrt_price`         | `str`   | Current sqrt price as string (u128).          |
| `oracle_address`     | `str`   | Oracle account address.                       |

#### from_api_response

```
from_api_response(data: dict[str, Any]) -> OrcaPool
```

Create from Orca API /pools/{address} response.

### OrcaPosition

```
OrcaPosition(
    nft_mint: str,
    pool_address: str,
    tick_lower: int,
    tick_upper: int,
    liquidity: int = 0,
    position_address: str = "",
)
```

Orca Whirlpool position (owned by the user).

Attributes:

| Name               | Type  | Description                        |
| ------------------ | ----- | ---------------------------------- |
| `nft_mint`         | `str` | Position NFT mint address.         |
| `pool_address`     | `str` | Whirlpool account address.         |
| `tick_lower`       | `int` | Lower tick boundary.               |
| `tick_upper`       | `int` | Upper tick boundary.               |
| `liquidity`        | `int` | Current liquidity in the position. |
| `position_address` | `str` | Position PDA address.              |

### OrcaTransactionBundle

```
OrcaTransactionBundle(
    transactions: list[str],
    action: str,
    position_nft_mint: str = "",
    metadata: dict[str, Any] = dict(),
)
```

Bundle of serialized transactions for an Orca operation.

Attributes:

| Name                | Type             | Description                                            |
| ------------------- | ---------------- | ------------------------------------------------------ |
| `transactions`      | `list[str]`      | List of base64-encoded VersionedTransactions.          |
| `action`            | `str`            | Action type ("open_position", "close_position", etc.). |
| `position_nft_mint` | `str`            | NFT mint address (for open_position).                  |
| `metadata`          | `dict[str, Any]` | Additional metadata.                                   |

### OrcaReceiptParser

```
OrcaReceiptParser(**kwargs: Any)
```

Parser for Orca Whirlpool transaction receipts.

Supports the extraction methods required by ResultEnricher:

- extract_position_id(receipt) -> str | None
- extract_liquidity(receipt) -> dict | None
- extract_lp_close_data(receipt) -> dict | None

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> dict[str, Any]
```

Parse a receipt for ReceiptParser protocol compatibility.

#### extract_position_id

```
extract_position_id(receipt: dict[str, Any]) -> str | None
```

Extract the position NFT mint from an Orca LP open receipt.

The position NFT is minted during open_position. We find it by looking for a new token account with amount=1 in postTokenBalances that wasn't in preTokenBalances.

#### extract_liquidity

```
extract_liquidity(
    receipt: dict[str, Any],
) -> dict[str, Any] | None
```

Extract liquidity data from an LP open/increase receipt.

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> dict[str, Any] | None
```

Extract LP close data from a receipt.

### OrcaWhirlpoolSDK

```
OrcaWhirlpoolSDK(
    wallet_address: str,
    base_url: str = ORCA_API_BASE_URL,
    timeout: int = 30,
)
```

SDK for building Orca Whirlpool instructions.

Provides methods to:

- Fetch pool data from the Orca API
- Build Solana instructions for LP operations
- Compute PDAs for positions, tick arrays, etc.

Example

sdk = OrcaWhirlpoolSDK(wallet_address="your-pubkey") pool = sdk.get_pool_info("pool-address") ixs, nft_mint = sdk.build_open_position_ix( pool=pool, tick_lower=-100, tick_upper=100, amount_a_max=1_000_000, amount_b_max=500_000_000, liquidity=1000000, )

#### get_pool_info

```
get_pool_info(pool_address: str) -> OrcaPool
```

Fetch pool information from the Orca API.

Parameters:

| Name           | Type  | Description                         | Default    |
| -------------- | ----- | ----------------------------------- | ---------- |
| `pool_address` | `str` | Whirlpool account address (Base58). | *required* |

Returns:

| Type       | Description                      |
| ---------- | -------------------------------- |
| `OrcaPool` | OrcaPool with current pool data. |

Raises:

| Type            | Description           |
| --------------- | --------------------- |
| `OrcaPoolError` | If pool not found.    |
| `OrcaAPIError`  | If API request fails. |

#### find_pool_by_tokens

```
find_pool_by_tokens(
    token_a: str, token_b: str, tick_spacing: int = 64
) -> OrcaPool | None
```

Find a Whirlpool by token pair.

Parameters:

| Name           | Type  | Description                                       | Default    |
| -------------- | ----- | ------------------------------------------------- | ---------- |
| `token_a`      | `str` | Token A mint address.                             | *required* |
| `token_b`      | `str` | Token B mint address.                             | *required* |
| `tick_spacing` | `int` | Preferred tick spacing (default: 64 = 0.30% fee). | `64`       |

Returns:

| Type       | Description |
| ---------- | ----------- |
| \`OrcaPool | None\`      |

#### get_position_state

```
get_position_state(
    nft_mint: str, rpc_url: str
) -> OrcaPosition
```

Query on-chain Position account for an Orca Whirlpool position.

Position layout (Anchor, 8-byte discriminator): [0:8] discriminator [8:40] whirlpool (Pubkey) [40:72] position_mint (Pubkey) [72:88] liquidity (u128 LE) [88:92] tick_lower_index (i32 LE) [92:96] tick_upper_index (i32 LE)

Parameters:

| Name       | Type  | Description                         | Default    |
| ---------- | ----- | ----------------------------------- | ---------- |
| `nft_mint` | `str` | Position NFT mint address (Base58). | *required* |
| `rpc_url`  | `str` | Solana RPC endpoint URL.            | *required* |

Returns:

| Type           | Description                                          |
| -------------- | ---------------------------------------------------- |
| `OrcaPosition` | OrcaPosition with on-chain tick range and liquidity. |

Raises:

| Type            | Description                                    |
| --------------- | ---------------------------------------------- |
| `OrcaPoolError` | If position account not found or data invalid. |

#### build_open_position_ix

```
build_open_position_ix(
    pool: OrcaPool,
    tick_lower: int,
    tick_upper: int,
    amount_a_max: int,
    amount_b_max: int,
    liquidity: int,
) -> tuple[list[Instruction], Keypair]
```

Build instructions for opening a new Whirlpool position.

Creates: open_position_with_metadata + increase_liquidity. Orca separates position creation (open_position) from liquidity deposit (increase_liquidity).

Parameters:

| Name           | Type       | Description                                    | Default    |
| -------------- | ---------- | ---------------------------------------------- | ---------- |
| `pool`         | `OrcaPool` | Pool information.                              | *required* |
| `tick_lower`   | `int`      | Lower tick boundary (aligned to tick spacing). | *required* |
| `tick_upper`   | `int`      | Upper tick boundary (aligned to tick spacing). | *required* |
| `amount_a_max` | `int`      | Maximum amount of token A in smallest units.   | *required* |
| `amount_b_max` | `int`      | Maximum amount of token B in smallest units.   | *required* |
| `liquidity`    | `int`      | Target liquidity amount (u128).                | *required* |

Returns:

| Type                                | Description                                |
| ----------------------------------- | ------------------------------------------ |
| `tuple[list[Instruction], Keypair]` | Tuple of (instructions, nft_mint_keypair). |

#### build_decrease_liquidity_ix

```
build_decrease_liquidity_ix(
    pool: OrcaPool,
    position: OrcaPosition,
    liquidity: int,
    amount_a_min: int = 0,
    amount_b_min: int = 0,
) -> list[Instruction]
```

Build instructions for removing liquidity from a position.

Parameters:

| Name           | Type           | Description                     | Default    |
| -------------- | -------------- | ------------------------------- | ---------- |
| `pool`         | `OrcaPool`     | Pool information.               | *required* |
| `position`     | `OrcaPosition` | Position to decrease.           | *required* |
| `liquidity`    | `int`          | Amount of liquidity to remove.  | *required* |
| `amount_a_min` | `int`          | Minimum acceptable token A out. | `0`        |
| `amount_b_min` | `int`          | Minimum acceptable token B out. | `0`        |

Returns:

| Type                | Description                  |
| ------------------- | ---------------------------- |
| `list[Instruction]` | List of Solana instructions. |

#### build_close_position_ix

```
build_close_position_ix(
    position: OrcaPosition,
) -> list[Instruction]
```

Build instructions for closing a position (burn NFT, recover rent).

Parameters:

| Name       | Type           | Description        | Default    |
| ---------- | -------------- | ------------------ | ---------- |
| `position` | `OrcaPosition` | Position to close. | *required* |

Returns:

| Type                | Description                  |
| ------------------- | ---------------------------- |
| `list[Instruction]` | List of Solana instructions. |

#### build_open_position_transaction

```
build_open_position_transaction(
    pool: OrcaPool,
    price_lower: float,
    price_upper: float,
    amount_a: int,
    amount_b: int,
    slippage_bps: int = 100,
) -> tuple[list[Instruction], Keypair, dict[str, Any]]
```

Build a complete open position transaction from price bounds.

Parameters:

| Name           | Type       | Description                                             | Default    |
| -------------- | ---------- | ------------------------------------------------------- | ---------- |
| `pool`         | `OrcaPool` | Pool information.                                       | *required* |
| `price_lower`  | `float`    | Lower price bound (token_b per token_a).                | *required* |
| `price_upper`  | `float`    | Upper price bound.                                      | *required* |
| `amount_a`     | `int`      | Amount of token A in smallest units.                    | *required* |
| `amount_b`     | `int`      | Amount of token B in smallest units.                    | *required* |
| `slippage_bps` | `int`      | Slippage tolerance in basis points (default: 100 = 1%). | `100`      |

Returns:

| Type                                                | Description                                          |
| --------------------------------------------------- | ---------------------------------------------------- |
| `tuple[list[Instruction], Keypair, dict[str, Any]]` | Tuple of (instructions, nft_mint_keypair, metadata). |

#### build_close_position_transaction

```
build_close_position_transaction(
    pool: OrcaPool,
    position: OrcaPosition,
    slippage_bps: int = 100,
) -> tuple[list[Instruction], dict[str, Any]]
```

Build instructions to fully close a position.

Decreases all liquidity, then closes the position account.

Parameters:

| Name           | Type           | Description                                | Default    |
| -------------- | -------------- | ------------------------------------------ | ---------- |
| `pool`         | `OrcaPool`     | Pool information.                          | *required* |
| `position`     | `OrcaPosition` | Position to close.                         | *required* |
| `slippage_bps` | `int`          | Slippage tolerance for decrease liquidity. | `100`      |

Returns:

| Type                                       | Description                            |
| ------------------------------------------ | -------------------------------------- |
| `tuple[list[Instruction], dict[str, Any]]` | Tuple of (all_instructions, metadata). |

# PancakeSwap V3

Connector for PancakeSwap V3 DEX.

## almanak.framework.connectors.pancakeswap_v3

PancakeSwap V3 Connector.

This module provides an adapter for interacting with PancakeSwap V3, which is a Uniswap V3 fork with different fee tiers and addresses.

PancakeSwap V3 is a decentralized exchange supporting:

- Exact input swaps (swap specific amount of input token)
- Exact output swaps (receive specific amount of output token)
- Multiple fee tiers (100, 500, 2500, 10000 bps)

Supported chains:

- BNB Smart Chain (BSC)
- Ethereum
- Arbitrum

Example

from almanak.framework.connectors.pancakeswap_v3 import ( PancakeSwapV3Adapter, PancakeSwapV3Config, )

config = PancakeSwapV3Config( chain="bnb", wallet_address="0x...", ) adapter = PancakeSwapV3Adapter(config)

### Swap exact input

result = adapter.swap_exact_input( token_in="USDT", token_out="WBNB", amount_in=Decimal("100"), )

### PancakeSwapV3Adapter

```
PancakeSwapV3Adapter(
    config: PancakeSwapV3Config,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for PancakeSwap V3 decentralized exchange.

This adapter provides methods for swapping tokens on PancakeSwap V3:

- Exact input swaps (specify input amount, receive variable output)
- Exact output swaps (specify output amount, send variable input)

PancakeSwap V3 is a Uniswap V3 fork with different fee tiers (100, 500, 2500, 10000 bps).

Example

config = PancakeSwapV3Config( chain="bnb", wallet_address="0x...", ) adapter = PancakeSwapV3Adapter(config)

#### Swap 100 USDT for WBNB

result = adapter.swap_exact_input("USDT", "WBNB", Decimal("100"))

Initialize the adapter.

Parameters:

| Name             | Type                  | Description           | Default                                                                             |
| ---------------- | --------------------- | --------------------- | ----------------------------------------------------------------------------------- |
| `config`         | `PancakeSwapV3Config` | Adapter configuration | *required*                                                                          |
| `token_resolver` | \`TokenResolver       | None\`                | Optional TokenResolver instance. If None, uses singleton from get_token_resolver(). |

#### swap_exact_input

```
swap_exact_input(
    token_in: str,
    token_out: str,
    amount_in: Decimal,
    amount_out_min: Decimal | None = None,
    fee_tier: int | None = None,
    recipient: str | None = None,
    deadline: int | None = None,
) -> TransactionResult
```

Build an exact input swap transaction.

Swaps a specific amount of input token for a variable amount of output token.

Parameters:

| Name             | Type      | Description                         | Default                                                   |
| ---------------- | --------- | ----------------------------------- | --------------------------------------------------------- |
| `token_in`       | `str`     | Input token symbol or address       | *required*                                                |
| `token_out`      | `str`     | Output token symbol or address      | *required*                                                |
| `amount_in`      | `Decimal` | Exact amount of input token to swap | *required*                                                |
| `amount_out_min` | \`Decimal | None\`                              | Minimum output amount (uses slippage if None)             |
| `fee_tier`       | \`int     | None\`                              | Pool fee tier in bps (default from config)                |
| `recipient`      | \`str     | None\`                              | Address to receive output (default: wallet_address)       |
| `deadline`       | \`int     | None\`                              | Transaction deadline timestamp (default: 20 min from now) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### swap_exact_output

```
swap_exact_output(
    token_in: str,
    token_out: str,
    amount_out: Decimal,
    amount_in_max: Decimal | None = None,
    fee_tier: int | None = None,
    recipient: str | None = None,
    deadline: int | None = None,
) -> TransactionResult
```

Build an exact output swap transaction.

Swaps a variable amount of input token for a specific amount of output token.

Parameters:

| Name            | Type      | Description                             | Default                                                   |
| --------------- | --------- | --------------------------------------- | --------------------------------------------------------- |
| `token_in`      | `str`     | Input token symbol or address           | *required*                                                |
| `token_out`     | `str`     | Output token symbol or address          | *required*                                                |
| `amount_out`    | `Decimal` | Exact amount of output token to receive | *required*                                                |
| `amount_in_max` | \`Decimal | None\`                                  | Maximum input amount (uses slippage if None)              |
| `fee_tier`      | \`int     | None\`                                  | Pool fee tier in bps (default from config)                |
| `recipient`     | \`str     | None\`                                  | Address to receive output (default: wallet_address)       |
| `deadline`      | \`int     | None\`                                  | Transaction deadline timestamp (default: 20 min from now) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

### PancakeSwapV3Config

```
PancakeSwapV3Config(
    chain: str,
    wallet_address: str,
    default_slippage_bps: int = 50,
    default_fee_tier: int = 100,
    price_provider: dict[str, Decimal] | None = None,
    allow_placeholder_prices: bool = False,
)
```

Configuration for PancakeSwap V3 adapter.

Attributes:

| Name                       | Type                 | Description                                                                                             |
| -------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------- |
| `chain`                    | `str`                | Blockchain network (bnb, ethereum, arbitrum)                                                            |
| `wallet_address`           | `str`                | User wallet address                                                                                     |
| `default_slippage_bps`     | `int`                | Default slippage tolerance in basis points                                                              |
| `default_fee_tier`         | `int`                | Default fee tier in basis points                                                                        |
| `price_provider`           | \`dict[str, Decimal] | None\`                                                                                                  |
| `allow_placeholder_prices` | `bool`               | If False (default), raises ValueError when no price_provider is given. Set to True ONLY for unit tests. |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

### TransactionResult

```
TransactionResult(
    success: bool,
    tx_data: dict[str, Any] | None = None,
    gas_estimate: int = 0,
    description: str = "",
    error: str | None = None,
)
```

Result of a transaction build operation.

Attributes:

| Name           | Type             | Description                 |
| -------------- | ---------------- | --------------------------- |
| `success`      | `bool`           | Whether operation succeeded |
| `tx_data`      | \`dict[str, Any] | None\`                      |
| `gas_estimate` | `int`            | Estimated gas               |
| `description`  | `str`            | Human-readable description  |
| `error`        | \`str            | None\`                      |

### PancakeSwapV3EventType

Bases: `Enum`

PancakeSwap V3 event types.

### PancakeSwapV3ReceiptParser

```
PancakeSwapV3ReceiptParser(
    chain: str = "bsc", **kwargs: Any
)
```

Bases: `BaseReceiptParser[SwapEventData, ParseResult]`

Parser for PancakeSwap V3 transaction receipts.

Uses base infrastructure for common parsing logic while handling PancakeSwap V3-specific event decoding.

Initialize the parser.

Parameters:

| Name    | Type  | Description                                              | Default |
| ------- | ----- | -------------------------------------------------------- | ------- |
| `chain` | `str` | Blockchain network (for position manager address lookup) | `'bsc'` |

#### extract_swap_amounts

```
extract_swap_amounts(
    receipt: dict[str, Any],
) -> SwapAmounts | None
```

Extract swap amounts from a transaction receipt.

Uses ERC-20 Transfer events to identify token addresses, then resolves actual decimals via the token resolver for accurate decimal conversion.

Parameters:

| Name      | Type             | Description                                            | Default    |
| --------- | ---------------- | ------------------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' and 'from' fields | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`SwapAmounts | None\`      |

#### extract_position_id

```
extract_position_id(receipt: dict[str, Any]) -> int | None
```

Extract LP position ID (NFT tokenId) from a transaction receipt.

Looks for ERC-721 Transfer events from the NonfungiblePositionManager where from=address(0), indicating a mint.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_tick_lower

```
extract_tick_lower(receipt: dict[str, Any]) -> int | None
```

Extract tick lower from LP mint transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_tick_upper

```
extract_tick_upper(receipt: dict[str, Any]) -> int | None
```

Extract tick upper from LP mint transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_liquidity

```
extract_liquidity(receipt: dict[str, Any]) -> int | None
```

Extract liquidity from LP mint transaction receipt.

Mint event data layout (non-indexed fields, 32-byte padded):

- sender (address): offset 0
- amount (uint128): offset 32
- amount0 (uint256): offset 64
- amount1 (uint256): offset 96

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> LPCloseData | None
```

Extract LP close data from transaction receipt.

Looks for Collect events which indicate fees and principal being collected.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`LPCloseData | None\`      |

#### parse_swap

```
parse_swap(log: dict[str, Any]) -> SwapEventData | None
```

Parse a Swap event from a single log entry.

Backward compatibility method.

#### is_pancakeswap_event

```
is_pancakeswap_event(topic: str | bytes) -> bool
```

Check if a topic is a known PancakeSwap V3 event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                                   |
| ------ | --------------------------------------------- |
| `bool` | True if topic is a known PancakeSwap V3 event |

#### get_event_type

```
get_event_type(
    topic: str | bytes,
) -> PancakeSwapV3EventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type                     | Description           |
| ------------------------ | --------------------- |
| `PancakeSwapV3EventType` | Event type or UNKNOWN |

### ParseResult

```
ParseResult(
    success: bool,
    swaps: list[SwapEventData] = list(),
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
)
```

Result of parsing a receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapEventData

```
SwapEventData(
    pool: str,
    sender: str,
    recipient: str,
    amount0: Decimal,
    amount1: Decimal,
    sqrt_price_x96: int = 0,
    liquidity: int = 0,
    tick: int = 0,
    protocol_fees_token0: int = 0,
    protocol_fees_token1: int = 0,
)
```

Parsed data from PancakeSwap V3 Swap event.

Note: PancakeSwap V3 Swap event has 9 parameters (vs 7 for Uniswap V3):

- sender, recipient (indexed)
- amount0, amount1, sqrtPriceX96, liquidity, tick (same as UniV3)
- protocolFeesToken0, protocolFeesToken1 (PancakeSwap V3 specific)

#### token0_in

```
token0_in: bool
```

Check if token0 is the input token.

#### token1_in

```
token1_in: bool
```

Check if token1 is the input token.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

# Pendle

Connector for Pendle yield trading protocol.

## almanak.framework.connectors.pendle

Pendle Protocol Connector

This module provides integration with Pendle Finance, a permissionless yield-trading protocol that enables:

- Tokenizing yield-bearing assets into PT (Principal) and YT (Yield) tokens
- Trading PT and YT on Pendle's AMM
- Providing liquidity to PT/SY pools
- Redeeming PT at maturity

Components:

- PendleSDK: Low-level protocol interactions
- PendleAdapter: ActionType to SDK mapping
- PendleReceiptParser: Transaction receipt parsing

Supported Chains:

- Arbitrum (primary)
- Ethereum

Example

from almanak.framework.connectors.pendle import ( PendleSDK, PendleAdapter, PendleReceiptParser, )

### Create SDK

sdk = PendleSDK(rpc_url="https://arb1.arbitrum.io/rpc", chain="arbitrum")

### Build swap transaction

tx = sdk.build_swap_exact_token_for_pt( receiver="0x...", market="0x...", token_in="0x...", amount_in=10**18, min_pt_out=10**18, )

### PendleAdapter

```
PendleAdapter(
    rpc_url: str,
    chain: str = "arbitrum",
    wallet_address: str | None = None,
    api_client: PendleAPIClient | None = None,
    on_chain_reader: PendleOnChainReader | None = None,
)
```

Adapter for Pendle Protocol operations.

This adapter translates between the framework's ActionType enum and Pendle's specific operations. It handles:

- Token swaps to/from PT (Principal Token)
- Token swaps to/from YT (Yield Token)
- Liquidity provision (adding/removing)
- PT/YT redemption at maturity

Example

adapter = PendleAdapter(rpc_url="https://arb1.arbitrum.io/rpc", chain="arbitrum")

#### Build a swap transaction

tx = adapter.build_swap( params=PendleSwapParams( market="0x...", token_in="0x...", token_out="0x...", amount_in=10**18, min_amount_out=10**18, receiver="0x...", swap_type="token_to_pt", ) )

Initialize the Pendle adapter.

Parameters:

| Name              | Type                  | Description                       | Default                                            |
| ----------------- | --------------------- | --------------------------------- | -------------------------------------------------- |
| `rpc_url`         | `str`                 | RPC endpoint URL                  | *required*                                         |
| `chain`           | `str`                 | Target chain (arbitrum, ethereum) | `'arbitrum'`                                       |
| `wallet_address`  | \`str                 | None\`                            | Optional default wallet address for transactions   |
| `api_client`      | \`PendleAPIClient     | None\`                            | Optional PendleAPIClient for REST API quotes       |
| `on_chain_reader` | \`PendleOnChainReader | None\`                            | Optional PendleOnChainReader for on-chain fallback |

#### supports_action

```
supports_action(action_type: ActionType) -> bool
```

Check if this adapter supports the given action type.

#### get_supported_actions

```
get_supported_actions() -> list[ActionType]
```

Get list of supported action types.

#### build_swap

```
build_swap(
    params: PendleSwapParams,
) -> PendleTransactionData
```

Build a swap transaction based on the swap type.

Parameters:

| Name     | Type               | Description                                           | Default    |
| -------- | ------------------ | ----------------------------------------------------- | ---------- |
| `params` | `PendleSwapParams` | Swap parameters including market, tokens, and amounts | *required* |

Returns:

| Type                    | Description                          |
| ----------------------- | ------------------------------------ |
| `PendleTransactionData` | Transaction data ready for execution |

#### build_swap_token_to_pt

```
build_swap_token_to_pt(
    market: str,
    token_in: str,
    amount_in: int,
    min_pt_out: int,
    receiver: str,
    slippage_bps: int = 50,
) -> PendleTransactionData
```

Build a token -> PT swap transaction.

This is a convenience method for the most common swap type.

Parameters:

| Name           | Type  | Description                        | Default    |
| -------------- | ----- | ---------------------------------- | ---------- |
| `market`       | `str` | Pendle market address              | *required* |
| `token_in`     | `str` | Input token address                | *required* |
| `amount_in`    | `int` | Amount of input token              | *required* |
| `min_pt_out`   | `int` | Minimum PT to receive              | *required* |
| `receiver`     | `str` | Address to receive PT              | *required* |
| `slippage_bps` | `int` | Slippage tolerance in basis points | `50`       |

Returns:

| Type                    | Description      |
| ----------------------- | ---------------- |
| `PendleTransactionData` | Transaction data |

#### build_swap_pt_to_token

```
build_swap_pt_to_token(
    market: str,
    pt_amount: int,
    token_out: str,
    min_token_out: int,
    receiver: str,
    slippage_bps: int = 50,
) -> PendleTransactionData
```

Build a PT -> token swap transaction.

Parameters:

| Name            | Type  | Description              | Default    |
| --------------- | ----- | ------------------------ | ---------- |
| `market`        | `str` | Pendle market address    | *required* |
| `pt_amount`     | `int` | Amount of PT to swap     | *required* |
| `token_out`     | `str` | Output token address     | *required* |
| `min_token_out` | `int` | Minimum token to receive | *required* |
| `receiver`      | `str` | Address to receive token | *required* |
| `slippage_bps`  | `int` | Slippage tolerance       | `50`       |

Returns:

| Type                    | Description      |
| ----------------------- | ---------------- |
| `PendleTransactionData` | Transaction data |

#### build_add_liquidity

```
build_add_liquidity(
    params: PendleLPParams,
) -> PendleTransactionData
```

Build an add liquidity transaction.

Parameters:

| Name     | Type             | Description          | Default    |
| -------- | ---------------- | -------------------- | ---------- |
| `params` | `PendleLPParams` | Liquidity parameters | *required* |

Returns:

| Type                    | Description      |
| ----------------------- | ---------------- |
| `PendleTransactionData` | Transaction data |

#### build_remove_liquidity

```
build_remove_liquidity(
    params: PendleLPParams,
) -> PendleTransactionData
```

Build a remove liquidity transaction.

Parameters:

| Name     | Type             | Description          | Default    |
| -------- | ---------------- | -------------------- | ---------- |
| `params` | `PendleLPParams` | Liquidity parameters | *required* |

Returns:

| Type                    | Description      |
| ----------------------- | ---------------- |
| `PendleTransactionData` | Transaction data |

#### build_redeem

```
build_redeem(
    params: PendleRedeemParams,
) -> PendleTransactionData
```

Build a PT+YT redemption transaction.

Parameters:

| Name     | Type                 | Description           | Default    |
| -------- | -------------------- | --------------------- | ---------- |
| `params` | `PendleRedeemParams` | Redemption parameters | *required* |

Returns:

| Type                    | Description      |
| ----------------------- | ---------------- |
| `PendleTransactionData` | Transaction data |

#### build_approve

```
build_approve(
    token_address: str, amount: int | None = None
) -> PendleTransactionData
```

Build an approval transaction for the Pendle Router.

Parameters:

| Name            | Type  | Description      | Default                             |
| --------------- | ----- | ---------------- | ----------------------------------- |
| `token_address` | `str` | Token to approve | *required*                          |
| `amount`        | \`int | None\`           | Amount to approve (defaults to max) |

Returns:

| Type                    | Description      |
| ----------------------- | ---------------- |
| `PendleTransactionData` | Transaction data |

#### get_router_address

```
get_router_address() -> str
```

Get the Pendle Router address for this chain.

#### get_gas_estimate

```
get_gas_estimate(action: PendleActionType) -> int
```

Get gas estimate for an action.

#### estimate_output

```
estimate_output(
    market: str,
    token_in: str,
    amount_in: int,
    swap_type: str,
    slippage_bps: int = 50,
) -> int
```

Estimate output amount for a swap using a 3-tier cascade:

1. Pendle REST API quote (most accurate)
1. On-chain RouterStatic rate (good fallback)
1. Conservative 1% haircut estimate (last resort, always logged as WARNING)

Parameters:

| Name           | Type  | Description                                       | Default    |
| -------------- | ----- | ------------------------------------------------- | ---------- |
| `market`       | `str` | Market address                                    | *required* |
| `token_in`     | `str` | Input token address                               | *required* |
| `amount_in`    | `int` | Input amount in wei                               | *required* |
| `swap_type`    | `str` | Type of swap ("token_to_pt", "pt_to_token", etc.) | *required* |
| `slippage_bps` | `int` | Slippage tolerance in basis points                | `50`       |

Returns:

| Type  | Description                    |
| ----- | ------------------------------ |
| `int` | Estimated output amount in wei |

### PendleLPParams

```
PendleLPParams(
    market: str,
    token: str,
    amount: int,
    min_amount: int,
    receiver: str,
    operation: str,
    slippage_bps: int = 50,
)
```

Parameters for Pendle liquidity operations.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PendleRedeemParams

```
PendleRedeemParams(
    yt_address: str,
    py_amount: int,
    token_out: str,
    min_token_out: int,
    receiver: str,
    slippage_bps: int = 50,
)
```

Parameters for Pendle redemption operations.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PendleSwapParams

```
PendleSwapParams(
    market: str,
    token_in: str,
    token_out: str,
    amount_in: int,
    min_amount_out: int,
    receiver: str,
    swap_type: str,
    slippage_bps: int = 50,
    token_mint_sy: str | None = None,
)
```

Parameters for Pendle swap operations.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### BurnEventData

```
BurnEventData(
    receiver: str,
    net_lp_burned: int,
    net_sy_out: int,
    net_pt_out: int,
    market_address: str,
)
```

Parsed data from Pendle Burn (LP removal) event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### MintEventData

```
MintEventData(
    receiver: str,
    net_lp_minted: int,
    net_sy_used: int,
    net_pt_used: int,
    market_address: str,
)
```

Parsed data from Pendle Mint (LP) event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### ParsedSwapResult

```
ParsedSwapResult(
    token_in: str,
    token_out: str,
    amount_in: int,
    amount_out: int,
    amount_in_decimal: Decimal,
    amount_out_decimal: Decimal,
    effective_price: Decimal,
    slippage_bps: int,
    market_address: str,
    swap_type: str,
)
```

High-level swap result from Pendle.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### ParseResult

```
ParseResult(
    success: bool,
    events: list[PendleEvent] = list(),
    swap_events: list[SwapEventData] = list(),
    mint_events: list[MintEventData] = list(),
    burn_events: list[BurnEventData] = list(),
    redeem_events: list[RedeemPYEventData] = list(),
    transfer_events: list[TransferEventData] = list(),
    swap_result: ParsedSwapResult | None = None,
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
    transaction_success: bool = True,
)
```

Result of parsing a Pendle receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PendleEvent

```
PendleEvent(
    event_type: PendleEventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)
```

Parsed Pendle event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PendleEventType

Bases: `Enum`

Pendle event types.

### PendleReceiptParser

```
PendleReceiptParser(
    chain: str = "arbitrum",
    token_in_decimals: int = 18,
    token_out_decimals: int = 18,
    quoted_price: Decimal | None = None,
    **kwargs: Any,
)
```

Parser for Pendle Protocol transaction receipts.

Uses the base infrastructure (EventRegistry, HexDecoder) for standardized event parsing while handling Pendle-specific event structures.

Example

parser = PendleReceiptParser(chain="arbitrum") result = parser.parse_receipt(receipt)

if result.success and result.swap_events: swap = result.swap_events[0] print(f"Swapped {swap.sy_amount} SY for {swap.pt_amount} PT")

Initialize the Pendle receipt parser.

Parameters:

| Name                 | Type      | Description                       | Default                                 |
| -------------------- | --------- | --------------------------------- | --------------------------------------- |
| `chain`              | `str`     | Chain name for address resolution | `'arbitrum'`                            |
| `token_in_decimals`  | `int`     | Decimals for input token          | `18`                                    |
| `token_out_decimals` | `int`     | Decimals for output token         | `18`                                    |
| `quoted_price`       | \`Decimal | None\`                            | Expected price for slippage calculation |

#### parse_receipt

```
parse_receipt(
    receipt: dict[str, Any],
    quoted_amount_out: int | None = None,
) -> ParseResult
```

Parse a Pendle transaction receipt.

Parameters:

| Name                | Type             | Description                    | Default                                  |
| ------------------- | ---------------- | ------------------------------ | ---------------------------------------- |
| `receipt`           | `dict[str, Any]` | Transaction receipt dictionary | *required*                               |
| `quoted_amount_out` | \`int            | None\`                         | Expected output for slippage calculation |

Returns:

| Type          | Description                                     |
| ------------- | ----------------------------------------------- |
| `ParseResult` | ParseResult with extracted events and swap data |

#### extract_swap_amounts

```
extract_swap_amounts(
    receipt: dict[str, Any],
) -> SwapAmounts | None
```

Extract swap amounts from receipt for Result Enrichment.

Called by the framework after SWAP execution to populate ExecutionResult.swap_amounts.

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`SwapAmounts | None\`      |

#### extract_lp_minted

```
extract_lp_minted(receipt: dict[str, Any]) -> int | None
```

Extract LP tokens minted from receipt.

Called by the framework after LP_OPEN execution.

#### extract_lp_burned

```
extract_lp_burned(receipt: dict[str, Any]) -> int | None
```

Extract LP tokens burned from receipt.

Called by the framework after LP_CLOSE execution.

#### extract_redemption_amounts

```
extract_redemption_amounts(
    receipt: dict[str, Any],
) -> dict[str, int] | None
```

Extract redemption amounts from receipt.

Called by the framework after WITHDRAW/REDEEM execution.

### RedeemPYEventData

```
RedeemPYEventData(
    caller: str,
    receiver: str,
    net_py_redeemed: int,
    net_sy_redeemed: int,
    yt_address: str,
)
```

Parsed data from Pendle RedeemPY event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapEventData

```
SwapEventData(
    caller: str,
    receiver: str,
    pt_to_account: int,
    sy_to_account: int,
    market_address: str,
)
```

Parsed data from Pendle Swap event.

#### is_buy_pt

```
is_buy_pt: bool
```

Check if this is a buy PT operation (SY -> PT).

#### is_sell_pt

```
is_sell_pt: bool
```

Check if this is a sell PT operation (PT -> SY).

#### pt_amount

```
pt_amount: int
```

Get absolute PT amount.

#### sy_amount

```
sy_amount: int
```

Get absolute SY amount.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### TransferEventData

```
TransferEventData(
    from_addr: str,
    to_addr: str,
    value: int,
    token_address: str,
)
```

Parsed data from ERC20 Transfer event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### LiquidityParams

```
LiquidityParams(
    receiver: str,
    market: str,
    token_in: str,
    amount_in: int,
    min_lp_out: int,
    slippage_bps: int = 50,
)
```

Parameters for liquidity operations.

### MarketInfo

```
MarketInfo(
    market_address: str,
    sy_address: str,
    pt_address: str,
    yt_address: str,
    expiry: int,
    underlying_token: str,
    underlying_symbol: str,
)
```

Information about a Pendle market.

#### is_expired

```
is_expired(current_timestamp: int) -> bool
```

Check if the market has expired.

### PendleActionType

Bases: `Enum`

Pendle action types.

### PendleQuote

```
PendleQuote(
    token_in: str,
    token_out: str,
    amount_in: int,
    amount_out: int,
    price_impact_bps: int,
    gas_estimate: int,
    effective_price: Decimal,
)
```

Quote for a Pendle operation.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PendleSDK

```
PendleSDK(
    rpc_url: str,
    chain: str = "arbitrum",
    token_resolver: TokenResolver | None = None,
)
```

SDK for interacting with Pendle Protocol.

Pendle enables yield tokenization and trading through its AMM. This SDK builds transactions for:

- Swapping tokens to/from PT (Principal Token)
- Swapping tokens to/from YT (Yield Token)
- Adding/removing liquidity
- Minting/redeeming SY and PY tokens

Example

sdk = PendleSDK(rpc_url="https://arb1.arbitrum.io/rpc", chain="arbitrum")

#### Build swap transaction (WETH -> PT-wstETH)

tx = sdk.build_swap_exact_token_for_pt( receiver="0x...", market="0x...", token_in="0x...", # WETH amount_in=10**18, # 1 WETH min_pt_out=10**18, # Minimum PT to receive )

Initialize Pendle SDK.

Parameters:

| Name             | Type            | Description                       | Default                                                   |
| ---------------- | --------------- | --------------------------------- | --------------------------------------------------------- |
| `rpc_url`        | `str`           | RPC endpoint URL                  | *required*                                                |
| `chain`          | `str`           | Target chain (arbitrum, ethereum) | `'arbitrum'`                                              |
| `token_resolver` | \`TokenResolver | None\`                            | Optional TokenResolver instance. If None, uses singleton. |

#### router_abi

```
router_abi: list[dict]
```

Load router ABI lazily.

#### erc20_abi

```
erc20_abi: list[dict]
```

Load ERC20 ABI lazily.

#### get_router

```
get_router() -> Contract
```

Get the Pendle Router contract.

#### build_swap_exact_token_for_pt

```
build_swap_exact_token_for_pt(
    receiver: str,
    market: str,
    token_in: str,
    amount_in: int,
    min_pt_out: int,
    slippage_bps: int = 50,
    token_mint_sy: str | None = None,
) -> PendleTransactionData
```

Build a swap transaction from token to PT using swapExactTokenForPtSimple.

This uses the simplified Pendle V4 function that doesn't require ApproxParams or LimitOrderData, making encoding more reliable.

Parameters:

| Name            | Type  | Description                        | Default                                                                                                                                                                         |
| --------------- | ----- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `receiver`      | `str` | Address to receive the PT          | *required*                                                                                                                                                                      |
| `market`        | `str` | Market address                     | *required*                                                                                                                                                                      |
| `token_in`      | `str` | Input token address                | *required*                                                                                                                                                                      |
| `amount_in`     | `int` | Amount of input token (in wei)     | *required*                                                                                                                                                                      |
| `min_pt_out`    | `int` | Minimum PT to receive              | *required*                                                                                                                                                                      |
| `slippage_bps`  | `int` | Slippage tolerance in basis points | `50`                                                                                                                                                                            |
| `token_mint_sy` | \`str | None\`                             | Token that mints SY (defaults to token_in if not specified). For yield-bearing token markets (like fUSDT0), this should be the yield-bearing token address, not the underlying. |

Returns:

| Type                    | Description                    |
| ----------------------- | ------------------------------ |
| `PendleTransactionData` | Transaction data for execution |

#### build_swap_exact_pt_for_token

```
build_swap_exact_pt_for_token(
    receiver: str,
    market: str,
    pt_amount: int,
    token_out: str,
    min_token_out: int,
    slippage_bps: int = 50,
    token_redeem_sy: str | None = None,
) -> PendleTransactionData
```

Build a swap transaction from PT to token using swapExactPtForToken.

Uses the full Pendle V4 function with empty LimitOrderData (the Simple variant does not exist on the deployed router).

Parameters:

| Name              | Type  | Description                        | Default                                                                                                                                                              |
| ----------------- | ----- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `receiver`        | `str` | Address to receive the token       | *required*                                                                                                                                                           |
| `market`          | `str` | Market address                     | *required*                                                                                                                                                           |
| `pt_amount`       | `int` | Amount of PT to swap               | *required*                                                                                                                                                           |
| `token_out`       | `str` | Output token address               | *required*                                                                                                                                                           |
| `min_token_out`   | `int` | Minimum output token to receive    | *required*                                                                                                                                                           |
| `slippage_bps`    | `int` | Slippage tolerance in basis points | `50`                                                                                                                                                                 |
| `token_redeem_sy` | \`str | None\`                             | Token that redeems SY (defaults to token_out if not specified). For yield-bearing token markets, this should be the yield-bearing token address, not the underlying. |

Returns:

| Type                    | Description                    |
| ----------------------- | ------------------------------ |
| `PendleTransactionData` | Transaction data for execution |

#### build_swap_exact_token_for_yt

```
build_swap_exact_token_for_yt(
    receiver: str,
    market: str,
    token_in: str,
    amount_in: int,
    min_yt_out: int,
    slippage_bps: int = 50,
    token_mint_sy: str | None = None,
) -> PendleTransactionData
```

Build a swap transaction from token to YT using swapExactTokenForYt.

Unlike PT swaps which use the Simple variant, YT swaps require ApproxParams for binary search of optimal flash swap size, plus LimitOrderData.

Parameters:

| Name            | Type  | Description                        | Default                                    |
| --------------- | ----- | ---------------------------------- | ------------------------------------------ |
| `receiver`      | `str` | Address to receive the YT          | *required*                                 |
| `market`        | `str` | Market address                     | *required*                                 |
| `token_in`      | `str` | Input token address                | *required*                                 |
| `amount_in`     | `int` | Amount of input token (in wei)     | *required*                                 |
| `min_yt_out`    | `int` | Minimum YT to receive              | *required*                                 |
| `slippage_bps`  | `int` | Slippage tolerance in basis points | `50`                                       |
| `token_mint_sy` | \`str | None\`                             | Token that mints SY (defaults to token_in) |

Returns:

| Type                    | Description                    |
| ----------------------- | ------------------------------ |
| `PendleTransactionData` | Transaction data for execution |

#### build_swap_exact_yt_for_token

```
build_swap_exact_yt_for_token(
    receiver: str,
    market: str,
    yt_amount: int,
    token_out: str,
    min_token_out: int,
    slippage_bps: int = 50,
) -> PendleTransactionData
```

Build a swap transaction from YT to token using swapExactYtForToken.

Parameters:

| Name            | Type  | Description                        | Default    |
| --------------- | ----- | ---------------------------------- | ---------- |
| `receiver`      | `str` | Address to receive the token       | *required* |
| `market`        | `str` | Market address                     | *required* |
| `yt_amount`     | `int` | Amount of YT to swap               | *required* |
| `token_out`     | `str` | Output token address               | *required* |
| `min_token_out` | `int` | Minimum output token to receive    | *required* |
| `slippage_bps`  | `int` | Slippage tolerance in basis points | `50`       |

Returns:

| Type                    | Description                    |
| ----------------------- | ------------------------------ |
| `PendleTransactionData` | Transaction data for execution |

#### build_add_liquidity_single_token

```
build_add_liquidity_single_token(
    receiver: str,
    market: str,
    token_in: str,
    amount_in: int,
    min_lp_out: int,
    slippage_bps: int = 50,
) -> PendleTransactionData
```

Build a transaction to add liquidity with a single token.

This adds liquidity to a Pendle market using a single input token. The router handles conversion to the proper ratio of SY and PT.

Parameters:

| Name           | Type  | Description                        | Default    |
| -------------- | ----- | ---------------------------------- | ---------- |
| `receiver`     | `str` | Address to receive LP tokens       | *required* |
| `market`       | `str` | Market address                     | *required* |
| `token_in`     | `str` | Input token address                | *required* |
| `amount_in`    | `int` | Amount of input token              | *required* |
| `min_lp_out`   | `int` | Minimum LP tokens to receive       | *required* |
| `slippage_bps` | `int` | Slippage tolerance in basis points | `50`       |

Returns:

| Type                    | Description                    |
| ----------------------- | ------------------------------ |
| `PendleTransactionData` | Transaction data for execution |

#### build_remove_liquidity_single_token

```
build_remove_liquidity_single_token(
    receiver: str,
    market: str,
    lp_amount: int,
    token_out: str,
    min_token_out: int,
    slippage_bps: int = 50,
) -> PendleTransactionData
```

Build a transaction to remove liquidity to a single token.

Parameters:

| Name            | Type  | Description                        | Default    |
| --------------- | ----- | ---------------------------------- | ---------- |
| `receiver`      | `str` | Address to receive output token    | *required* |
| `market`        | `str` | Market address                     | *required* |
| `lp_amount`     | `int` | Amount of LP tokens to burn        | *required* |
| `token_out`     | `str` | Output token address               | *required* |
| `min_token_out` | `int` | Minimum output token to receive    | *required* |
| `slippage_bps`  | `int` | Slippage tolerance in basis points | `50`       |

Returns:

| Type                    | Description                    |
| ----------------------- | ------------------------------ |
| `PendleTransactionData` | Transaction data for execution |

#### build_redeem_py_to_token

```
build_redeem_py_to_token(
    receiver: str,
    yt_address: str,
    py_amount: int,
    token_out: str,
    min_token_out: int,
    slippage_bps: int = 50,
) -> PendleTransactionData
```

Build a transaction to redeem PT+YT to token.

After maturity, PT can be redeemed 1:1 for the underlying. Before maturity, you need equal amounts of PT and YT to redeem.

Parameters:

| Name            | Type  | Description                     | Default    |
| --------------- | ----- | ------------------------------- | ---------- |
| `receiver`      | `str` | Address to receive output token | *required* |
| `yt_address`    | `str` | YT contract address             | *required* |
| `py_amount`     | `int` | Amount of PT+YT to redeem       | *required* |
| `token_out`     | `str` | Output token address            | *required* |
| `min_token_out` | `int` | Minimum output token            | *required* |
| `slippage_bps`  | `int` | Slippage tolerance              | `50`       |

Returns:

| Type                    | Description                    |
| ----------------------- | ------------------------------ |
| `PendleTransactionData` | Transaction data for execution |

#### build_approve_tx

```
build_approve_tx(
    token_address: str,
    spender: str | None = None,
    amount: int = MAX_UINT256,
) -> PendleTransactionData
```

Build an ERC-20 approval transaction.

Parameters:

| Name            | Type  | Description                         | Default                              |
| --------------- | ----- | ----------------------------------- | ------------------------------------ |
| `token_address` | `str` | Token to approve                    | *required*                           |
| `spender`       | \`str | None\`                              | Spender address (defaults to router) |
| `amount`        | `int` | Amount to approve (defaults to max) | `MAX_UINT256`                        |

Returns:

| Type                    | Description                    |
| ----------------------- | ------------------------------ |
| `PendleTransactionData` | Transaction data for execution |

### PendleTransactionData

```
PendleTransactionData(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
    action_type: PendleActionType,
)
```

Transaction data for Pendle operations.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapParams

```
SwapParams(
    receiver: str,
    market: str,
    min_out: int,
    token_in: str,
    amount_in: int,
    slippage_bps: int = 50,
)
```

Parameters for a swap operation.

#### amount_out_minimum

```
amount_out_minimum: int
```

Calculate minimum output with slippage.

### get_pendle_adapter

```
get_pendle_adapter(
    rpc_url: str,
    chain: str = "arbitrum",
    wallet_address: str | None = None,
) -> PendleAdapter
```

Factory function to create a PendleAdapter instance.

### get_pendle_sdk

```
get_pendle_sdk(
    rpc_url: str, chain: str = "arbitrum"
) -> PendleSDK
```

Factory function to create a PendleSDK instance.

# Polymarket

Connector for Polymarket prediction market protocol.

## almanak.framework.connectors.polymarket

Polymarket prediction market connector.

This module provides integration with Polymarket's hybrid CLOB + on-chain architecture for prediction market trading.

Architecture Overview

- CLOB (Central Limit Order Book): Off-chain order matching via API
- CTF (Conditional Token Framework): On-chain ERC-1155 outcome tokens
- Market Making: Specialized utilities for automated market making
- Signals: Integration with news, social sentiment, and prediction models

Export Organization

This connector exports 103 items across 14 categories due to Polymarket's unique hybrid architecture. See notes/tech-debt/polymarket-export-list.md for detailed analysis and justification.

Major categories:

- Clients & SDK: Core client classes (ClobClient, CtfSDK, PolymarketSDK)
- Adapter & Results: Adapter interface and transaction results
- Constants: Contract addresses, EIP-712 domains, CTF SDK constants
- Configuration: Credentials and config management
- Enums: Order types, statuses, signature types
- Market Data: Market info, order books, prices
- Orders: Order parameters and state transitions
- Positions & Trades: Position tracking and trade history
- Filters: Query filters for markets, orders, trades
- Historical Data: Price history and historical trades
- Receipt Parser: Event parsing for on-chain transactions
- Market Making: Quote generation and risk management
- Signals: Prediction signal providers and aggregation
- Exceptions: Comprehensive error hierarchy

Example

from almanak.framework.connectors.polymarket import ( ClobClient, PolymarketConfig, MarketFilters, )

### Initialize client

config = PolymarketConfig.from_env() client = ClobClient(config)

### Fetch markets

markets = client.get_markets(MarketFilters(active=True, limit=10)) for market in markets: print(f"{market.question}: YES={market.yes_price}, NO={market.no_price}")

### OrderResult

```
OrderResult(
    success: bool,
    order_id: str | None = None,
    signed_order_payload: dict[str, Any] | None = None,
    error: str | None = None,
    token_id: str | None = None,
    side: str | None = None,
    price: Decimal | None = None,
    size: Decimal | None = None,
)
```

Result of building and signing an order.

Attributes:

| Name                   | Type             | Description                              |
| ---------------------- | ---------------- | ---------------------------------------- |
| `success`              | `bool`           | Whether the order was built successfully |
| `order_id`             | \`str            | None\`                                   |
| `signed_order_payload` | \`dict[str, Any] | None\`                                   |
| `error`                | \`str            | None\`                                   |

### PolymarketAdapter

```
PolymarketAdapter(
    config: PolymarketConfig, web3: Any | None = None
)
```

Adapter for compiling prediction market intents to ActionBundles.

This adapter integrates with the Almanak Strategy Framework to compile high-level prediction market intents into executable orders and transactions.

The compilation process:

1. Resolve market_id to a GammaMarket (supports ID or slug)
1. Determine token_id based on outcome (YES or NO)
1. Build order/transaction based on intent type
1. Return ActionBundle for execution

Attributes:

| Name     | Type | Description                                    |
| -------- | ---- | ---------------------------------------------- |
| `config` |      | Polymarket configuration                       |
| `clob`   |      | CLOB API client for order management           |
| `ctf`    |      | CTF SDK for on-chain operations                |
| `web3`   |      | Optional Web3 instance for on-chain operations |

Example

> > > config = PolymarketConfig.from_env() adapter = PolymarketAdapter(config)
> > >
> > > intent = Intent.prediction_buy( ... market_id="btc-100k", ... outcome="YES", ... amount_usd=Decimal("100"), ... ) bundle = adapter.compile_intent(intent, market_snapshot)

Initialize the Polymarket adapter.

Parameters:

| Name     | Type               | Description                                          | Default                                                                      |
| -------- | ------------------ | ---------------------------------------------------- | ---------------------------------------------------------------------------- |
| `config` | `PolymarketConfig` | Polymarket configuration with wallet and credentials | *required*                                                                   |
| `web3`   | \`Any              | None\`                                               | Optional Web3 instance for on-chain operations. Required for redeem intents. |

#### close

```
close() -> None
```

Close adapter and release resources.

#### compile_intent

```
compile_intent(
    intent: PredictionBuyIntent
    | PredictionSellIntent
    | PredictionRedeemIntent,
    market_snapshot: MarketSnapshot | None = None,
) -> ActionBundle
```

Compile a prediction market intent to an ActionBundle.

This is the main entry point for intent compilation. It dispatches to the appropriate handler based on intent type.

Parameters:

| Name              | Type                  | Description          | Default                                         |
| ----------------- | --------------------- | -------------------- | ----------------------------------------------- |
| `intent`          | \`PredictionBuyIntent | PredictionSellIntent | PredictionRedeemIntent\`                        |
| `market_snapshot` | \`MarketSnapshot      | None\`               | Optional market snapshot for additional context |

Returns:

| Type           | Description                                              |
| -------------- | -------------------------------------------------------- |
| `ActionBundle` | ActionBundle containing the compiled orders/transactions |

Raises:

| Type         | Description                     |
| ------------ | ------------------------------- |
| `ValueError` | If intent type is not supported |

### RedeemResult

```
RedeemResult(
    success: bool,
    transactions: list[dict[str, Any]] = list(),
    error: str | None = None,
    condition_id: str | None = None,
    outcome: str | None = None,
    amount: Decimal | None = None,
)
```

Result of building a redeem transaction.

Attributes:

| Name           | Type                   | Description                                    |
| -------------- | ---------------------- | ---------------------------------------------- |
| `success`      | `bool`                 | Whether the transaction was built successfully |
| `transactions` | `list[dict[str, Any]]` | List of transaction data dicts                 |
| `error`        | \`str                  | None\`                                         |

### ClobClient

```
ClobClient(
    config: PolymarketConfig,
    http_client: Client | None = None,
    rate_limiter: TokenBucketRateLimiter | None = None,
)
```

Client for Polymarket CLOB API.

Handles both L1 (EIP-712) and L2 (HMAC) authentication and provides methods for market data and order management.

Attributes:

| Name          | Type | Description                                 |
| ------------- | ---- | ------------------------------------------- |
| `config`      |      | Polymarket configuration                    |
| `credentials` |      | API credentials (may be None until created) |

Thread Safety

This class is NOT thread-safe. Use separate instances per thread.

Initialize CLOB client.

Parameters:

| Name           | Type                     | Description                                   | Default                                                                                   |
| -------------- | ------------------------ | --------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `config`       | `PolymarketConfig`       | Polymarket configuration with wallet and keys | *required*                                                                                |
| `http_client`  | \`Client                 | None\`                                        | Optional HTTP client for testing                                                          |
| `rate_limiter` | \`TokenBucketRateLimiter | None\`                                        | Optional rate limiter for testing. If not provided, creates one based on config settings. |

#### rate_limiter

```
rate_limiter: TokenBucketRateLimiter
```

Access the rate limiter for configuration or testing.

#### close

```
close() -> None
```

Close HTTP client.

#### create_api_credentials

```
create_api_credentials() -> ApiCredentials
```

Create new API credentials using L1 authentication.

This signs an EIP-712 message to prove wallet ownership and creates new API credentials for L2 authentication.

Returns:

| Type             | Description                                         |
| ---------------- | --------------------------------------------------- |
| `ApiCredentials` | ApiCredentials with api_key, secret, and passphrase |

Raises:

| Type                            | Description                  |
| ------------------------------- | ---------------------------- |
| `PolymarketAuthenticationError` | If credential creation fails |

#### derive_api_credentials

```
derive_api_credentials() -> ApiCredentials
```

Derive existing API credentials using L1 authentication.

If credentials were previously created, this retrieves them using wallet signature.

Returns:

| Type             | Description                                         |
| ---------------- | --------------------------------------------------- |
| `ApiCredentials` | ApiCredentials with api_key, secret, and passphrase |

Raises:

| Type                            | Description                    |
| ------------------------------- | ------------------------------ |
| `PolymarketAuthenticationError` | If credential derivation fails |

#### get_or_create_credentials

```
get_or_create_credentials() -> ApiCredentials
```

Get existing credentials or create new ones.

First attempts to derive existing credentials. If that fails, creates new ones.

Returns:

| Type             | Description    |
| ---------------- | -------------- |
| `ApiCredentials` | ApiCredentials |

Raises:

| Type                            | Description             |
| ------------------------------- | ----------------------- |
| `PolymarketAuthenticationError` | If both operations fail |

#### health_check

```
health_check() -> bool
```

Check if CLOB API is healthy.

Returns:

| Type   | Description               |
| ------ | ------------------------- |
| `bool` | True if API is responding |

#### get_server_time

```
get_server_time() -> int
```

Get server timestamp.

Returns:

| Type  | Description                |
| ----- | -------------------------- |
| `int` | Unix timestamp from server |

#### get_markets

```
get_markets(
    filters: MarketFilters | None = None,
) -> list[GammaMarket]
```

Get list of markets from Gamma API.

Parameters:

| Name      | Type            | Description | Default                        |
| --------- | --------------- | ----------- | ------------------------------ |
| `filters` | \`MarketFilters | None\`      | Optional filters for the query |

Returns:

| Type                | Description                 |
| ------------------- | --------------------------- |
| `list[GammaMarket]` | List of GammaMarket objects |

#### get_market

```
get_market(market_id: str) -> GammaMarket
```

Get single market by ID.

Parameters:

| Name        | Type  | Description | Default    |
| ----------- | ----- | ----------- | ---------- |
| `market_id` | `str` | Market ID   | *required* |

Returns:

| Type          | Description        |
| ------------- | ------------------ |
| `GammaMarket` | GammaMarket object |

Raises:

| Type                 | Description         |
| -------------------- | ------------------- |
| `PolymarketAPIError` | If market not found |

#### get_market_by_slug

```
get_market_by_slug(slug: str) -> GammaMarket | None
```

Get market by URL slug.

Parameters:

| Name   | Type  | Description     | Default    |
| ------ | ----- | --------------- | ---------- |
| `slug` | `str` | Market URL slug | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`GammaMarket | None\`      |

#### get_orderbook

```
get_orderbook(token_id: str) -> OrderBook
```

Get orderbook for a token.

Parameters:

| Name       | Type  | Description               | Default    |
| ---------- | ----- | ------------------------- | ---------- |
| `token_id` | `str` | CLOB token ID (YES or NO) | *required* |

Returns:

| Type        | Description                  |
| ----------- | ---------------------------- |
| `OrderBook` | OrderBook with bids and asks |

#### get_price

```
get_price(token_id: str) -> TokenPrice
```

Get price for a token.

Parameters:

| Name       | Type  | Description   | Default    |
| ---------- | ----- | ------------- | ---------- |
| `token_id` | `str` | CLOB token ID | *required* |

Returns:

| Type         | Description                       |
| ------------ | --------------------------------- |
| `TokenPrice` | TokenPrice with bid, ask, and mid |

#### get_midpoint

```
get_midpoint(token_id: str) -> Decimal
```

Get midpoint price for a token.

Parameters:

| Name       | Type  | Description   | Default    |
| ---------- | ----- | ------------- | ---------- |
| `token_id` | `str` | CLOB token ID | *required* |

Returns:

| Type      | Description    |
| --------- | -------------- |
| `Decimal` | Midpoint price |

#### get_tick_size

```
get_tick_size(token_id: str) -> Decimal
```

Get minimum tick size for a token.

Parameters:

| Name       | Type  | Description   | Default    |
| ---------- | ----- | ------------- | ---------- |
| `token_id` | `str` | CLOB token ID | *required* |

Returns:

| Type      | Description       |
| --------- | ----------------- |
| `Decimal` | Minimum tick size |

#### get_balance_allowance

```
get_balance_allowance(
    asset_type: str = "COLLATERAL",
    token_id: str | None = None,
) -> BalanceAllowance
```

Get balance and allowance.

Parameters:

| Name         | Type  | Description                                                | Default                             |
| ------------ | ----- | ---------------------------------------------------------- | ----------------------------------- |
| `asset_type` | `str` | "COLLATERAL" for USDC or "CONDITIONAL" for position tokens | `'COLLATERAL'`                      |
| `token_id`   | \`str | None\`                                                     | Token ID (required for CONDITIONAL) |

Returns:

| Type               | Description                                         |
| ------------------ | --------------------------------------------------- |
| `BalanceAllowance` | BalanceAllowance with current balance and allowance |

#### round_price_to_tick

```
round_price_to_tick(
    price: Decimal,
    side: str,
    market: GammaMarket | None = None,
    tick_size: Decimal | None = None,
) -> Decimal
```

Round price to valid tick size for the market.

Public method for rounding prices before order submission. Use this when you want automatic rounding instead of validation errors.

Parameters:

| Name        | Type          | Description                  | Default                                            |
| ----------- | ------------- | ---------------------------- | -------------------------------------------------- |
| `price`     | `Decimal`     | Price to round               | *required*                                         |
| `side`      | `str`         | Order side ("BUY" or "SELL") | *required*                                         |
| `market`    | \`GammaMarket | None\`                       | Optional GammaMarket for market-specific tick size |
| `tick_size` | \`Decimal     | None\`                       | Optional explicit tick size (overrides market)     |

Returns:

| Type      | Description                         |
| --------- | ----------------------------------- |
| `Decimal` | Price rounded to nearest valid tick |

Example

> > > market = client.get_market(market_id) rounded = client.round_price_to_tick(Decimal("0.655"), "BUY", market=market)

#### build_limit_order

```
build_limit_order(
    params: LimitOrderParams,
    market: GammaMarket | None = None,
) -> UnsignedOrder
```

Build an unsigned limit order.

Limit orders specify exact price and size. They remain on the orderbook until filled, cancelled, or expired.

For BUY orders

- You spend USDC (maker_amount = size * price)
- You receive shares (taker_amount = size)

For SELL orders

- You spend shares (maker_amount = size)
- You receive USDC (taker_amount = size * price)

Parameters:

| Name     | Type               | Description            | Default                                                                                                                                                                             |
| -------- | ------------------ | ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `params` | `LimitOrderParams` | Limit order parameters | *required*                                                                                                                                                                          |
| `market` | \`GammaMarket      | None\`                 | Optional GammaMarket metadata for market-specific validation. If provided, uses market.order_min_size for size validation and market.order_price_min_tick_size for tick validation. |

Returns:

| Type            | Description                     |
| --------------- | ------------------------------- |
| `UnsignedOrder` | UnsignedOrder ready for signing |

Raises:

| Type                             | Description                           |
| -------------------------------- | ------------------------------------- |
| `PolymarketInvalidPriceError`    | If price is out of range (0.01-0.99)  |
| `PolymarketInvalidTickSizeError` | If price is not a valid tick multiple |
| `PolymarketMinimumOrderError`    | If size is below market minimum       |

Example

> > > params = LimitOrderParams( ... token_id="123...", ... side="BUY", ... price=Decimal("0.65"), ... size=Decimal("100"), ... ) order = client.build_limit_order(params)
> > >
> > > ##### With market metadata for proper minimum and tick validation
> > >
> > > market = client.get_market(market_id) order = client.build_limit_order(params, market=market)

#### build_market_order

```
build_market_order(
    params: MarketOrderParams,
    market: GammaMarket | None = None,
) -> UnsignedOrder
```

Build an unsigned market order.

Market orders execute immediately at the best available price. They should be submitted with IOC (Immediate or Cancel) order type.

For BUY orders

- You specify USDC amount to spend
- worst_price sets the maximum price per share

For SELL orders

- You specify number of shares to sell
- worst_price sets the minimum price per share

Parameters:

| Name     | Type                | Description             | Default                                                                                                                                                                             |
| -------- | ------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `params` | `MarketOrderParams` | Market order parameters | *required*                                                                                                                                                                          |
| `market` | \`GammaMarket       | None\`                  | Optional GammaMarket metadata for market-specific validation. If provided, uses market.order_min_size for size validation and market.order_price_min_tick_size for tick validation. |

Returns:

| Type            | Description                     |
| --------------- | ------------------------------- |
| `UnsignedOrder` | UnsignedOrder ready for signing |

Raises:

| Type                             | Description                                 |
| -------------------------------- | ------------------------------------------- |
| `PolymarketInvalidPriceError`    | If worst_price is out of range (0.01-0.99)  |
| `PolymarketInvalidTickSizeError` | If worst_price is not a valid tick multiple |
| `PolymarketMinimumOrderError`    | If amount is below market minimum           |

Example

> > > params = MarketOrderParams( ... token_id="123...", ... side="BUY", ... amount=Decimal("100"), # USDC to spend ... worst_price=Decimal("0.70"), # Max price per share ... ) order = client.build_market_order(params)
> > >
> > > ##### With market metadata for proper minimum and tick validation
> > >
> > > market = client.get_market(market_id) order = client.build_market_order(params, market=market)

#### sign_order

```
sign_order(order: UnsignedOrder) -> SignedOrder
```

Sign an order using EIP-712 typed data signing.

This creates a cryptographic signature that proves the order was created by the wallet owner.

Parameters:

| Name    | Type            | Description            | Default    |
| ------- | --------------- | ---------------------- | ---------- |
| `order` | `UnsignedOrder` | Unsigned order to sign | *required* |

Returns:

| Type          | Description                         |
| ------------- | ----------------------------------- |
| `SignedOrder` | SignedOrder with signature attached |

Example

> > > unsigned = client.build_limit_order(params) signed = client.sign_order(unsigned) response = client.submit_order(signed)

#### create_and_sign_limit_order

```
create_and_sign_limit_order(
    params: LimitOrderParams,
    market: GammaMarket | None = None,
) -> SignedOrder
```

Build and sign a limit order in one call.

Convenience method that combines build_limit_order and sign_order.

Parameters:

| Name     | Type               | Description            | Default                                                      |
| -------- | ------------------ | ---------------------- | ------------------------------------------------------------ |
| `params` | `LimitOrderParams` | Limit order parameters | *required*                                                   |
| `market` | \`GammaMarket      | None\`                 | Optional GammaMarket metadata for market-specific validation |

Returns:

| Type          | Description                      |
| ------------- | -------------------------------- |
| `SignedOrder` | SignedOrder ready for submission |

#### create_and_sign_market_order

```
create_and_sign_market_order(
    params: MarketOrderParams,
    market: GammaMarket | None = None,
) -> SignedOrder
```

Build and sign a market order in one call.

Convenience method that combines build_market_order and sign_order.

Parameters:

| Name     | Type                | Description             | Default                                                      |
| -------- | ------------------- | ----------------------- | ------------------------------------------------------------ |
| `params` | `MarketOrderParams` | Market order parameters | *required*                                                   |
| `market` | \`GammaMarket       | None\`                  | Optional GammaMarket metadata for market-specific validation |

Returns:

| Type          | Description                      |
| ------------- | -------------------------------- |
| `SignedOrder` | SignedOrder ready for submission |

#### submit_order

```
submit_order(
    order: SignedOrder,
    order_type: OrderType = OrderType.GTC,
) -> OrderResponse
```

Submit a signed order.

Parameters:

| Name         | Type          | Description                | Default    |
| ------------ | ------------- | -------------------------- | ---------- |
| `order`      | `SignedOrder` | Signed order to submit     | *required* |
| `order_type` | `OrderType`   | Order type (GTC, IOC, FOK) | `GTC`      |

Returns:

| Type            | Description                            |
| --------------- | -------------------------------------- |
| `OrderResponse` | OrderResponse with order ID and status |

#### submit_order_payload

```
submit_order_payload(
    payload: dict[str, Any],
) -> OrderResponse
```

Submit an order from a pre-built payload dict.

This method is used by ClobActionHandler to submit orders from ActionBundle metadata where the payload is already prepared.

Parameters:

| Name      | Type             | Description                                                         | Default    |
| --------- | ---------------- | ------------------------------------------------------------------- | ---------- |
| `payload` | `dict[str, Any]` | Order payload dict containing 'order', 'signature', and 'orderType' | *required* |

Returns:

| Type            | Description                            |
| --------------- | -------------------------------------- |
| `OrderResponse` | OrderResponse with order ID and status |

#### get_order

```
get_order(order_id: str) -> OpenOrder | None
```

Get a single order by ID.

Parameters:

| Name       | Type  | Description          | Default    |
| ---------- | ----- | -------------------- | ---------- |
| `order_id` | `str` | Order ID to retrieve | *required* |

Returns:

| Type        | Description |
| ----------- | ----------- |
| \`OpenOrder | None\`      |

#### cancel_order

```
cancel_order(order_id: str) -> bool
```

Cancel an order by ID.

Parameters:

| Name       | Type  | Description        | Default    |
| ---------- | ----- | ------------------ | ---------- |
| `order_id` | `str` | Order ID to cancel | *required* |

Returns:

| Type   | Description                    |
| ------ | ------------------------------ |
| `bool` | True if cancelled successfully |

#### cancel_orders

```
cancel_orders(order_ids: list[str]) -> bool
```

Cancel multiple orders.

Parameters:

| Name        | Type        | Description                 | Default    |
| ----------- | ----------- | --------------------------- | ---------- |
| `order_ids` | `list[str]` | List of order IDs to cancel | *required* |

Returns:

| Type   | Description                    |
| ------ | ------------------------------ |
| `bool` | True if cancelled successfully |

#### cancel_all_orders

```
cancel_all_orders() -> bool
```

Cancel all open orders.

Returns:

| Type   | Description                    |
| ------ | ------------------------------ |
| `bool` | True if cancelled successfully |

#### get_open_orders

```
get_open_orders(
    filters: OrderFilters | None = None,
) -> list[OpenOrder]
```

Get open orders.

Parameters:

| Name      | Type           | Description | Default          |
| --------- | -------------- | ----------- | ---------------- |
| `filters` | \`OrderFilters | None\`      | Optional filters |

Returns:

| Type              | Description         |
| ----------------- | ------------------- |
| `list[OpenOrder]` | List of open orders |

#### get_trades

```
get_trades(
    filters: TradeFilters | None = None,
) -> list[Trade]
```

Get trade history.

Parameters:

| Name      | Type           | Description | Default          |
| --------- | -------------- | ----------- | ---------------- |
| `filters` | \`TradeFilters | None\`      | Optional filters |

Returns:

| Type          | Description    |
| ------------- | -------------- |
| `list[Trade]` | List of trades |

#### get_positions

```
get_positions(
    wallet: str | None = None,
    filters: PositionFilters | None = None,
) -> list[Position]
```

Get positions for a wallet.

Queries the Polymarket Data API to retrieve all open prediction market positions for the specified wallet.

Parameters:

| Name      | Type              | Description | Default                                                              |
| --------- | ----------------- | ----------- | -------------------------------------------------------------------- |
| `wallet`  | \`str             | None\`      | Wallet address to query. Defaults to config wallet if not specified. |
| `filters` | \`PositionFilters | None\`      | Optional filters for market or outcome                               |

Returns:

| Type             | Description                                              |
| ---------------- | -------------------------------------------------------- |
| `list[Position]` | List of Position objects with size, prices, and PnL data |

Example

> > > positions = client.get_positions() for pos in positions: ... print(f"{pos.outcome}: {pos.size} shares at {pos.avg_price}")

#### get_price_history

```
get_price_history(
    token_id: str,
    interval: str | PriceHistoryInterval | None = None,
    start_ts: int | None = None,
    end_ts: int | None = None,
    fidelity: int | None = None,
) -> PriceHistory
```

Get historical price data for a token.

Fetches time-series price data from the CLOB API. Can query by predefined interval or custom time range.

Parameters:

| Name       | Type  | Description                       | Default                                                                                     |
| ---------- | ----- | --------------------------------- | ------------------------------------------------------------------------------------------- |
| `token_id` | `str` | CLOB token ID (YES or NO outcome) | *required*                                                                                  |
| `interval` | \`str | PriceHistoryInterval              | None\`                                                                                      |
| `start_ts` | \`int | None\`                            | Unix timestamp for start of range (UTC). Requires end_ts. Mutually exclusive with interval. |
| `end_ts`   | \`int | None\`                            | Unix timestamp for end of range (UTC). Requires start_ts. Mutually exclusive with interval. |
| `fidelity` | \`int | None\`                            | Data resolution in minutes (e.g., 1, 5, 15, 60). Optional for both modes.                   |

Returns:

| Type           | Description                                  |
| -------------- | -------------------------------------------- |
| `PriceHistory` | PriceHistory with list of timestamped prices |

Raises:

| Type                 | Description                                       |
| -------------------- | ------------------------------------------------- |
| `PolymarketAPIError` | If request fails                                  |
| `ValueError`         | If both interval and start_ts/end_ts are provided |

Example

> > > ##### Get last 24 hours
> > >
> > > history = client.get_price_history(token_id, interval="1d") print(f"Open: {history.open_price}, Close: {history.close_price}")
> > >
> > > ##### Get custom range
> > >
> > > history = client.get_price_history( ... token_id, ... start_ts=1700000000, ... end_ts=1700100000, ... fidelity=5, # 5-minute resolution ... )

#### get_trade_tape

```
get_trade_tape(
    token_id: str | None = None, limit: int = 100
) -> list[HistoricalTrade]
```

Get recent executed trades (trade tape).

Fetches the most recent trades for analysis. Can be filtered by token ID for market-specific trades.

Parameters:

| Name       | Type  | Description                                               | Default                                 |
| ---------- | ----- | --------------------------------------------------------- | --------------------------------------- |
| `token_id` | \`str | None\`                                                    | Optional CLOB token ID to filter trades |
| `limit`    | `int` | Maximum number of trades to return (default 100, max 500) | `100`                                   |

Returns:

| Type                    | Description                                   |
| ----------------------- | --------------------------------------------- |
| `list[HistoricalTrade]` | List of HistoricalTrade objects, newest first |

Raises:

| Type                 | Description      |
| -------------------- | ---------------- |
| `PolymarketAPIError` | If request fails |

Example

> > > ##### Get recent trades for YES token
> > >
> > > trades = client.get_trade_tape(token_id="123...", limit=50) for trade in trades: ... print(f"{trade.side} {trade.size} @ {trade.price}")

### TokenBucketRateLimiter

```
TokenBucketRateLimiter(
    rate_per_second: float, enabled: bool = True
)
```

Token bucket rate limiter for API calls.

Implements a token bucket algorithm that allows bursting while maintaining an average rate over time. Tokens are added to the bucket at a fixed rate, and each request consumes one token.

The algorithm:

1. Bucket starts full (capacity = rate_per_second tokens)
1. Tokens are added at rate_per_second tokens per second
1. Each request consumes 1 token
1. If bucket is empty, the caller waits until a token is available

Thread Safety

This implementation is NOT thread-safe. Use separate instances per thread or add locking if needed.

Example

> > > limiter = TokenBucketRateLimiter(rate_per_second=30.0) limiter.acquire() # Blocks if rate limit exceeded
> > >
> > > #### Make API call

Initialize the rate limiter.

Parameters:

| Name              | Type    | Description                                                   | Default    |
| ----------------- | ------- | ------------------------------------------------------------- | ---------- |
| `rate_per_second` | `float` | Maximum average requests per second                           | *required* |
| `enabled`         | `bool`  | Whether rate limiting is active (can be disabled for testing) | `True`     |

#### enabled

```
enabled: bool
```

Whether rate limiting is enabled.

#### rate

```
rate: float
```

Current rate limit (requests per second).

#### available_tokens

```
available_tokens: float
```

Number of tokens currently available (after refill).

#### acquire

```
acquire(timeout: float | None = None) -> bool
```

Acquire a token, blocking if necessary.

Waits until a token is available, then consumes it. If the bucket is empty, sleeps until enough time has passed for a new token.

Parameters:

| Name      | Type    | Description | Default                                                                                                                  |
| --------- | ------- | ----------- | ------------------------------------------------------------------------------------------------------------------------ |
| `timeout` | \`float | None\`      | Maximum time to wait in seconds. None means wait forever. If timeout expires before a token is available, returns False. |

Returns:

| Type   | Description                                                                     |
| ------ | ------------------------------------------------------------------------------- |
| `bool` | True if token was acquired, False if timeout expired (only when timeout is set) |

Example

> > > if limiter.acquire(timeout=5.0): ... make_api_call() ... else: ... print("Timeout waiting for rate limit")

#### try_acquire

```
try_acquire() -> bool
```

Try to acquire a token without blocking.

Returns:

| Type   | Description                                          |
| ------ | ---------------------------------------------------- |
| `bool` | True if token was acquired, False if bucket is empty |

Example

> > > if limiter.try_acquire(): ... make_api_call() ... else: ... print("Rate limit would be exceeded, skipping")

#### reset

```
reset() -> None
```

Reset the rate limiter to full capacity.

Useful for testing or after a period of inactivity.

### AllowanceStatus

```
AllowanceStatus(
    usdc_balance: int,
    usdc_allowance_ctf_exchange: int,
    usdc_allowance_neg_risk_exchange: int,
    ctf_approved_for_ctf_exchange: bool,
    ctf_approved_for_neg_risk_adapter: bool,
)
```

Status of token allowances for Polymarket trading.

Attributes:

| Name                                | Type   | Description                            |
| ----------------------------------- | ------ | -------------------------------------- |
| `usdc_balance`                      | `int`  | USDC balance in token units            |
| `usdc_allowance_ctf_exchange`       | `int`  | USDC allowance for CTF Exchange        |
| `usdc_allowance_neg_risk_exchange`  | `int`  | USDC allowance for Neg Risk Exchange   |
| `ctf_approved_for_ctf_exchange`     | `bool` | ERC-1155 approved for CTF Exchange     |
| `ctf_approved_for_neg_risk_adapter` | `bool` | ERC-1155 approved for Neg Risk Adapter |

#### usdc_approved_ctf_exchange

```
usdc_approved_ctf_exchange: bool
```

Check if USDC is approved for CTF Exchange.

#### usdc_approved_neg_risk_exchange

```
usdc_approved_neg_risk_exchange: bool
```

Check if USDC is approved for Neg Risk Exchange.

#### fully_approved

```
fully_approved: bool
```

Check if all necessary approvals are in place.

### CtfSDK

```
CtfSDK(
    chain_id: int = POLYGON_CHAIN_ID,
    ctf_exchange: str = CTF_EXCHANGE,
    neg_risk_exchange: str = NEG_RISK_EXCHANGE,
    conditional_tokens: str = CONDITIONAL_TOKENS,
    neg_risk_adapter: str = NEG_RISK_ADAPTER,
    usdc: str = USDC_POLYGON,
)
```

Low-level SDK for Polymarket CTF on-chain operations.

This SDK provides methods to:

- Check and set token approvals
- Query token balances
- Build split, merge, and redeem transactions
- Check condition resolution status

All transaction building methods return TransactionData objects that can be signed and submitted using a signer.

Example

sdk = CtfSDK() web3 = Web3(Web3.HTTPProvider(rpc_url))

#### Check if wallet needs approvals

status = sdk.check_allowances("0x...", web3)

if not status.usdc_approved_ctf_exchange: tx = sdk.build_approve_usdc_tx(CTF_EXCHANGE, MAX_UINT256, "0x...")

# Sign and submit tx...

#### Build redeem transaction for resolved market

tx = sdk.build_redeem_tx( condition_id="0x...", index_sets=[1, 2], sender="0x...", )

Initialize the CTF SDK.

Parameters:

| Name                 | Type  | Description                         | Default              |
| -------------------- | ----- | ----------------------------------- | -------------------- |
| `chain_id`           | `int` | Chain ID (default: Polygon 137)     | `POLYGON_CHAIN_ID`   |
| `ctf_exchange`       | `str` | CTF Exchange contract address       | `CTF_EXCHANGE`       |
| `neg_risk_exchange`  | `str` | Neg Risk Exchange contract address  | `NEG_RISK_EXCHANGE`  |
| `conditional_tokens` | `str` | Conditional Tokens contract address | `CONDITIONAL_TOKENS` |
| `neg_risk_adapter`   | `str` | Neg Risk Adapter contract address   | `NEG_RISK_ADAPTER`   |
| `usdc`               | `str` | USDC token address                  | `USDC_POLYGON`       |

#### build_approve_usdc_tx

```
build_approve_usdc_tx(
    spender: str, amount: int, sender: str
) -> TransactionData
```

Build USDC approval transaction.

Approves the spender (typically CTF Exchange or Neg Risk Exchange) to spend USDC on behalf of the sender.

Parameters:

| Name      | Type  | Description                                       | Default    |
| --------- | ----- | ------------------------------------------------- | ---------- |
| `spender` | `str` | Address to approve (e.g., CTF_EXCHANGE)           | *required* |
| `amount`  | `int` | Amount to approve (use MAX_UINT256 for unlimited) | *required* |
| `sender`  | `str` | Transaction sender address                        | *required* |

Returns:

| Type              | Description                      |
| ----------------- | -------------------------------- |
| `TransactionData` | TransactionData for the approval |

#### build_approve_conditional_tokens_tx

```
build_approve_conditional_tokens_tx(
    operator: str, approved: bool, sender: str
) -> TransactionData
```

Build ERC-1155 setApprovalForAll transaction.

Approves the operator (typically CTF Exchange or Neg Risk Adapter) to transfer conditional tokens on behalf of the sender.

Parameters:

| Name       | Type   | Description                             | Default    |
| ---------- | ------ | --------------------------------------- | ---------- |
| `operator` | `str`  | Address to approve (e.g., CTF_EXCHANGE) | *required* |
| `approved` | `bool` | True to approve, False to revoke        | *required* |
| `sender`   | `str`  | Transaction sender address              | *required* |

Returns:

| Type              | Description                      |
| ----------------- | -------------------------------- |
| `TransactionData` | TransactionData for the approval |

#### check_allowances

```
check_allowances(wallet: str, web3: Any) -> AllowanceStatus
```

Check all relevant token allowances.

Queries USDC allowances and ERC-1155 operator approvals needed for trading on Polymarket.

Parameters:

| Name     | Type  | Description             | Default    |
| -------- | ----- | ----------------------- | ---------- |
| `wallet` | `str` | Wallet address to check | *required* |
| `web3`   | `Any` | Web3 instance           | *required* |

Returns:

| Type              | Description                                    |
| ----------------- | ---------------------------------------------- |
| `AllowanceStatus` | AllowanceStatus with all allowance information |

#### ensure_allowances

```
ensure_allowances(
    wallet: str, web3: Any
) -> list[TransactionData]
```

Build transactions to ensure all necessary approvals.

Checks current allowance status and returns a list of transactions needed to set up all required approvals for trading.

Parameters:

| Name     | Type  | Description    | Default    |
| -------- | ----- | -------------- | ---------- |
| `wallet` | `str` | Wallet address | *required* |
| `web3`   | `Any` | Web3 instance  | *required* |

Returns:

| Type                    | Description                                      |
| ----------------------- | ------------------------------------------------ |
| `list[TransactionData]` | List of TransactionData for any needed approvals |

#### get_token_balance

```
get_token_balance(
    wallet: str, token_id: int, web3: Any
) -> int
```

Get ERC-1155 token balance.

Parameters:

| Name       | Type  | Description                        | Default    |
| ---------- | ----- | ---------------------------------- | ---------- |
| `wallet`   | `str` | Wallet address                     | *required* |
| `token_id` | `int` | Conditional token ID (position ID) | *required* |
| `web3`     | `Any` | Web3 instance                      | *required* |

Returns:

| Type  | Description                 |
| ----- | --------------------------- |
| `int` | Token balance in base units |

#### get_token_balances

```
get_token_balances(
    wallet: str, token_ids: list[int], web3: Any
) -> list[int]
```

Get multiple ERC-1155 token balances in a single call.

Parameters:

| Name        | Type        | Description                   | Default    |
| ----------- | ----------- | ----------------------------- | ---------- |
| `wallet`    | `str`       | Wallet address                | *required* |
| `token_ids` | `list[int]` | List of conditional token IDs | *required* |
| `web3`      | `Any`       | Web3 instance                 | *required* |

Returns:

| Type        | Description                          |
| ----------- | ------------------------------------ |
| `list[int]` | List of token balances in base units |

#### get_usdc_balance

```
get_usdc_balance(wallet: str, web3: Any) -> int
```

Get USDC balance.

Parameters:

| Name     | Type  | Description    | Default    |
| -------- | ----- | -------------- | ---------- |
| `wallet` | `str` | Wallet address | *required* |
| `web3`   | `Any` | Web3 instance  | *required* |

Returns:

| Type  | Description                             |
| ----- | --------------------------------------- |
| `int` | USDC balance in base units (6 decimals) |

#### get_collection_id

```
get_collection_id(
    condition_id: bytes,
    index_set: int,
    parent_collection_id: bytes = ZERO_BYTES32,
) -> bytes
```

Calculate collection ID for an outcome.

Parameters:

| Name                   | Type    | Description                                | Default        |
| ---------------------- | ------- | ------------------------------------------ | -------------- |
| `condition_id`         | `bytes` | Condition ID (32 bytes)                    | *required*     |
| `index_set`            | `int`   | Outcome index set (1=YES, 2=NO for binary) | *required*     |
| `parent_collection_id` | `bytes` | Parent collection (default: root)          | `ZERO_BYTES32` |

Returns:

| Type    | Description              |
| ------- | ------------------------ |
| `bytes` | Collection ID (32 bytes) |

#### get_position_id

```
get_position_id(
    collateral: str, collection_id: bytes
) -> int
```

Calculate ERC-1155 position ID from collection ID.

Parameters:

| Name            | Type    | Description                     | Default    |
| --------------- | ------- | ------------------------------- | ---------- |
| `collateral`    | `str`   | Collateral token address (USDC) | *required* |
| `collection_id` | `bytes` | Collection ID (32 bytes)        | *required* |

Returns:

| Type  | Description           |
| ----- | --------------------- |
| `int` | Position ID (uint256) |

#### get_token_ids_for_condition

```
get_token_ids_for_condition(
    condition_id: str | bytes,
) -> tuple[int, int]
```

Get YES and NO token IDs for a binary condition.

Parameters:

| Name           | Type  | Description | Default                            |
| -------------- | ----- | ----------- | ---------------------------------- |
| `condition_id` | \`str | bytes\`     | Condition ID (hex string or bytes) |

Returns:

| Type              | Description                          |
| ----------------- | ------------------------------------ |
| `tuple[int, int]` | Tuple of (yes_token_id, no_token_id) |

#### build_split_tx

```
build_split_tx(
    condition_id: str | bytes, amount: int, sender: str
) -> TransactionData
```

Build split position transaction.

Splits USDC into YES and NO conditional tokens. Requires USDC approval for Conditional Tokens contract.

Parameters:

| Name           | Type  | Description                             | Default                            |
| -------------- | ----- | --------------------------------------- | ---------------------------------- |
| `condition_id` | \`str | bytes\`                                 | Condition ID (hex string or bytes) |
| `amount`       | `int` | Amount of USDC to split (in base units) | *required*                         |
| `sender`       | `str` | Transaction sender address              | *required*                         |

Returns:

| Type              | Description                             |
| ----------------- | --------------------------------------- |
| `TransactionData` | TransactionData for the split operation |

#### build_merge_tx

```
build_merge_tx(
    condition_id: str | bytes, amount: int, sender: str
) -> TransactionData
```

Build merge positions transaction.

Merges equal amounts of YES and NO tokens back into USDC. Requires ERC-1155 approval for Conditional Tokens contract.

Parameters:

| Name           | Type  | Description                           | Default                            |
| -------------- | ----- | ------------------------------------- | ---------------------------------- |
| `condition_id` | \`str | bytes\`                               | Condition ID (hex string or bytes) |
| `amount`       | `int` | Amount of each outcome token to merge | *required*                         |
| `sender`       | `str` | Transaction sender address            | *required*                         |

Returns:

| Type              | Description                             |
| ----------------- | --------------------------------------- |
| `TransactionData` | TransactionData for the merge operation |

#### build_redeem_tx

```
build_redeem_tx(
    condition_id: str | bytes,
    index_sets: list[int],
    sender: str,
) -> TransactionData
```

Build redeem positions transaction.

Redeems winning positions after market resolution. Only works if the condition has been resolved.

Parameters:

| Name           | Type        | Description                                          | Default                            |
| -------------- | ----------- | ---------------------------------------------------- | ---------------------------------- |
| `condition_id` | \`str       | bytes\`                                              | Condition ID (hex string or bytes) |
| `index_sets`   | `list[int]` | List of index sets to redeem (e.g., [1, 2] for both) | *required*                         |
| `sender`       | `str`       | Transaction sender address                           | *required*                         |

Returns:

| Type              | Description                        |
| ----------------- | ---------------------------------- |
| `TransactionData` | TransactionData for the redemption |

#### get_condition_resolution

```
get_condition_resolution(
    condition_id: str | bytes, web3: Any
) -> ResolutionStatus
```

Get resolution status of a condition.

Checks if a condition has been resolved and returns payout information.

Parameters:

| Name           | Type  | Description   | Default                            |
| -------------- | ----- | ------------- | ---------------------------------- |
| `condition_id` | \`str | bytes\`       | Condition ID (hex string or bytes) |
| `web3`         | `Any` | Web3 instance | *required*                         |

Returns:

| Type               | Description                                  |
| ------------------ | -------------------------------------------- |
| `ResolutionStatus` | ResolutionStatus with resolution information |

### ResolutionStatus

```
ResolutionStatus(
    condition_id: str,
    is_resolved: bool,
    payout_denominator: int,
    payout_numerators: list[int],
    winning_outcome: int | None = None,
)
```

Resolution status of a condition.

Attributes:

| Name                 | Type        | Description                                |
| -------------------- | ----------- | ------------------------------------------ |
| `condition_id`       | `str`       | The condition ID (bytes32 hex string)      |
| `is_resolved`        | `bool`      | Whether the condition has been resolved    |
| `payout_denominator` | `int`       | Denominator for payout calculation         |
| `payout_numerators`  | `list[int]` | List of payout numerators for each outcome |
| `winning_outcome`    | \`int       | None\`                                     |

### TransactionData

```
TransactionData(
    to: str,
    data: str,
    value: int = 0,
    gas_estimate: int = 100000,
    description: str = "",
)
```

Transaction data for on-chain operations.

Attributes:

| Name           | Type  | Description                       |
| -------------- | ----- | --------------------------------- |
| `to`           | `str` | Contract address to call          |
| `data`         | `str` | Encoded function call data        |
| `value`        | `int` | ETH value to send (usually 0)     |
| `gas_estimate` | `int` | Estimated gas for the transaction |
| `description`  | `str` | Human-readable description        |

#### to_tx_params

```
to_tx_params(sender: str) -> dict[str, Any]
```

Convert to web3 transaction parameters.

Parameters:

| Name     | Type  | Description                | Default    |
| -------- | ----- | -------------------------- | ---------- |
| `sender` | `str` | Transaction sender address | *required* |

Returns:

| Type             | Description                               |
| ---------------- | ----------------------------------------- |
| `dict[str, Any]` | Dict with transaction parameters for web3 |

### PolymarketAPIError

```
PolymarketAPIError(
    message: str,
    status_code: int | None = None,
    errors: list[str] | None = None,
)
```

Bases: `PolymarketError`

Error from Polymarket API call.

### PolymarketAuthenticationError

Bases: `PolymarketError`

Authentication failed with Polymarket.

### PolymarketCredentialsError

Bases: `PolymarketError`

API credentials are invalid or missing.

### PolymarketError

Bases: `Exception`

Base exception for all Polymarket errors.

### PolymarketInsufficientBalanceError

```
PolymarketInsufficientBalanceError(
    asset: str, required: str, available: str
)
```

Bases: `PolymarketOrderError`

Insufficient balance for operation.

### PolymarketInvalidPriceError

```
PolymarketInvalidPriceError(
    price: str,
    min_price: str = "0.01",
    max_price: str = "0.99",
)
```

Bases: `PolymarketOrderError`

Invalid order price.

### PolymarketMarketClosedError

```
PolymarketMarketClosedError(market_id: str)
```

Bases: `PolymarketMarketError`

Market is closed for trading.

### PolymarketMarketError

Bases: `PolymarketError`

Error related to market operations.

### PolymarketMarketNotFoundError

```
PolymarketMarketNotFoundError(market_id: str)
```

Bases: `PolymarketMarketError`

Market not found.

### PolymarketMarketNotResolvedError

```
PolymarketMarketNotResolvedError(market_id: str)
```

Bases: `PolymarketMarketError`

Market is not yet resolved.

### PolymarketMinimumOrderError

```
PolymarketMinimumOrderError(size: str, minimum: str)
```

Bases: `PolymarketOrderError`

Order size below minimum.

### PolymarketOrderError

Bases: `PolymarketError`

Error related to order operations.

### PolymarketOrderNotFoundError

```
PolymarketOrderNotFoundError(order_id: str)
```

Bases: `PolymarketOrderError`

Order not found.

### PolymarketRateLimitError

```
PolymarketRateLimitError(
    message: str = "Rate limit exceeded",
    retry_after: int | None = None,
)
```

Bases: `PolymarketError`

Rate limit exceeded.

### PolymarketRedemptionError

Bases: `PolymarketError`

Error during position redemption.

### PolymarketSignatureError

Bases: `PolymarketError`

Error creating or verifying signature.

### Quote

```
Quote(
    price: Decimal,
    size: Decimal,
    side: Literal["BUY", "SELL"],
)
```

A single quote with price, size, and side.

Represents a single order that can be placed on the orderbook. Used in quote ladders for market making.

Attributes:

| Name    | Type                     | Description                                       |
| ------- | ------------------------ | ------------------------------------------------- |
| `price` | `Decimal`                | Quote price (0.01 to 0.99 for prediction markets) |
| `size`  | `Decimal`                | Number of shares to quote                         |
| `side`  | `Literal['BUY', 'SELL']` | "BUY" for bid, "SELL" for ask                     |

Example

> > > quote = Quote(price=Decimal("0.50"), size=Decimal("100"), side="BUY") print(f"Bid {quote.size} @ {quote.price}") Bid 100 @ 0.50

#### __post_init__

```
__post_init__() -> None
```

Validate quote parameters.

#### to_dict

```
to_dict() -> dict
```

Convert to dictionary for serialization.

#### from_dict

```
from_dict(data: dict) -> Quote
```

Create from dictionary.

### RiskParameters

```
RiskParameters(
    base_spread: Decimal = Decimal("0.02"),
    skew_factor: Decimal = Decimal("0.5"),
    max_position: Decimal = Decimal("1000"),
    min_edge: Decimal = Decimal("0.001"),
    volatility_multiplier: Decimal = Decimal("1.0"),
    tick_size: Decimal = Decimal("0.01"),
)
```

Parameters for market making risk management.

These parameters control how the market maker adjusts spreads and quotes based on inventory position and market conditions.

Attributes:

| Name                    | Type      | Description                                                                                                       |
| ----------------------- | --------- | ----------------------------------------------------------------------------------------------------------------- |
| `base_spread`           | `Decimal` | Minimum spread to maintain (e.g., 0.02 = 2%)                                                                      |
| `skew_factor`           | `Decimal` | How much to skew quotes based on inventory (0-1). Higher values mean more aggressive skewing to reduce inventory. |
| `max_position`          | `Decimal` | Maximum position size allowed. Orders beyond this size will not be quoted on the accumulating side.               |
| `min_edge`              | `Decimal` | Minimum expected edge (profit margin) per trade. Quotes will not be placed if edge falls below this threshold.    |
| `volatility_multiplier` | `Decimal` | Multiplier for spread based on volatility. Higher volatility leads to wider spreads.                              |
| `tick_size`             | `Decimal` | Minimum price increment (default 0.01).                                                                           |

Example

> > > params = RiskParameters( ... base_spread=Decimal("0.02"), ... skew_factor=Decimal("0.5"), ... max_position=Decimal("1000"), ... )

#### __post_init__

```
__post_init__() -> None
```

Validate risk parameters.

### ApiCredentials

Bases: `BaseModel`

Polymarket API credentials from L1 authentication.

These credentials are obtained by signing an EIP-712 message and calling the create/derive API key endpoint.

Credential Handling Limitations

The current implementation has the following limitations that users should be aware of:

1. **Storage**: Credentials are stored in environment variables only. There is no integration with secret management systems (e.g., AWS Secrets Manager, HashiCorp Vault). Users are responsible for secure credential storage in their deployment environment.
1. **No Automatic Rotation**: API credentials do not auto-rotate. The Polymarket CLOB API credentials have an expiration (typically 7 days from creation). Users must manually regenerate credentials before expiration by calling `ClobClient.create_api_credentials()` or `ClobClient.derive_api_credentials()` and updating environment variables.
1. **No Expiration Monitoring**: The connector does not monitor credential expiration. If credentials expire, API calls will fail with authentication errors. Users should implement their own monitoring or credential refresh workflow.
1. **Manual Renewal Process**:
1. Generate new credentials using L1 authentication (EIP-712 signature)
1. Update environment variables: POLYMARKET_API_KEY, POLYMARKET_SECRET, POLYMARKET_PASSPHRASE
1. Restart the application or reload configuration

Environment Variables

- POLYMARKET_API_KEY: API key for L2 HMAC authentication (required for trading)
- POLYMARKET_SECRET: Base64-encoded HMAC secret (required for trading)
- POLYMARKET_PASSPHRASE: Passphrase for API requests (required for trading)

Example

#### Load from environment

creds = ApiCredentials.from_env()

#### Or create manually

creds = ApiCredentials( api_key="your-api-key", secret=SecretStr("your-base64-secret"), passphrase=SecretStr("your-passphrase"), )

#### from_dict

```
from_dict(data: dict) -> ApiCredentials
```

Create from API response.

#### from_env

```
from_env(
    api_key_env: str = "POLYMARKET_API_KEY",
    secret_env: str = "POLYMARKET_SECRET",
    passphrase_env: str = "POLYMARKET_PASSPHRASE",
) -> ApiCredentials
```

Load credentials from environment variables.

### BalanceAllowance

Bases: `BaseModel`

Balance and allowance information.

### GammaMarket

Bases: `BaseModel`

Market data from Gamma Markets API.

#### yes_token_id

```
yes_token_id: str | None
```

Get YES token ID.

#### no_token_id

```
no_token_id: str | None
```

Get NO token ID.

#### yes_price

```
yes_price: Decimal
```

Get YES price.

#### no_price

```
no_price: Decimal
```

Get NO price.

#### from_api_response

```
from_api_response(data: dict) -> GammaMarket
```

Create from Gamma API response.

### HistoricalPrice

```
HistoricalPrice(timestamp: datetime, price: Decimal)
```

Single historical price point.

Represents a price at a specific timestamp from the CLOB API. Price values represent probability (0.0 to 1.0).

#### from_api_response

```
from_api_response(data: dict) -> HistoricalPrice
```

Create from API response.

### HistoricalTrade

```
HistoricalTrade(
    id: str,
    token_id: str,
    side: Literal["BUY", "SELL"],
    price: Decimal,
    size: Decimal,
    timestamp: datetime,
    maker: str | None = None,
    taker: str | None = None,
)
```

Historical trade from the market.

Represents a single executed trade from the trade tape.

#### from_api_response

```
from_api_response(data: dict) -> HistoricalTrade
```

Create from API response.

### LimitOrderParams

```
LimitOrderParams(
    token_id: str,
    side: Literal["BUY", "SELL"],
    price: Decimal,
    size: Decimal,
    expiration: int | None = None,
    fee_rate_bps: int = 0,
)
```

Parameters for building a limit order.

### MarketFilters

Bases: `BaseModel`

Filters for market queries.

### MarketOrderParams

```
MarketOrderParams(
    token_id: str,
    side: Literal["BUY", "SELL"],
    amount: Decimal,
    worst_price: Decimal | None = None,
)
```

Parameters for building a market order.

### OpenOrder

Bases: `BaseModel`

Open order information.

### OrderBook

Bases: `BaseModel`

Orderbook for a token.

#### best_bid

```
best_bid: Decimal | None
```

Get best bid price.

#### best_ask

```
best_ask: Decimal | None
```

Get best ask price.

#### spread

```
spread: Decimal | None
```

Get bid-ask spread.

#### from_api_response

```
from_api_response(data: dict) -> OrderBook
```

Create from CLOB API response.

### OrderFilters

Bases: `BaseModel`

Filters for order queries.

### OrderResponse

Bases: `BaseModel`

Response from order submission.

#### from_api_response

```
from_api_response(data: dict) -> OrderResponse
```

Create from CLOB API response.

### OrderSide

Bases: `int`, `Enum`

Order side.

### OrderStatus

Bases: `StrEnum`

Order status values.

### OrderType

Bases: `StrEnum`

Order time-in-force types.

### PolymarketConfig

Bases: `BaseModel`

Configuration for Polymarket connector.

This configuration supports both L1 (EIP-712 signing) and L2 (HMAC API) authentication for Polymarket's hybrid CLOB + on-chain architecture.

Credential Management

Polymarket uses a two-tier authentication system:

**L1 Authentication (Wallet Signing)**:

- Used for: Creating/deriving API credentials, signing on-chain transactions
- Requires: wallet_address and private_key
- No expiration: Wallet keys don't expire

**L2 Authentication (HMAC API)**:

- Used for: Orderbook operations (place/cancel orders, fetch positions)
- Requires: api_credentials (api_key, secret, passphrase)
- Expiration: Credentials typically expire after 7 days
- Storage: Environment variables only (no secret manager integration)

**Current Limitations**:

1. Credentials stored in environment variables only
1. No automatic credential rotation or renewal
1. No expiration monitoring - API calls will fail silently when expired
1. Manual renewal required before expiration

**Renewal Process**: When credentials expire, you must:

1. Use ClobClient with a valid wallet to call create_api_credentials() or derive_api_credentials()
1. Update environment variables with new credentials
1. Restart the application or reload configuration

Configurable URLs

All API URLs are configurable to support proxies, test environments, or alternative endpoints:

- clob_base_url: CLOB API for orderbook, prices, and trading (default: https://clob.polymarket.com)
- gamma_base_url: Gamma Markets API for market metadata (default: https://gamma-api.polymarket.com)
- data_api_base_url: Data API for positions and user data (default: https://data-api.polymarket.com)

Environment Variables

Required for wallet operations:

- POLYMARKET_WALLET_ADDRESS: Wallet address for signing and transactions
- POLYMARKET_PRIVATE_KEY: Private key for EIP-712 signing (0x prefixed hex)

Required for trading operations (L2 auth):

- POLYMARKET_API_KEY: API key from credential creation/derivation
- POLYMARKET_SECRET: Base64-encoded HMAC secret
- POLYMARKET_PASSPHRASE: Passphrase from credential creation/derivation

Optional:

- POLYGON_RPC_URL: RPC endpoint for Polygon (for on-chain operations)
- POLYMARKET_CLOB_URL: Override clob_base_url
- POLYMARKET_GAMMA_URL: Override gamma_base_url
- POLYMARKET_DATA_API_URL: Override data_api_base_url

Example

#### Basic configuration (wallet only, for read operations and credential creation)

config = PolymarketConfig( wallet_address="0x...", private_key=SecretStr("0x..."), )

#### Full configuration with API credentials (for trading)

config = PolymarketConfig( wallet_address="0x...", private_key=SecretStr("0x..."), api_credentials=ApiCredentials( api_key="your-api-key", secret=SecretStr("your-base64-secret"), passphrase=SecretStr("your-passphrase"), ), )

#### Load from environment

config = PolymarketConfig.from_env() creds = ApiCredentials.from_env() config.api_credentials = creds

#### With custom URLs (for proxies or testing)

config = PolymarketConfig( wallet_address="0x...", private_key=SecretStr("0x..."), data_api_base_url="https://my-proxy.example.com/data", )

#### checksum_wallet

```
checksum_wallet(v: str) -> str
```

Ensure wallet address is checksummed.

#### from_env

```
from_env(
    wallet_env: str = "POLYMARKET_WALLET_ADDRESS",
    private_key_env: str = "POLYMARKET_PRIVATE_KEY",
    rpc_env: str = "POLYGON_RPC_URL",
    clob_url_env: str = "POLYMARKET_CLOB_URL",
    gamma_url_env: str = "POLYMARKET_GAMMA_URL",
    data_api_url_env: str = "POLYMARKET_DATA_API_URL",
) -> PolymarketConfig
```

Load configuration from environment variables.

Required Environment Variables

- POLYMARKET_WALLET_ADDRESS: Wallet address
- POLYMARKET_PRIVATE_KEY: Private key for signing

Optional Environment Variables

- POLYGON_RPC_URL: RPC endpoint for Polygon
- POLYMARKET_CLOB_URL: Override CLOB API base URL
- POLYMARKET_GAMMA_URL: Override Gamma Markets API base URL
- POLYMARKET_DATA_API_URL: Override Data API base URL

### Position

Bases: `BaseModel`

Position information.

### PriceHistory

```
PriceHistory(
    token_id: str,
    interval: str,
    prices: list[HistoricalPrice],
    start_time: datetime | None = None,
    end_time: datetime | None = None,
)
```

Historical price data for a token.

Contains a time series of prices for OHLC-style analysis.

#### open_price

```
open_price: Decimal | None
```

Get opening price (first price in the series).

#### close_price

```
close_price: Decimal | None
```

Get closing price (last price in the series).

#### high_price

```
high_price: Decimal | None
```

Get highest price in the series.

#### low_price

```
low_price: Decimal | None
```

Get lowest price in the series.

### PriceHistoryInterval

Bases: `StrEnum`

Supported intervals for price history queries.

### PriceLevel

Bases: `BaseModel`

Single price level in orderbook.

### SignatureType

Bases: `int`, `Enum`

Signature types for Polymarket authentication.

### SignedOrder

```
SignedOrder(order: UnsignedOrder, signature: str)
```

Signed order ready for submission.

#### to_api_payload

```
to_api_payload() -> dict
```

Convert to API submission payload.

### TokenPrice

Bases: `BaseModel`

Price information for a token.

#### from_api_response

```
from_api_response(data: dict) -> TokenPrice
```

Create from CLOB API response.

### Trade

Bases: `BaseModel`

Trade information.

### TradeFilters

Bases: `BaseModel`

Filters for trade queries.

### TradeStatus

Bases: `StrEnum`

Trade status values.

### UnsignedOrder

```
UnsignedOrder(
    salt: int,
    maker: str,
    signer: str,
    taker: str,
    token_id: int,
    maker_amount: int,
    taker_amount: int,
    expiration: int,
    nonce: int,
    fee_rate_bps: int,
    side: int,
    signature_type: int,
)
```

Unsigned order ready for signing.

#### to_struct

```
to_struct() -> dict
```

Convert to struct for EIP-712 signing.

### CtfEvent

```
CtfEvent(
    event_type: PolymarketEventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)
```

Parsed CTF/ERC-1155 event.

Attributes:

| Name               | Type                  | Description                 |
| ------------------ | --------------------- | --------------------------- |
| `event_type`       | `PolymarketEventType` | Type of event               |
| `event_name`       | `str`                 | Name of event               |
| `log_index`        | `int`                 | Index of log in transaction |
| `transaction_hash` | `str`                 | Transaction hash            |
| `block_number`     | `int`                 | Block number                |
| `contract_address` | `str`                 | Contract that emitted event |
| `data`             | `dict[str, Any]`      | Parsed event data           |
| `raw_topics`       | `list[str]`           | Raw event topics            |
| `raw_data`         | `str`                 | Raw event data              |
| `timestamp`        | `datetime`            | Event timestamp             |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### CtfParseResult

```
CtfParseResult(
    success: bool,
    events: list[CtfEvent] = list(),
    transfer_singles: list[TransferSingleData] = list(),
    transfer_batches: list[TransferBatchData] = list(),
    redemptions: list[PayoutRedemptionData] = list(),
    erc20_transfers: list[Erc20TransferData] = list(),
    redemption_result: RedemptionResult | None = None,
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
    transaction_success: bool = True,
)
```

Result of parsing a CTF transaction receipt.

Attributes:

| Name                  | Type                         | Description                   |
| --------------------- | ---------------------------- | ----------------------------- |
| `success`             | `bool`                       | Whether parsing succeeded     |
| `events`              | `list[CtfEvent]`             | List of all parsed events     |
| `transfer_singles`    | `list[TransferSingleData]`   | TransferSingle events         |
| `transfer_batches`    | `list[TransferBatchData]`    | TransferBatch events          |
| `redemptions`         | `list[PayoutRedemptionData]` | PayoutRedemption events       |
| `erc20_transfers`     | `list[Erc20TransferData]`    | ERC-20 Transfer events        |
| `redemption_result`   | \`RedemptionResult           | None\`                        |
| `error`               | \`str                        | None\`                        |
| `transaction_hash`    | `str`                        | Transaction hash              |
| `block_number`        | `int`                        | Block number                  |
| `transaction_success` | `bool`                       | Whether transaction succeeded |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### Erc20TransferData

```
Erc20TransferData(
    from_addr: str,
    to_addr: str,
    value: int,
    token_address: str,
)
```

Parsed data from ERC-20 Transfer event.

Event: Transfer(from, to, value)

Attributes:

| Name            | Type  | Description            |
| --------------- | ----- | ---------------------- |
| `from_addr`     | `str` | Sender address         |
| `to_addr`       | `str` | Recipient address      |
| `value`         | `int` | Amount transferred     |
| `token_address` | `str` | Token contract address |

#### value_decimal

```
value_decimal: Decimal
```

Get value as decimal (assumes 6 decimals for USDC).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PayoutRedemptionData

```
PayoutRedemptionData(
    redeemer: str,
    collateral_token: str,
    parent_collection_id: str,
    condition_id: str,
    index_sets: list[int],
    payout: int,
    contract_address: str,
)
```

Parsed data from PayoutRedemption event.

Event: PayoutRedemption(redeemer, collateralToken, parentCollectionId, conditionId, indexSets[], payout)

Attributes:

| Name                   | Type        | Description                        |
| ---------------------- | ----------- | ---------------------------------- |
| `redeemer`             | `str`       | Address that received payout       |
| `collateral_token`     | `str`       | Collateral token address (USDC)    |
| `parent_collection_id` | `str`       | Parent collection ID (usually 0x0) |
| `condition_id`         | `str`       | CTF condition ID                   |
| `index_sets`           | `list[int]` | Which outcomes were redeemed       |
| `payout`               | `int`       | Total payout amount                |
| `contract_address`     | `str`       | Contract that emitted event        |

#### payout_decimal

```
payout_decimal: Decimal
```

Get payout as decimal with 6 decimals.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PolymarketEventType

Bases: `Enum`

Polymarket/CTF event types.

### PolymarketReceiptParser

```
PolymarketReceiptParser(**kwargs: Any)
```

Parser for Polymarket CLOB responses and CTF transaction receipts.

This parser handles two types of data:

1. CLOB API responses - order submissions, fills, status updates
1. CTF transaction receipts - on-chain token transfers and redemptions

Example

parser = PolymarketReceiptParser()

#### Parse CLOB order submission response

trade_result = parser.parse_order_response(api_response) if trade_result.success: print(f"Order {trade_result.order_id}: {trade_result.status}")

#### Parse CTF redemption transaction receipt

ctf_result = parser.parse_ctf_receipt(tx_receipt) if ctf_result.success and ctf_result.redemption_result: print(f"Redeemed: {ctf_result.redemption_result.amount_redeemed} USDC")

Initialize the receipt parser.

Parameters:

| Name       | Type  | Description                                      | Default |
| ---------- | ----- | ------------------------------------------------ | ------- |
| `**kwargs` | `Any` | Additional arguments (ignored for compatibility) | `{}`    |

#### parse_order_response

```
parse_order_response(
    response: dict[str, Any],
) -> TradeResult
```

Parse a CLOB order submission response.

The response contains order details and initial status after submission.

Parameters:

| Name       | Type             | Description                  | Default    |
| ---------- | ---------------- | ---------------------------- | ---------- |
| `response` | `dict[str, Any]` | CLOB API order response dict | *required* |

Returns:

| Type          | Description                    |
| ------------- | ------------------------------ |
| `TradeResult` | TradeResult with order details |

Example response

{ "orderID": "0x1234...", "status": "LIVE", "owner": "0xYourAddress", "market": "19045189...", "side": "BUY", "price": "0.65", "size": "100", "filledSize": "0", "createdAt": "2025-01-15T10:30:00Z" }

#### parse_fill_notification

```
parse_fill_notification(
    notification: dict[str, Any],
) -> TradeResult
```

Parse a CLOB fill notification.

Fill notifications are received when an order is matched.

Parameters:

| Name           | Type             | Description                 | Default    |
| -------------- | ---------------- | --------------------------- | ---------- |
| `notification` | `dict[str, Any]` | Fill notification from CLOB | *required* |

Returns:

| Type          | Description                   |
| ------------- | ----------------------------- |
| `TradeResult` | TradeResult with fill details |

Example notification

{ "type": "fill", "orderId": "0x1234...", "matchId": "0x5678...", "fillSize": "50", "fillPrice": "0.65", "fee": "0", "timestamp": "2025-01-15T10:30:00Z" }

#### parse_order_status

```
parse_order_status(
    status_response: dict[str, Any],
) -> TradeResult
```

Parse a CLOB order status query response.

Parameters:

| Name              | Type             | Description                     | Default    |
| ----------------- | ---------------- | ------------------------------- | ---------- |
| `status_response` | `dict[str, Any]` | Order status response from CLOB | *required* |

Returns:

| Type          | Description                           |
| ------------- | ------------------------------------- |
| `TradeResult` | TradeResult with current order status |

#### parse_ctf_receipt

```
parse_ctf_receipt(
    receipt: dict[str, Any], filter_by_contract: bool = True
) -> CtfParseResult
```

Parse a CTF transaction receipt for on-chain events.

Parameters:

| Name                 | Type             | Description                                                                                                                                                                                                                                                                                                 | Default    |
| -------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
| `receipt`            | `dict[str, Any]` | Transaction receipt dict with 'logs', 'transactionHash', etc.                                                                                                                                                                                                                                               | *required* |
| `filter_by_contract` | `bool`           | If True (default), only parse logs from known Polymarket contracts (CONDITIONAL_TOKENS, CTF_EXCHANGE, NEG_RISK_EXCHANGE, NEG_RISK_ADAPTER, USDC_POLYGON). Set to False to parse all matching event signatures regardless of contract address (useful for testing or analyzing multi-protocol transactions). | `True`     |

Returns:

| Type             | Description                                              |
| ---------------- | -------------------------------------------------------- |
| `CtfParseResult` | CtfParseResult with extracted events and redemption data |

#### is_polymarket_event

```
is_polymarket_event(topic: str | bytes) -> bool
```

Check if a topic is a known Polymarket/CTF event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                    |
| ------ | ------------------------------ |
| `bool` | True if topic is a known event |

#### is_polymarket_contract

```
is_polymarket_contract(address: str) -> bool
```

Check if an address is a known Polymarket contract.

Parameters:

| Name      | Type  | Description      | Default    |
| --------- | ----- | ---------------- | ---------- |
| `address` | `str` | Contract address | *required* |

Returns:

| Type   | Description                                    |
| ------ | ---------------------------------------------- |
| `bool` | True if address is a known Polymarket contract |

#### get_event_type

```
get_event_type(topic: str | bytes) -> PolymarketEventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type                  | Description           |
| --------------------- | --------------------- |
| `PolymarketEventType` | Event type or UNKNOWN |

#### extract_outcome_tokens_received

```
extract_outcome_tokens_received(
    receipt: dict[str, Any],
) -> int | None
```

Extract outcome tokens received from transaction receipt.

Note: Without the user's address, we cannot determine transfer direction. This method sums all TransferSingle values as a proxy for tokens involved. For precise directional filtering, pass the user address to your own logic.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_cost_basis

```
extract_cost_basis(receipt: dict[str, Any]) -> int | None
```

Extract cost basis (USDC involved) from transaction receipt.

Note: Without the user's address, we cannot determine transfer direction. This method sums all USDC transfer values as a proxy for the transaction size. For precise directional filtering, pass the user address to your own logic.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_market_id

```
extract_market_id(receipt: dict[str, Any]) -> str | None
```

Extract market ID (condition ID) from transaction receipt.

For redemptions, extracts the condition ID directly from PayoutRedemption event.

For trades, returns the raw token ID as hex. Note: CTF token IDs use a complex encoding where conditionId = keccak256(oracle, questionId, outcomeSlotCount) and tokenId = positionId derived from collateral + conditionId + indexSet. Decoding the actual conditionId from a tokenId requires reverse lookup via CTF contract. For simplified use cases, the raw tokenId serves as a unique market identifier.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |
| \`str | None\`      |

#### extract_outcome_tokens_sold

```
extract_outcome_tokens_sold(
    receipt: dict[str, Any],
) -> int | None
```

Extract outcome tokens sold from transaction receipt.

Note: Polymarket sells are transfers to the exchange, not burns. Without the exchange contract address, we cannot reliably distinguish sells from other transfers. This method sums all TransferSingle values as a proxy for tokens involved in the transaction.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_proceeds

```
extract_proceeds(receipt: dict[str, Any]) -> int | None
```

Extract proceeds (USDC involved) from transaction receipt.

Note: Without the user's address, we cannot determine transfer direction. This method sums all USDC transfer values as a proxy for the transaction size. For precise directional filtering, pass the user address to your own logic.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_redemption_amount

```
extract_redemption_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract redemption amount (outcome tokens redeemed) from receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_payout

```
extract_payout(receipt: dict[str, Any]) -> int | None
```

Extract payout amount from redemption transaction.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

### RedemptionResult

```
RedemptionResult(
    success: bool,
    tx_hash: str = "",
    amount_redeemed: Decimal = (lambda: Decimal("0"))(),
    condition_id: str | None = None,
    index_sets: list[int] = list(),
    payout_amounts: list[Decimal] = list(),
    redeemer: str | None = None,
    gas_used: int = 0,
    error: str | None = None,
)
```

Result of parsing a CTF redemption transaction receipt.

Attributes:

| Name              | Type            | Description                         |
| ----------------- | --------------- | ----------------------------------- |
| `success`         | `bool`          | Whether parsing succeeded           |
| `tx_hash`         | `str`           | Transaction hash                    |
| `amount_redeemed` | `Decimal`       | Total USDC received from redemption |
| `condition_id`    | \`str           | None\`                              |
| `index_sets`      | `list[int]`     | Which index sets were redeemed      |
| `payout_amounts`  | `list[Decimal]` | Amount redeemed per index set       |
| `redeemer`        | \`str           | None\`                              |
| `gas_used`        | `int`           | Gas used by the transaction         |
| `error`           | \`str           | None\`                              |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### TradeResult

```
TradeResult(
    success: bool,
    order_id: str | None = None,
    status: str | None = None,
    filled_size: Decimal = (lambda: Decimal("0"))(),
    avg_price: Decimal = (lambda: Decimal("0"))(),
    fee: Decimal = (lambda: Decimal("0"))(),
    tx_hash: str | None = None,
    side: str | None = None,
    token_id: str | None = None,
    timestamp: datetime | None = None,
    error: str | None = None,
)
```

Result of parsing a CLOB order response or fill.

Attributes:

| Name          | Type       | Description               |
| ------------- | ---------- | ------------------------- |
| `success`     | `bool`     | Whether parsing succeeded |
| `order_id`    | \`str      | None\`                    |
| `status`      | \`str      | None\`                    |
| `filled_size` | `Decimal`  | Amount filled (in shares) |
| `avg_price`   | `Decimal`  | Average fill price        |
| `fee`         | `Decimal`  | Trading fee (in USDC)     |
| `tx_hash`     | \`str      | None\`                    |
| `side`        | \`str      | None\`                    |
| `token_id`    | \`str      | None\`                    |
| `timestamp`   | \`datetime | None\`                    |
| `error`       | \`str      | None\`                    |

#### is_filled

```
is_filled: bool
```

Check if order has been (at least partially) filled.

#### is_complete

```
is_complete: bool
```

Check if order is completely filled or cancelled.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### TransferBatchData

```
TransferBatchData(
    operator: str,
    from_addr: str,
    to_addr: str,
    token_ids: list[int],
    values: list[int],
    contract_address: str,
)
```

Parsed data from TransferBatch event.

Event: TransferBatch(operator, from, to, ids[], values[])

Attributes:

| Name               | Type        | Description                         |
| ------------------ | ----------- | ----------------------------------- |
| `operator`         | `str`       | Address that triggered the transfer |
| `from_addr`        | `str`       | Sender address                      |
| `to_addr`          | `str`       | Recipient address                   |
| `token_ids`        | `list[int]` | List of ERC-1155 token IDs          |
| `values`           | `list[int]` | List of amounts transferred         |
| `contract_address` | `str`       | Contract that emitted event         |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### TransferSingleData

```
TransferSingleData(
    operator: str,
    from_addr: str,
    to_addr: str,
    token_id: int,
    value: int,
    contract_address: str,
)
```

Parsed data from TransferSingle event.

Event: TransferSingle(operator, from, to, id, value)

Attributes:

| Name               | Type  | Description                         |
| ------------------ | ----- | ----------------------------------- |
| `operator`         | `str` | Address that triggered the transfer |
| `from_addr`        | `str` | Sender address                      |
| `to_addr`          | `str` | Recipient address                   |
| `token_id`         | `int` | ERC-1155 token ID                   |
| `value`            | `int` | Amount transferred                  |
| `contract_address` | `str` | Contract that emitted event         |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PolymarketSDK

```
PolymarketSDK(
    config: PolymarketConfig, web3: Any | None = None
)
```

Unified SDK for Polymarket prediction market operations.

Orchestrates both CLOB API client (off-chain order management) and CTF SDK (on-chain token operations) to provide a seamless trading experience.

Key features:

- Lazy credential creation: API credentials are created on first authenticated request
- Unified market lookups: Find markets by ID, slug, or condition ID
- Automatic approval checking: Ensure all token approvals are set up
- Convenience methods: Common operations wrapped in simple methods

Attributes:

| Name     | Type | Description                                   |
| -------- | ---- | --------------------------------------------- |
| `config` |      | Polymarket configuration                      |
| `clob`   |      | CLOB API client for order management          |
| `ctf`    |      | CTF SDK for on-chain operations               |
| `web3`   |      | Web3 instance for on-chain queries (optional) |

Thread Safety

This class is NOT thread-safe. Use separate instances per thread.

Example

> > > config = PolymarketConfig.from_env() web3 = Web3(Web3.HTTPProvider(rpc_url)) sdk = PolymarketSDK(config, web3)
> > >
> > > #### Get market data
> > >
> > > market = sdk.get_market_by_slug("btc-100k") print(f"YES: {market.yes_price}, NO: {market.no_price}")
> > >
> > > #### Check and set up approvals
> > >
> > > approval_txs = sdk.ensure_allowances() if approval_txs: ... print(f"Need {len(approval_txs)} approval(s)")

Initialize Polymarket SDK.

Parameters:

| Name     | Type               | Description                                   | Default                                                                                             |
| -------- | ------------------ | --------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| `config` | `PolymarketConfig` | Polymarket configuration with wallet and keys | *required*                                                                                          |
| `web3`   | \`Any              | None\`                                        | Optional Web3 instance for on-chain operations. Required for allowance checking and CTF operations. |

#### credentials

```
credentials: ApiCredentials | None
```

Get current API credentials.

#### close

```
close() -> None
```

Close SDK and release resources.

#### get_or_create_credentials

```
get_or_create_credentials() -> ApiCredentials
```

Get existing credentials or create new ones lazily.

This method is called automatically when making authenticated requests. It first attempts to derive existing credentials, and if that fails, creates new ones.

Returns:

| Type             | Description                                         |
| ---------------- | --------------------------------------------------- |
| `ApiCredentials` | ApiCredentials with api_key, secret, and passphrase |

Raises:

| Type                            | Description                  |
| ------------------------------- | ---------------------------- |
| `PolymarketAuthenticationError` | If credential creation fails |

#### get_market_by_slug

```
get_market_by_slug(slug: str) -> GammaMarket
```

Get market by URL slug.

Convenience method that searches for a market by its URL slug and raises an error if not found.

Parameters:

| Name   | Type  | Description                                             | Default    |
| ------ | ----- | ------------------------------------------------------- | ---------- |
| `slug` | `str` | Market URL slug (e.g., "will-bitcoin-exceed-100k-2025") | *required* |

Returns:

| Type          | Description        |
| ------------- | ------------------ |
| `GammaMarket` | GammaMarket object |

Raises:

| Type                            | Description         |
| ------------------------------- | ------------------- |
| `PolymarketMarketNotFoundError` | If market not found |

Example

> > > market = sdk.get_market_by_slug("btc-100k") print(market.question)

#### get_market_by_condition_id

```
get_market_by_condition_id(
    condition_id: str,
) -> GammaMarket
```

Get market by CTF condition ID.

Parameters:

| Name           | Type  | Description              | Default    |
| -------------- | ----- | ------------------------ | ---------- |
| `condition_id` | `str` | CTF condition ID (0x...) | *required* |

Returns:

| Type          | Description        |
| ------------- | ------------------ |
| `GammaMarket` | GammaMarket object |

Raises:

| Type                            | Description         |
| ------------------------------- | ------------------- |
| `PolymarketMarketNotFoundError` | If market not found |

Example

> > > market = sdk.get_market_by_condition_id("0x9915bea...")

#### get_market_by_token_id

```
get_market_by_token_id(token_id: str) -> GammaMarket
```

Get market by CLOB token ID.

Parameters:

| Name       | Type  | Description                     | Default    |
| ---------- | ----- | ------------------------------- | ---------- |
| `token_id` | `str` | CLOB token ID (YES or NO token) | *required* |

Returns:

| Type          | Description        |
| ------------- | ------------------ |
| `GammaMarket` | GammaMarket object |

Raises:

| Type                            | Description         |
| ------------------------------- | ------------------- |
| `PolymarketMarketNotFoundError` | If market not found |

Example

> > > market = sdk.get_market_by_token_id("19045189...")

#### get_yes_no_prices

```
get_yes_no_prices(
    market_id: str,
) -> tuple[Decimal, Decimal]
```

Get YES and NO prices for a market.

Fetches the market and returns the current prices for both YES and NO outcomes.

Parameters:

| Name        | Type  | Description | Default    |
| ----------- | ----- | ----------- | ---------- |
| `market_id` | `str` | Market ID   | *required* |

Returns:

| Type                      | Description                    |
| ------------------------- | ------------------------------ |
| `tuple[Decimal, Decimal]` | Tuple of (yes_price, no_price) |

Example

> > > yes_price, no_price = sdk.get_yes_no_prices("12345") print(f"YES: ${yes_price}, NO: ${no_price}")

#### get_prices_by_slug

```
get_prices_by_slug(slug: str) -> tuple[Decimal, Decimal]
```

Get YES and NO prices for a market by slug.

Parameters:

| Name   | Type  | Description     | Default    |
| ------ | ----- | --------------- | ---------- |
| `slug` | `str` | Market URL slug | *required* |

Returns:

| Type                      | Description                    |
| ------------------------- | ------------------------------ |
| `tuple[Decimal, Decimal]` | Tuple of (yes_price, no_price) |

Example

> > > yes_price, no_price = sdk.get_prices_by_slug("btc-100k")

#### ensure_allowances

```
ensure_allowances() -> list[TransactionData]
```

Build transactions to ensure all necessary approvals.

Checks current allowance status and returns a list of transactions needed to set up all required approvals for trading on Polymarket.

This includes:

- USDC approval for CTF Exchange
- USDC approval for Neg Risk Exchange
- ERC-1155 approval for CTF Exchange
- ERC-1155 approval for Neg Risk Adapter

Returns:

| Type                    | Description                                       |
| ----------------------- | ------------------------------------------------- |
| `list[TransactionData]` | List of TransactionData for any needed approvals. |
| `list[TransactionData]` | Empty list if all approvals are already in place. |

Raises:

| Type         | Description                        |
| ------------ | ---------------------------------- |
| `ValueError` | If web3 instance is not configured |

Example

> > > txs = sdk.ensure_allowances() if txs: ... print(f"Need {len(txs)} approval(s)") ... for tx in txs: ... # Sign and submit transaction ... pass

#### check_allowances

```
check_allowances() -> AllowanceStatus
```

Check all relevant token allowances.

Queries USDC allowances and ERC-1155 operator approvals needed for trading on Polymarket.

Returns:

| Type              | Description                                    |
| ----------------- | ---------------------------------------------- |
| `AllowanceStatus` | AllowanceStatus with all allowance information |

Raises:

| Type         | Description                        |
| ------------ | ---------------------------------- |
| `ValueError` | If web3 instance is not configured |

Example

> > > status = sdk.check_allowances() print(f"USDC approved: {status.usdc_approved_ctf_exchange}") print(f"Fully approved: {status.fully_approved}")

#### get_usdc_balance

```
get_usdc_balance() -> int
```

Get USDC balance for configured wallet.

Returns:

| Type  | Description                             |
| ----- | --------------------------------------- |
| `int` | USDC balance in base units (6 decimals) |

Raises:

| Type         | Description                        |
| ------------ | ---------------------------------- |
| `ValueError` | If web3 instance is not configured |

Example

> > > balance = sdk.get_usdc_balance() print(f"USDC balance: {balance / 1e6}")

#### get_position_balance

```
get_position_balance(token_id: int) -> int
```

Get ERC-1155 position token balance.

Parameters:

| Name       | Type  | Description                        | Default    |
| ---------- | ----- | ---------------------------------- | ---------- |
| `token_id` | `int` | Conditional token ID (position ID) | *required* |

Returns:

| Type  | Description                 |
| ----- | --------------------------- |
| `int` | Token balance in base units |

Raises:

| Type         | Description                        |
| ------------ | ---------------------------------- |
| `ValueError` | If web3 instance is not configured |

Example

> > > balance = sdk.get_position_balance(token_id) print(f"Position: {balance / 1e6} shares")

### ModelPredictionProvider

```
ModelPredictionProvider(
    model: Callable | None = None,
    confidence_calibration: float = 1.0,
    threshold_bullish: float = 0.6,
    threshold_bearish: float = 0.4,
)
```

Signal provider that wraps a machine learning model.

This provider allows integration of custom ML models that predict market outcomes. The model should output a probability for the YES outcome, which is then converted to a SignalResult.

Attributes:

| Name                     | Type | Description                                                         |
| ------------------------ | ---- | ------------------------------------------------------------------- |
| `model`                  |      | The prediction model (any callable returning float between 0 and 1) |
| `confidence_calibration` |      | Calibration factor for model confidence                             |

Example

```
from sklearn.linear_model import LogisticRegression

# Train your model
model = LogisticRegression()
model.fit(X_train, y_train)

# Wrap in provider
def predict_fn(market_id, features):
    return model.predict_proba([features])[0][1]

provider = ModelPredictionProvider(model=predict_fn)
signal = provider.get_signal("market-123", features=[0.5, 0.3, 0.8])
```

Initialize the model prediction provider.

Parameters:

| Name                     | Type       | Description                               | Default                                                                             |
| ------------------------ | ---------- | ----------------------------------------- | ----------------------------------------------------------------------------------- |
| `model`                  | \`Callable | None\`                                    | Callable that takes (market_id, \*\*kwargs) and returns probability between 0 and 1 |
| `confidence_calibration` | `float`    | Factor to scale model confidence          | `1.0`                                                                               |
| `threshold_bullish`      | `float`    | Probability above which signal is BULLISH | `0.6`                                                                               |
| `threshold_bearish`      | `float`    | Probability below which signal is BEARISH | `0.4`                                                                               |

#### get_signal

```
get_signal(market_id: str, **kwargs) -> SignalResult
```

Get signal from model prediction.

Parameters:

| Name        | Type  | Description                                      | Default    |
| ----------- | ----- | ------------------------------------------------ | ---------- |
| `market_id` | `str` | The Polymarket market ID                         | *required* |
| `**kwargs`  | `Any` | Parameters to pass to the model (e.g., features) | `{}`       |

Returns:

| Type           | Description                            |
| -------------- | -------------------------------------- |
| `SignalResult` | SignalResult based on model prediction |

### NewsAPISignalProvider

```
NewsAPISignalProvider(
    api_key: str | None = None,
    base_url: str = "https://newsapi.org/v2",
    sentiment_threshold: float = 0.3,
)
```

Example signal provider using news sentiment analysis.

This is a reference implementation showing how to build a news-based signal provider. In production, you would integrate with a real news API (e.g., NewsAPI, Aylien, or a custom NLP pipeline).

The provider analyzes news headlines and articles related to the market question, extracts sentiment, and converts it to a trading signal.

Attributes:

| Name                  | Type | Description                                  |
| --------------------- | ---- | -------------------------------------------- |
| `api_key`             |      | API key for the news service                 |
| `base_url`            |      | Base URL for the news API                    |
| `sentiment_threshold` |      | Minimum sentiment score to generate a signal |

Example

provider = NewsAPISignalProvider(api_key="your-api-key") signal = provider.get_signal( "market-123", question="Will Bitcoin reach $100k by end of 2024?" )

Initialize the news signal provider.

Parameters:

| Name                  | Type    | Description                                               | Default                                  |
| --------------------- | ------- | --------------------------------------------------------- | ---------------------------------------- |
| `api_key`             | \`str   | None\`                                                    | API key for NewsAPI (or similar service) |
| `base_url`            | `str`   | Base URL for the news API                                 | `'https://newsapi.org/v2'`               |
| `sentiment_threshold` | `float` | Minimum absolute sentiment to generate non-neutral signal | `0.3`                                    |

#### get_signal

```
get_signal(market_id: str, **kwargs) -> SignalResult
```

Get signal based on news sentiment.

This example implementation demonstrates the pattern. In production, replace \_analyze_sentiment with actual API calls and NLP analysis.

Parameters:

| Name        | Type  | Description                                                                                                                                                                    | Default    |
| ----------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `market_id` | `str` | The Polymarket market ID                                                                                                                                                       | *required* |
| `**kwargs`  | `Any` | Optional parameters: - question: Market question text for keyword extraction - keywords: Explicit keywords to search for - lookback_hours: How far back to search (default 24) | `{}`       |

Returns:

| Type           | Description                                                |
| -------------- | ---------------------------------------------------------- |
| `SignalResult` | SignalResult with sentiment-based direction and confidence |

### PredictionSignal

Bases: `Protocol`

Protocol for prediction market signal providers.

Any class implementing this protocol can be used as a signal provider. The protocol is runtime-checkable, so isinstance() can be used.

Example

class MyProvider: def get_signal(self, market_id: str, \*\*kwargs) -> SignalResult: return SignalResult(SignalDirection.BULLISH, 0.8)

provider = MyProvider() assert isinstance(provider, PredictionSignal) # True

#### get_signal

```
get_signal(market_id: str, **kwargs) -> SignalResult
```

Get signal for a specific market.

Parameters:

| Name        | Type  | Description                                             | Default    |
| ----------- | ----- | ------------------------------------------------------- | ---------- |
| `market_id` | `str` | The Polymarket market ID to analyze                     | *required* |
| `**kwargs`  | `Any` | Additional parameters (market question, end date, etc.) | `{}`       |

Returns:

| Type           | Description                                |
| -------------- | ------------------------------------------ |
| `SignalResult` | SignalResult with direction and confidence |

### SignalDirection

Bases: `StrEnum`

Direction of a prediction signal.

BULLISH: Expect the YES outcome to increase in probability BEARISH: Expect the YES outcome to decrease in probability (NO increases) NEUTRAL: No clear directional signal

### SignalResult

```
SignalResult(
    direction: SignalDirection,
    confidence: float,
    timestamp: datetime = (lambda: datetime.now(UTC))(),
    metadata: dict | None = None,
    source: str | None = None,
    raw_score: float | None = None,
)
```

Result from a signal provider.

Represents the output of a signal analysis with direction, confidence, and optional metadata for debugging and auditing.

Attributes:

| Name         | Type              | Description                                                |
| ------------ | ----------------- | ---------------------------------------------------------- |
| `direction`  | `SignalDirection` | The predicted direction (BULLISH, BEARISH, NEUTRAL)        |
| `confidence` | `float`           | Confidence level from 0.0 (no confidence) to 1.0 (certain) |
| `timestamp`  | `datetime`        | When the signal was generated (defaults to now)            |
| `metadata`   | \`dict            | None\`                                                     |
| `source`     | \`str             | None\`                                                     |
| `raw_score`  | \`float           | None\`                                                     |

Example

result = SignalResult( direction=SignalDirection.BULLISH, confidence=0.85, source="news_sentiment", metadata={"headline": "Positive developments..."} )

#### __post_init__

```
__post_init__() -> None
```

Validate confidence is in valid range.

#### to_dict

```
to_dict() -> dict
```

Convert to dictionary for serialization.

#### from_dict

```
from_dict(data: dict) -> SignalResult
```

Create from dictionary.

#### neutral

```
neutral(
    source: str | None = None, reason: str | None = None
) -> SignalResult
```

Create a neutral signal with low confidence.

Useful for error cases or when no signal is available.

### SocialSentimentProvider

```
SocialSentimentProvider(
    platforms: list[str] | None = None,
    min_engagement: int = 10,
    sentiment_model: str = "vader",
)
```

Example signal provider using social media sentiment.

This is a reference implementation showing how to build a social media signal provider. In production, you would integrate with social APIs (e.g., Twitter/X API, Reddit API) or social listening platforms.

The provider analyzes social media posts and discussions related to the market topic, extracts sentiment and engagement metrics, and converts them to a trading signal.

Attributes:

| Name              | Type | Description                                          |
| ----------------- | ---- | ---------------------------------------------------- |
| `platforms`       |      | List of platforms to analyze (twitter, reddit, etc.) |
| `min_engagement`  |      | Minimum engagement score to consider                 |
| `sentiment_model` |      | Sentiment analysis approach to use                   |

Example

provider = SocialSentimentProvider(platforms=["twitter", "reddit"]) signal = provider.get_signal( "market-123", topic="Bitcoin ETF approval", hashtags=["#Bitcoin", "#ETF"] )

Initialize the social sentiment provider.

Parameters:

| Name              | Type        | Description                                              | Default                                               |
| ----------------- | ----------- | -------------------------------------------------------- | ----------------------------------------------------- |
| `platforms`       | \`list[str] | None\`                                                   | Platforms to analyze (default: ["twitter", "reddit"]) |
| `min_engagement`  | `int`       | Minimum engagement (likes, upvotes) to consider          | `10`                                                  |
| `sentiment_model` | `str`       | Model for sentiment analysis ("vader", "textblob", "ml") | `'vader'`                                             |

#### get_signal

```
get_signal(market_id: str, **kwargs) -> SignalResult
```

Get signal based on social media sentiment.

This example implementation demonstrates the pattern. In production, replace with actual social API integration.

Parameters:

| Name        | Type  | Description                                                                                                                          | Default    |
| ----------- | ----- | ------------------------------------------------------------------------------------------------------------------------------------ | ---------- |
| `market_id` | `str` | The Polymarket market ID                                                                                                             | *required* |
| `**kwargs`  | `Any` | Optional parameters: - topic: Topic to search for - hashtags: Hashtags to track - lookback_hours: How far back to search (default 6) | `{}`       |

Returns:

| Type           | Description                                                       |
| -------------- | ----------------------------------------------------------------- |
| `SignalResult` | SignalResult with social-sentiment-based direction and confidence |

### calculate_inventory_skew

```
calculate_inventory_skew(
    position: Decimal, max_position: Decimal
) -> Decimal
```

Calculate inventory skew factor based on current position.

The skew factor represents how much the position deviates from neutral as a proportion of the maximum allowed position. It's used to adjust bid/ask prices to encourage trades that reduce inventory.

Returns:

| Type      | Description                                                  |
| --------- | ------------------------------------------------------------ |
| `Decimal` | Skew factor from -1.0 (max short) to +1.0 (max long).        |
| `Decimal` | Positive: Long position, should tighten asks and widen bids  |
| `Decimal` | Negative: Short position, should tighten bids and widen asks |
| `Decimal` | Zero: Neutral position, symmetric quotes                     |

Parameters:

| Name           | Type      | Description                                                    | Default    |
| -------------- | --------- | -------------------------------------------------------------- | ---------- |
| `position`     | `Decimal` | Current position in shares. Positive = long, negative = short. | *required* |
| `max_position` | `Decimal` | Maximum allowed position size (absolute value).                | *required* |

Raises:

| Type         | Description                     |
| ------------ | ------------------------------- |
| `ValueError` | If max_position is not positive |

Example

> > > skew = calculate_inventory_skew(Decimal("500"), Decimal("1000")) print(f"Skew: {skew}") # 0.5 (50% of max long position) Skew: 0.5
> > >
> > > skew = calculate_inventory_skew(Decimal("-250"), Decimal("1000")) print(f"Skew: {skew}") # -0.25 (25% of max short position) Skew: -0.25

### calculate_optimal_spread

```
calculate_optimal_spread(
    orderbook: OrderBook,
    inventory: Decimal,
    risk_params: RiskParameters,
) -> tuple[Decimal, Decimal]
```

Calculate optimal bid and ask prices based on inventory and market state.

Uses the Avellaneda-Stoikov market making model adapted for prediction markets. The spread is adjusted based on:

1. Base spread requirement (minimum profit margin)
1. Inventory skew (encourage position reduction)
1. Market volatility (wider spreads in volatile markets)

The algorithm:

1. Calculate mid price from orderbook
1. Calculate base half-spread
1. Apply inventory skew adjustment
1. Apply volatility adjustment
1. Clamp to valid price range [0.01, 0.99]

Parameters:

| Name          | Type             | Description                                          | Default    |
| ------------- | ---------------- | ---------------------------------------------------- | ---------- |
| `orderbook`   | `OrderBook`      | Current orderbook with bids and asks                 | *required* |
| `inventory`   | `Decimal`        | Current position (positive = long, negative = short) | *required* |
| `risk_params` | `RiskParameters` | Risk management parameters                           | *required* |

Returns:

| Type      | Description                                                  |
| --------- | ------------------------------------------------------------ |
| `Decimal` | Tuple of (bid_price, ask_price) representing optimal quotes. |
| `Decimal` | Returns (MIN_PRICE, MAX_PRICE) if orderbook is empty.        |

Example

> > > orderbook = client.get_orderbook(token_id) risk_params = RiskParameters(base_spread=Decimal("0.02")) bid, ask = calculate_optimal_spread(orderbook, Decimal("100"), risk_params) print(f"Quote: {bid} - {ask}")

### generate_quote_ladder

```
generate_quote_ladder(
    mid_price: Decimal,
    spread: Decimal,
    num_levels: int,
    size_per_level: Decimal,
    tick_size: Decimal = Decimal("0.01"),
    skew: Decimal = Decimal("0"),
) -> list[Quote]
```

Generate a quote ladder with multiple price levels.

Creates a symmetric ladder of bids and asks around the mid price, with optional skew adjustment for inventory management.

The ladder places quotes at increasing distances from mid:

- Level 1: mid +/- spread/2
- Level 2: mid +/- spread
- Level 3: mid +/- spread\*1.5
- etc.

Parameters:

| Name             | Type      | Description                                                                                                                                                      | Default           |
| ---------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| `mid_price`      | `Decimal` | Center price for the ladder                                                                                                                                      | *required*        |
| `spread`         | `Decimal` | Base spread between best bid and ask                                                                                                                             | *required*        |
| `num_levels`     | `int`     | Number of price levels on each side (1-10)                                                                                                                       | *required*        |
| `size_per_level` | `Decimal` | Size to quote at each price level                                                                                                                                | *required*        |
| `tick_size`      | `Decimal` | Minimum price increment (default 0.01)                                                                                                                           | `Decimal('0.01')` |
| `skew`           | `Decimal` | Inventory skew factor (-1 to +1) to shift the ladder. Positive skew shifts asks closer (encourage selling). Negative skew shifts bids closer (encourage buying). | `Decimal('0')`    |

Returns:

| Type          | Description                                             |
| ------------- | ------------------------------------------------------- |
| `list[Quote]` | List of Quote objects, sorted by price (highest first). |
| `list[Quote]` | Bids have side="BUY", asks have side="SELL".            |

Raises:

| Type         | Description                                                                     |
| ------------ | ------------------------------------------------------------------------------- |
| `ValueError` | If num_levels is not between 1 and 10, or if parameters produce invalid prices. |

Example

> > > quotes = generate_quote_ladder( ... mid_price=Decimal("0.50"), ... spread=Decimal("0.02"), ... num_levels=3, ... size_per_level=Decimal("10"), ... ) for q in quotes: ... print(f"{q.side} {q.size} @ {q.price}") SELL 10 @ 0.53 SELL 10 @ 0.52 SELL 10 @ 0.51 BUY 10 @ 0.49 BUY 10 @ 0.48 BUY 10 @ 0.47

### should_requote

```
should_requote(
    current_quotes: list[Quote],
    orderbook: OrderBook,
    threshold: Decimal = Decimal("0.01"),
) -> bool
```

Determine if quotes should be updated based on market changes.

Compares current quotes against the orderbook to decide if requoting is necessary. Requoting is recommended when:

1. The market has moved significantly (mid price change > threshold)
1. Our quotes are no longer at competitive prices
1. Our quotes have been fully filled (detected by absence in orderbook)

This function helps avoid excessive requoting while ensuring quotes remain competitive.

Parameters:

| Name             | Type          | Description                                                                                                                        | Default           |
| ---------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----------------- |
| `current_quotes` | `list[Quote]` | List of currently active quotes                                                                                                    | *required*        |
| `orderbook`      | `OrderBook`   | Current orderbook state                                                                                                            | *required*        |
| `threshold`      | `Decimal`     | Price movement threshold that triggers requote (default 0.01). A threshold of 0.01 means requote if mid price moves by 1% or more. | `Decimal('0.01')` |

Returns:

| Type   | Description                                        |
| ------ | -------------------------------------------------- |
| `bool` | True if quotes should be updated, False otherwise. |

Example

> > > quotes = [Quote(Decimal("0.49"), Decimal("10"), "BUY"), ... Quote(Decimal("0.51"), Decimal("10"), "SELL")] orderbook = client.get_orderbook(token_id) if should_requote(quotes, orderbook, threshold=Decimal("0.01")): ... # Cancel and replace quotes ... pass

### aggregate_signals

```
aggregate_signals(
    signals: list[SignalResult],
    weights: list[float] | None = None,
    min_agreement: float = 0.0,
) -> SignalResult
```

Aggregate multiple signals into a single signal.

Combines signals from multiple providers using weighted averaging. Each signal's contribution is weighted by both its explicit weight and its confidence level.

Parameters:

| Name            | Type                 | Description                                                                                  | Default                                                   |
| --------------- | -------------------- | -------------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| `signals`       | `list[SignalResult]` | List of SignalResult objects to aggregate                                                    | *required*                                                |
| `weights`       | \`list[float]        | None\`                                                                                       | Optional weights for each signal (default: equal weights) |
| `min_agreement` | `float`              | Minimum fraction of signals that must agree on direction for non-neutral result (0.0 to 1.0) | `0.0`                                                     |

Returns:

| Type           | Description             |
| -------------- | ----------------------- |
| `SignalResult` | Aggregated SignalResult |

Example

signals = [ SignalResult(SignalDirection.BULLISH, 0.8, source="news"), SignalResult(SignalDirection.BULLISH, 0.6, source="social"), SignalResult(SignalDirection.NEUTRAL, 0.5, source="model"), ] result = aggregate_signals(signals)

#### result.direction = BULLISH (2/3 agreement)

#### result.confidence = weighted average of confidences

Note

- Empty signals list returns neutral with 0.5 confidence
- Signals with higher confidence have more influence
- Direction is determined by vote weighted by confidence
- Final confidence is the average confidence of the winning direction

### combine_with_market_price

```
combine_with_market_price(
    signal: SignalResult,
    current_price: Decimal,
    edge_threshold: Decimal = Decimal("0.05"),
) -> SignalResult
```

Combine signal with market price to calculate expected edge.

Adjusts signal confidence based on the potential edge between the signal's implied probability and the current market price.

Parameters:

| Name             | Type           | Description                                | Default           |
| ---------------- | -------------- | ------------------------------------------ | ----------------- |
| `signal`         | `SignalResult` | The signal to evaluate                     | *required*        |
| `current_price`  | `Decimal`      | Current YES token price (between 0 and 1)  | *required*        |
| `edge_threshold` | `Decimal`      | Minimum edge required for confident signal | `Decimal('0.05')` |

Returns:

| Type           | Description                              |
| -------------- | ---------------------------------------- |
| `SignalResult` | Adjusted SignalResult with edge metadata |

Example

signal = SignalResult(SignalDirection.BULLISH, 0.8) current_price = Decimal("0.50") # Market thinks 50/50 adjusted = combine_with_market_price(signal, current_price)

#### If signal says bullish with 80% confidence, implied prob ~65-70%

#### Edge = 0.65 - 0.50 = 0.15 (15% edge)

# Raydium

Connector for Raydium protocol.

## almanak.framework.connectors.raydium

Raydium CLMM concentrated liquidity connector.

Provides LP operations on Raydium CLMM pools on Solana:

- Open concentrated liquidity positions
- Close positions (decrease liquidity + burn NFT)

Unlike Jupiter/Kamino (REST API), Raydium CLMM builds instructions locally using `solders` and submits via SolanaExecutionPlanner.

### RaydiumAdapter

```
RaydiumAdapter(
    config: RaydiumConfig,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Raydium CLMM integration with the Intent system.

Converts LP intents to ActionBundles containing serialized Solana VersionedTransactions built from Raydium CLMM instructions.

Example

config = RaydiumConfig(wallet_address="your-solana-pubkey") adapter = RaydiumAdapter(config)

intent = LPOpenIntent( pool="SOL/USDC/60", amount0=Decimal("1"), amount1=Decimal("150"), range_lower=Decimal("100"), range_upper=Decimal("200"), protocol="raydium_clmm", ) bundle = adapter.compile_lp_open_intent(intent)

#### compile_lp_open_intent

```
compile_lp_open_intent(
    intent: LPOpenIntent,
) -> ActionBundle
```

Compile an LPOpenIntent to an ActionBundle.

Builds Raydium CLMM openPosition instructions, serializes them into a VersionedTransaction, and wraps in an ActionBundle.

Parameters:

| Name     | Type           | Description                  | Default    |
| -------- | -------------- | ---------------------------- | ---------- |
| `intent` | `LPOpenIntent` | The LPOpenIntent to compile. | *required* |

Returns:

| Type           | Description                                               |
| -------------- | --------------------------------------------------------- |
| `ActionBundle` | ActionBundle containing serialized Solana transaction(s). |

#### compile_lp_close_intent

```
compile_lp_close_intent(
    intent: LPCloseIntent,
) -> ActionBundle
```

Compile an LPCloseIntent to an ActionBundle.

Decreases all liquidity from the position and closes it.

Parameters:

| Name     | Type            | Description                   | Default    |
| -------- | --------------- | ----------------------------- | ---------- |
| `intent` | `LPCloseIntent` | The LPCloseIntent to compile. | *required* |

Returns:

| Type           | Description                                               |
| -------------- | --------------------------------------------------------- |
| `ActionBundle` | ActionBundle containing serialized Solana transaction(s). |

### RaydiumConfig

```
RaydiumConfig(wallet_address: str, rpc_url: str = '')
```

Configuration for Raydium adapter.

Attributes:

| Name             | Type | Description                                         |
| ---------------- | ---- | --------------------------------------------------- |
| `wallet_address` |      | Solana wallet public key (Base58).                  |
| `rpc_url`        |      | Solana RPC endpoint URL (for pool queries via RPC). |

### RaydiumAPIError

```
RaydiumAPIError(
    message: str, status_code: int = 0, endpoint: str = ""
)
```

Bases: `RaydiumError`

Error communicating with the Raydium API.

### RaydiumConfigError

```
RaydiumConfigError(message: str, parameter: str = '')
```

Bases: `RaydiumError`

Invalid Raydium configuration.

### RaydiumError

Bases: `Exception`

Base exception for Raydium operations.

### RaydiumPoolError

Bases: `RaydiumError`

Error with pool state or operations.

### RaydiumTickError

Bases: `RaydiumError`

Error with tick calculations.

### RaydiumPool

```
RaydiumPool(
    address: str,
    mint_a: str,
    mint_b: str,
    symbol_a: str = "",
    symbol_b: str = "",
    decimals_a: int = 9,
    decimals_b: int = 6,
    tick_spacing: int = 60,
    current_price: float = 0.0,
    tvl: float = 0.0,
    vault_a: str = "",
    vault_b: str = "",
    amm_config: str = "",
    fee_rate: int = 3000,
    observation_address: str = "",
    program_id: str = "",
    raw_response: dict[str, Any] = dict(),
)
```

Raydium CLMM pool information.

Can be constructed from the Raydium API response or on-chain data.

Attributes:

| Name                  | Type    | Description                                    |
| --------------------- | ------- | ---------------------------------------------- |
| `address`             | `str`   | Pool state account address (Base58).           |
| `mint_a`              | `str`   | Token A mint address.                          |
| `mint_b`              | `str`   | Token B mint address.                          |
| `symbol_a`            | `str`   | Token A symbol (e.g., "SOL").                  |
| `symbol_b`            | `str`   | Token B symbol (e.g., "USDC").                 |
| `decimals_a`          | `int`   | Token A decimals.                              |
| `decimals_b`          | `int`   | Token B decimals.                              |
| `tick_spacing`        | `int`   | Tick spacing for this pool.                    |
| `current_price`       | `float` | Current price of token A in terms of token B.  |
| `tvl`                 | `float` | Total value locked in USD.                     |
| `vault_a`             | `str`   | Token A vault address.                         |
| `vault_b`             | `str`   | Token B vault address.                         |
| `amm_config`          | `str`   | AMM config account address.                    |
| `fee_rate`            | `int`   | Fee rate in basis points (e.g., 3000 = 0.30%). |
| `observation_address` | `str`   | Observation account address.                   |
| `program_id`          | `str`   | CLMM program ID.                               |

#### from_api_response

```
from_api_response(data: dict[str, Any]) -> RaydiumPool
```

Create from Raydium API /pools/info/list response item.

### RaydiumPosition

```
RaydiumPosition(
    nft_mint: str,
    pool_address: str,
    tick_lower: int,
    tick_upper: int,
    liquidity: int = 0,
    token_fees_owed_a: int = 0,
    token_fees_owed_b: int = 0,
    personal_position_address: str = "",
)
```

Raydium CLMM position (owned by the user).

Attributes:

| Name                        | Type  | Description                        |
| --------------------------- | ----- | ---------------------------------- |
| `nft_mint`                  | `str` | Position NFT mint address.         |
| `pool_address`              | `str` | Pool state account address.        |
| `tick_lower`                | `int` | Lower tick boundary.               |
| `tick_upper`                | `int` | Upper tick boundary.               |
| `liquidity`                 | `int` | Current liquidity in the position. |
| `token_fees_owed_a`         | `int` | Accumulated fees for token A.      |
| `token_fees_owed_b`         | `int` | Accumulated fees for token B.      |
| `personal_position_address` | `str` | PersonalPositionState PDA address. |

### RaydiumTransactionBundle

```
RaydiumTransactionBundle(
    transactions: list[str],
    action: str,
    position_nft_mint: str = "",
    metadata: dict[str, Any] = dict(),
)
```

Bundle of serialized transactions for a Raydium operation.

Attributes:

| Name                | Type             | Description                                                |
| ------------------- | ---------------- | ---------------------------------------------------------- |
| `transactions`      | `list[str]`      | List of base64-encoded VersionedTransactions.              |
| `action`            | `str`            | Action type ("open_position", "increase_liquidity", etc.). |
| `position_nft_mint` | `str`            | NFT mint address (for open_position).                      |
| `metadata`          | `dict[str, Any]` | Additional metadata.                                       |

### RaydiumReceiptParser

```
RaydiumReceiptParser(**kwargs: Any)
```

Parser for Raydium CLMM transaction receipts.

Extracts position IDs, liquidity amounts, and token balances from Solana transaction receipts.

Supports the extraction methods required by ResultEnricher:

- extract_position_id(receipt) -> str | None
- extract_liquidity(receipt) -> dict | None
- extract_lp_close_data(receipt) -> dict | None

Extraction approach:

1. Parse log messages for Raydium program events
1. Use preTokenBalances/postTokenBalances for actual amounts
1. Look for NFT mint in innerInstructions

Initialize RaydiumReceiptParser.

Parameters:

| Name       | Type  | Description                                            | Default |
| ---------- | ----- | ------------------------------------------------------ | ------- |
| `**kwargs` | `Any` | Keyword arguments from receipt_registry (e.g., chain). | `{}`    |

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> dict[str, Any]
```

Parse a receipt for ReceiptParser protocol compatibility.

Parameters:

| Name      | Type             | Description                     | Default    |
| --------- | ---------------- | ------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Solana transaction receipt dict | *required* |

Returns:

| Type             | Description              |
| ---------------- | ------------------------ |
| `dict[str, Any]` | Dict with parsed LP data |

#### extract_position_id

```
extract_position_id(receipt: dict[str, Any]) -> str | None
```

Extract the position NFT mint from a Raydium LP open receipt.

The position NFT is minted during openPosition. We find it by looking for a new token account with amount=1 in postTokenBalances that wasn't in preTokenBalances.

Parameters:

| Name      | Type             | Description                      | Default    |
| --------- | ---------------- | -------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Solana transaction receipt dict. | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`str | None\`      |

#### extract_liquidity

```
extract_liquidity(
    receipt: dict[str, Any],
) -> dict[str, Any] | None
```

Extract liquidity data from an LP open/increase receipt.

Uses balance deltas to determine actual deposited amounts.

Parameters:

| Name      | Type             | Description                      | Default    |
| --------- | ---------------- | -------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Solana transaction receipt dict. | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`dict[str, Any] | None\`      |

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> dict[str, Any] | None
```

Extract LP close data from a receipt.

Uses balance deltas to determine received amounts.

Parameters:

| Name      | Type             | Description                      | Default    |
| --------- | ---------------- | -------------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Solana transaction receipt dict. | *required* |

Returns:

| Type             | Description |
| ---------------- | ----------- |
| \`dict[str, Any] | None\`      |

### RaydiumCLMMSDK

```
RaydiumCLMMSDK(
    wallet_address: str,
    base_url: str = RAYDIUM_API_BASE_URL,
    timeout: int = 30,
)
```

SDK for building Raydium CLMM instructions.

Provides methods to:

- Fetch pool data from the Raydium API
- Build Solana instructions for LP operations
- Compute PDAs for positions, tick arrays, etc.

Example

sdk = RaydiumCLMMSDK(wallet_address="your-pubkey", rpc_url="...") pool = sdk.get_pool_info("pool-address") ixs, nft_mint = sdk.build_open_position_ix( pool=pool, tick_lower=-100, tick_upper=100, amount_a=1_000_000, amount_b=500_000_000, liquidity=1000000, )

#### get_pool_info

```
get_pool_info(pool_address: str) -> RaydiumPool
```

Fetch pool information from the Raydium API.

Uses two endpoints:

- /pools/key/ids: provides vault addresses, observation IDs, lookup tables
- /pools/info/ids: provides price, TVL, volume data

Parameters:

| Name           | Type  | Description                          | Default    |
| -------------- | ----- | ------------------------------------ | ---------- |
| `pool_address` | `str` | Pool state account address (Base58). | *required* |

Returns:

| Type          | Description                         |
| ------------- | ----------------------------------- |
| `RaydiumPool` | RaydiumPool with current pool data. |

Raises:

| Type               | Description           |
| ------------------ | --------------------- |
| `RaydiumPoolError` | If pool not found.    |
| `RaydiumAPIError`  | If API request fails. |

#### find_pool_by_tokens

```
find_pool_by_tokens(
    token_a: str, token_b: str, tick_spacing: int = 60
) -> RaydiumPool | None
```

Find a CLMM pool by token pair.

Parameters:

| Name           | Type  | Description                                       | Default    |
| -------------- | ----- | ------------------------------------------------- | ---------- |
| `token_a`      | `str` | Token A mint address or symbol.                   | *required* |
| `token_b`      | `str` | Token B mint address or symbol.                   | *required* |
| `tick_spacing` | `int` | Preferred tick spacing (default: 60 = 0.30% fee). | `60`       |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`RaydiumPool | None\`      |

#### get_position_state

```
get_position_state(
    nft_mint: str, rpc_url: str
) -> RaydiumPosition
```

Query on-chain PersonalPositionState for a Raydium CLMM position.

Derives the PDA from the NFT mint, calls getAccountInfo, and parses the account data to extract tick range and liquidity.

PersonalPositionState layout (Anchor, 8-byte discriminator): [0:8] discriminator [8:9] bump (u8) [9:41] nft_mint (Pubkey) [41:73] pool_id (Pubkey) [73:77] tick_lower_index (i32 LE) [77:81] tick_upper_index (i32 LE) [81:97] liquidity (u128 LE)

Parameters:

| Name       | Type  | Description                         | Default    |
| ---------- | ----- | ----------------------------------- | ---------- |
| `nft_mint` | `str` | Position NFT mint address (Base58). | *required* |
| `rpc_url`  | `str` | Solana RPC endpoint URL.            | *required* |

Returns:

| Type              | Description                                             |
| ----------------- | ------------------------------------------------------- |
| `RaydiumPosition` | RaydiumPosition with on-chain tick range and liquidity. |

Raises:

| Type               | Description                                    |
| ------------------ | ---------------------------------------------- |
| `RaydiumPoolError` | If position account not found or data invalid. |

#### build_open_position_ix

```
build_open_position_ix(
    pool: RaydiumPool,
    tick_lower: int,
    tick_upper: int,
    amount_a_max: int,
    amount_b_max: int,
    liquidity: int,
    with_metadata: bool = True,
) -> tuple[list[Instruction], Keypair]
```

Build instructions for opening a new CLMM position.

Creates a new position NFT, initializes the PersonalPositionState, and deposits the specified amounts of token A and B.

Parameters:

| Name            | Type          | Description                                            | Default    |
| --------------- | ------------- | ------------------------------------------------------ | ---------- |
| `pool`          | `RaydiumPool` | Pool information.                                      | *required* |
| `tick_lower`    | `int`         | Lower tick boundary (must be aligned to tick spacing). | *required* |
| `tick_upper`    | `int`         | Upper tick boundary (must be aligned to tick spacing). | *required* |
| `amount_a_max`  | `int`         | Maximum amount of token A in smallest units.           | *required* |
| `amount_b_max`  | `int`         | Maximum amount of token B in smallest units.           | *required* |
| `liquidity`     | `int`         | Target liquidity amount (u128).                        | *required* |
| `with_metadata` | `bool`        | Whether to create Metaplex NFT metadata.               | `True`     |

Returns:

| Type                | Description                                        |
| ------------------- | -------------------------------------------------- |
| `list[Instruction]` | Tuple of (instructions, nft_mint_keypair).         |
| `Keypair`           | The nft_mint_keypair must be included as a signer. |

Raises:

| Type               | Description                 |
| ------------------ | --------------------------- |
| `RaydiumPoolError` | If pool data is incomplete. |

#### build_decrease_liquidity_ix

```
build_decrease_liquidity_ix(
    pool: RaydiumPool,
    position: RaydiumPosition,
    liquidity: int,
    amount_a_min: int = 0,
    amount_b_min: int = 0,
) -> list[Instruction]
```

Build instructions for removing liquidity from a position.

Parameters:

| Name           | Type              | Description                                           | Default    |
| -------------- | ----------------- | ----------------------------------------------------- | ---------- |
| `pool`         | `RaydiumPool`     | Pool information.                                     | *required* |
| `position`     | `RaydiumPosition` | Position to decrease.                                 | *required* |
| `liquidity`    | `int`             | Amount of liquidity to remove.                        | *required* |
| `amount_a_min` | `int`             | Minimum acceptable token A out (slippage protection). | `0`        |
| `amount_b_min` | `int`             | Minimum acceptable token B out (slippage protection). | `0`        |

Returns:

| Type                | Description                  |
| ------------------- | ---------------------------- |
| `list[Instruction]` | List of Solana instructions. |

#### build_close_position_ix

```
build_close_position_ix(
    position: RaydiumPosition,
) -> list[Instruction]
```

Build instructions for closing a position (burn NFT, recover rent).

The position must have zero liquidity and zero fees owed.

Parameters:

| Name       | Type              | Description        | Default    |
| ---------- | ----------------- | ------------------ | ---------- |
| `position` | `RaydiumPosition` | Position to close. | *required* |

Returns:

| Type                | Description                  |
| ------------------- | ---------------------------- |
| `list[Instruction]` | List of Solana instructions. |

#### build_open_position_transaction

```
build_open_position_transaction(
    pool: RaydiumPool,
    price_lower: float,
    price_upper: float,
    amount_a: int,
    amount_b: int,
    slippage_bps: int = 100,
) -> tuple[list[Instruction], Keypair, dict[str, Any]]
```

Build a complete open position transaction from price bounds.

High-level method that handles tick conversion, alignment, liquidity calculation, and slippage.

Parameters:

| Name           | Type          | Description                                              | Default    |
| -------------- | ------------- | -------------------------------------------------------- | ---------- |
| `pool`         | `RaydiumPool` | Pool information.                                        | *required* |
| `price_lower`  | `float`       | Lower price bound (human-readable, token_b per token_a). | *required* |
| `price_upper`  | `float`       | Upper price bound.                                       | *required* |
| `amount_a`     | `int`         | Amount of token A in smallest units.                     | *required* |
| `amount_b`     | `int`         | Amount of token B in smallest units.                     | *required* |
| `slippage_bps` | `int`         | Slippage tolerance in basis points (default: 100 = 1%).  | `100`      |

Returns:

| Type                                                | Description                                          |
| --------------------------------------------------- | ---------------------------------------------------- |
| `tuple[list[Instruction], Keypair, dict[str, Any]]` | Tuple of (instructions, nft_mint_keypair, metadata). |

#### build_close_position_transaction

```
build_close_position_transaction(
    pool: RaydiumPool,
    position: RaydiumPosition,
    slippage_bps: int = 100,
) -> tuple[list[Instruction], dict[str, Any]]
```

Build instructions to fully close a position.

Decreases all liquidity, then closes the position account.

Parameters:

| Name           | Type              | Description                                | Default    |
| -------------- | ----------------- | ------------------------------------------ | ---------- |
| `pool`         | `RaydiumPool`     | Pool information.                          | *required* |
| `position`     | `RaydiumPosition` | Position to close.                         | *required* |
| `slippage_bps` | `int`             | Slippage tolerance for decrease liquidity. | `100`      |

Returns:

| Type                                       | Description                            |
| ------------------------------------------ | -------------------------------------- |
| `tuple[list[Instruction], dict[str, Any]]` | Tuple of (all_instructions, metadata). |

# Spark

Connector for Spark lending protocol.

## almanak.framework.connectors.spark

Spark Connector.

This module provides an adapter for interacting with Spark lending protocol, which is an Aave V3 fork with Spark-specific addresses and configurations.

Spark is a decentralized lending protocol supporting:

- Supply assets to earn yield
- Borrow against collateral
- Variable interest rates

Supported chains:

- Ethereum

Example

from almanak.framework.connectors.spark import SparkAdapter, SparkConfig

config = SparkConfig( chain="ethereum", wallet_address="0x...", ) adapter = SparkAdapter(config)

### Supply collateral

result = adapter.supply( asset="USDC", amount=Decimal("1000"), )

### Borrow against collateral

result = adapter.borrow( asset="DAI", amount=Decimal("500"), )

### Parse transaction receipt

from almanak.framework.connectors.spark import SparkReceiptParser

parser = SparkReceiptParser() result = parser.parse_receipt(receipt)

### SparkAdapter

```
SparkAdapter(
    config: SparkConfig,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Spark lending protocol.

This adapter provides methods for interacting with Spark:

- Supply/withdraw collateral
- Borrow/repay assets

Spark is an Aave V3 fork with the same ABI but different contract addresses.

Example

config = SparkConfig( chain="ethereum", wallet_address="0x...", ) adapter = SparkAdapter(config)

#### Supply DAI as collateral

result = adapter.supply("DAI", Decimal("1000"))

#### Borrow WETH

result = adapter.borrow("WETH", Decimal("0.5"))

Initialize the adapter.

Parameters:

| Name             | Type            | Description           | Default                                                   |
| ---------------- | --------------- | --------------------- | --------------------------------------------------------- |
| `config`         | `SparkConfig`   | Adapter configuration | *required*                                                |
| `token_resolver` | \`TokenResolver | None\`                | Optional TokenResolver instance. If None, uses singleton. |

#### supply

```
supply(
    asset: str,
    amount: Decimal,
    on_behalf_of: str | None = None,
) -> TransactionResult
```

Build a supply transaction.

Parameters:

| Name           | Type      | Description            | Default                                                  |
| -------------- | --------- | ---------------------- | -------------------------------------------------------- |
| `asset`        | `str`     | Asset symbol to supply | *required*                                               |
| `amount`       | `Decimal` | Amount to supply       | *required*                                               |
| `on_behalf_of` | \`str     | None\`                 | Address to supply on behalf of (default: wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### borrow

```
borrow(
    asset: str,
    amount: Decimal,
    interest_rate_mode: int = SPARK_VARIABLE_RATE_MODE,
    on_behalf_of: str | None = None,
) -> TransactionResult
```

Build a borrow transaction.

Parameters:

| Name                 | Type      | Description                       | Default                                                  |
| -------------------- | --------- | --------------------------------- | -------------------------------------------------------- |
| `asset`              | `str`     | Asset symbol to borrow            | *required*                                               |
| `amount`             | `Decimal` | Amount to borrow                  | *required*                                               |
| `interest_rate_mode` | `int`     | Interest rate mode (2 = variable) | `SPARK_VARIABLE_RATE_MODE`                               |
| `on_behalf_of`       | \`str     | None\`                            | Address to borrow on behalf of (default: wallet_address) |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### repay

```
repay(
    asset: str,
    amount: Decimal,
    interest_rate_mode: int = SPARK_VARIABLE_RATE_MODE,
    on_behalf_of: str | None = None,
    repay_all: bool = False,
) -> TransactionResult
```

Build a repay transaction.

Parameters:

| Name                 | Type      | Description                                 | Default                                                 |
| -------------------- | --------- | ------------------------------------------- | ------------------------------------------------------- |
| `asset`              | `str`     | Asset symbol to repay                       | *required*                                              |
| `amount`             | `Decimal` | Amount to repay                             | *required*                                              |
| `interest_rate_mode` | `int`     | Interest rate mode (2 = variable)           | `SPARK_VARIABLE_RATE_MODE`                              |
| `on_behalf_of`       | \`str     | None\`                                      | Address to repay on behalf of (default: wallet_address) |
| `repay_all`          | `bool`    | If True, use MAX_UINT256 to repay full debt | `False`                                                 |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

#### withdraw

```
withdraw(
    asset: str,
    amount: Decimal,
    to: str | None = None,
    withdraw_all: bool = False,
) -> TransactionResult
```

Build a withdraw transaction.

Parameters:

| Name           | Type      | Description                              | Default                                                    |
| -------------- | --------- | ---------------------------------------- | ---------------------------------------------------------- |
| `asset`        | `str`     | Asset symbol to withdraw                 | *required*                                                 |
| `amount`       | `Decimal` | Amount to withdraw                       | *required*                                                 |
| `to`           | \`str     | None\`                                   | Address to send withdrawn assets (default: wallet_address) |
| `withdraw_all` | `bool`    | If True, use MAX_UINT256 to withdraw all | `False`                                                    |

Returns:

| Type                | Description                             |
| ------------------- | --------------------------------------- |
| `TransactionResult` | TransactionResult with transaction data |

### SparkConfig

```
SparkConfig(
    chain: str,
    wallet_address: str,
    default_slippage_bps: int = 50,
)
```

Configuration for Spark adapter.

Attributes:

| Name                   | Type  | Description                                |
| ---------------------- | ----- | ------------------------------------------ |
| `chain`                | `str` | Blockchain network (ethereum)              |
| `wallet_address`       | `str` | User wallet address                        |
| `default_slippage_bps` | `int` | Default slippage tolerance in basis points |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

### TransactionResult

```
TransactionResult(
    success: bool,
    tx_data: dict[str, Any] | None = None,
    gas_estimate: int = 0,
    description: str = "",
    error: str | None = None,
)
```

Result of a transaction build operation.

Attributes:

| Name           | Type             | Description                 |
| -------------- | ---------------- | --------------------------- |
| `success`      | `bool`           | Whether operation succeeded |
| `tx_data`      | \`dict[str, Any] | None\`                      |
| `gas_estimate` | `int`            | Estimated gas               |
| `description`  | `str`            | Human-readable description  |
| `error`        | \`str            | None\`                      |

### BorrowEventData

```
BorrowEventData(
    reserve: str,
    user: str,
    on_behalf_of: str,
    amount: Decimal,
    interest_rate_mode: int,
    borrow_rate: Decimal = Decimal("0"),
    referral_code: int = 0,
)
```

Parsed data from Borrow event.

#### is_variable_rate

```
is_variable_rate: bool
```

Check if borrow is variable rate.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### ParseResult

```
ParseResult(
    success: bool,
    supplies: list[SupplyEventData] = list(),
    withdraws: list[WithdrawEventData] = list(),
    borrows: list[BorrowEventData] = list(),
    repays: list[RepayEventData] = list(),
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
)
```

Result of parsing a receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### RepayEventData

```
RepayEventData(
    reserve: str,
    user: str,
    repayer: str,
    amount: Decimal,
    use_atokens: bool = False,
)
```

Parsed data from Repay event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SparkEventType

Bases: `Enum`

Spark event types.

### SparkReceiptParser

```
SparkReceiptParser(
    pool_addresses: set[str] | None = None, **kwargs: Any
)
```

Parser for Spark transaction receipts.

Refactored to use base infrastructure utilities for hex decoding and event registry management. Maintains pool address filtering to avoid false positives with Aave V3 events.

Initialize the parser.

Parameters:

| Name             | Type       | Description                                      | Default                                                                                                                                         |
| ---------------- | ---------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `pool_addresses` | \`set[str] | None\`                                           | Optional set of known Spark pool addresses to filter by. If not provided, uses the default SPARK_POOL_ADDRESSES. Addresses should be lowercase. |
| `**kwargs`       | `Any`      | Additional arguments (ignored for compatibility) | `{}`                                                                                                                                            |

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name      | Type             | Description              | Default    |
| --------- | ---------------- | ------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict | *required* |

Returns:

| Type          | Description                       |
| ------------- | --------------------------------- |
| `ParseResult` | ParseResult with extracted events |

#### parse_supply

```
parse_supply(log: dict[str, Any]) -> SupplyEventData | None
```

Parse a Supply event from a single log entry.

#### parse_borrow

```
parse_borrow(log: dict[str, Any]) -> BorrowEventData | None
```

Parse a Borrow event from a single log entry.

#### parse_repay

```
parse_repay(log: dict[str, Any]) -> RepayEventData | None
```

Parse a Repay event from a single log entry.

#### parse_withdraw

```
parse_withdraw(
    log: dict[str, Any],
) -> WithdrawEventData | None
```

Parse a Withdraw event from a single log entry.

#### is_spark_event

```
is_spark_event(topic: str | bytes) -> bool
```

Check if a topic is a known Spark event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                          |
| ------ | ------------------------------------ |
| `bool` | True if topic is a known Spark event |

#### get_event_type

```
get_event_type(topic: str | bytes) -> SparkEventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type             | Description           |
| ---------------- | --------------------- |
| `SparkEventType` | Event type or UNKNOWN |

#### is_spark_pool

```
is_spark_pool(address: str) -> bool
```

Check if an address is a known Spark pool.

#### extract_supply_amount

```
extract_supply_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract supply amount from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_withdraw_amount

```
extract_withdraw_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract withdraw amount from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_borrow_amount

```
extract_borrow_amount(
    receipt: dict[str, Any],
) -> int | None
```

Extract borrow amount from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_repay_amount

```
extract_repay_amount(receipt: dict[str, Any]) -> int | None
```

Extract repay amount from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_a_token_received

```
extract_a_token_received(
    receipt: dict[str, Any],
) -> int | None
```

Extract spToken amount received from transaction receipt.

Spark uses spTokens (similar to Aave's aTokens) to represent deposits. This extracts the amount from Transfer events (mint from zero address).

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_borrow_rate

```
extract_borrow_rate(
    receipt: dict[str, Any],
) -> Decimal | None
```

Extract borrow rate from transaction receipt.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type      | Description |
| --------- | ----------- |
| \`Decimal | None\`      |

### SupplyEventData

```
SupplyEventData(
    reserve: str,
    user: str,
    on_behalf_of: str,
    amount: Decimal,
    referral_code: int = 0,
)
```

Parsed data from Supply event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### WithdrawEventData

```
WithdrawEventData(
    reserve: str, user: str, to: str, amount: Decimal
)
```

Parsed data from Withdraw event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

# SushiSwap V3

Connector for SushiSwap V3 DEX.

## almanak.framework.connectors.sushiswap_v3

SushiSwap V3 Connector Package.

This package provides the SushiSwap V3 protocol integration for the Almanak Strategy Framework, including SDK, adapter, and receipt parser.

SushiSwap V3 is a concentrated liquidity AMM forked from Uniswap V3, deployed across multiple chains including Arbitrum, Ethereum, Base, Polygon, Avalanche, BSC, and Optimism.

Key Components:

- SushiSwapV3SDK: Low-level SDK for direct protocol interaction
- SushiSwapV3Adapter: High-level adapter for framework integration
- SushiSwapV3ReceiptParser: Transaction receipt parsing

Supported Operations:

- Token swaps (exact input and exact output)
- LP position management (mint, increase, decrease, collect)
- Quote fetching
- Pool address computation

Example

from almanak.framework.connectors.sushiswap_v3 import ( SushiSwapV3SDK, SushiSwapV3Adapter, SushiSwapV3Config, SushiSwapV3ReceiptParser, )

### Using the SDK directly

sdk = SushiSwapV3SDK(chain="arbitrum") pool = sdk.get_pool_address(weth_address, usdc_address, fee_tier=3000)

### Using the adapter

config = SushiSwapV3Config( chain="arbitrum", wallet_address="0x...", price_provider={"ETH": Decimal("3400"), "USDC": Decimal("1")}, ) adapter = SushiSwapV3Adapter(config) result = adapter.swap_exact_input("USDC", "WETH", Decimal("1000"))

### Parsing receipts

parser = SushiSwapV3ReceiptParser(chain="arbitrum") parse_result = parser.parse_receipt(receipt)

### LPResult

```
LPResult(
    success: bool,
    transactions: list[TransactionData] = list(),
    error: str | None = None,
    gas_estimate: int = 0,
    position_info: dict[str, Any] = dict(),
)
```

Result of an LP operation.

Attributes:

| Name            | Type                    | Description                                  |
| --------------- | ----------------------- | -------------------------------------------- |
| `success`       | `bool`                  | Whether the operation was built successfully |
| `transactions`  | `list[TransactionData]` | List of transactions to execute              |
| `error`         | \`str                   | None\`                                       |
| `gas_estimate`  | `int`                   | Total gas estimate for all transactions      |
| `position_info` | `dict[str, Any]`        | Additional info about the LP position        |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SushiSwapV3Adapter

```
SushiSwapV3Adapter(
    config: SushiSwapV3Config,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for SushiSwap V3 DEX protocol.

This adapter provides methods for:

- Executing token swaps (exact input and exact output)
- Building swap transactions
- Managing LP positions (mint, increase, decrease, collect)
- Handling ERC-20 approvals
- Managing slippage protection

Example

config = SushiSwapV3Config( chain="arbitrum", wallet_address="0x...", price_provider={"ETH": Decimal("3400"), "USDC": Decimal("1")}, ) adapter = SushiSwapV3Adapter(config)

#### Execute a swap

result = adapter.swap_exact_input( token_in="USDC", token_out="WETH", amount_in=Decimal("1000"), # 1000 USDC slippage_bps=50, )

#### Open LP position

lp_result = adapter.open_lp_position( token0="USDC", token1="WETH", amount0=Decimal("1000"), amount1=Decimal("0.5"), fee_tier=3000, tick_lower=-887220, tick_upper=887220, )

Initialize the adapter.

Parameters:

| Name             | Type                | Description                        | Default                                                   |
| ---------------- | ------------------- | ---------------------------------- | --------------------------------------------------------- |
| `config`         | `SushiSwapV3Config` | SushiSwap V3 adapter configuration | *required*                                                |
| `token_resolver` | \`TokenResolver     | None\`                             | Optional TokenResolver instance. If None, uses singleton. |

#### swap_exact_input

```
swap_exact_input(
    token_in: str,
    token_out: str,
    amount_in: Decimal,
    slippage_bps: int | None = None,
    fee_tier: int | None = None,
    recipient: str | None = None,
) -> SwapResult
```

Build a swap transaction with exact input amount.

This is the most common swap type where you specify exactly how much you want to spend and accept variable output.

Parameters:

| Name           | Type      | Description                                     | Default                                                    |
| -------------- | --------- | ----------------------------------------------- | ---------------------------------------------------------- |
| `token_in`     | `str`     | Input token symbol or address                   | *required*                                                 |
| `token_out`    | `str`     | Output token symbol or address                  | *required*                                                 |
| `amount_in`    | `Decimal` | Amount of input token (in token units, not wei) | *required*                                                 |
| `slippage_bps` | \`int     | None\`                                          | Slippage tolerance in basis points (default from config)   |
| `fee_tier`     | \`int     | None\`                                          | Pool fee tier (default from config)                        |
| `recipient`    | \`str     | None\`                                          | Address to receive output tokens (default: wallet_address) |

Returns:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `SwapResult` | SwapResult with transaction data |

#### swap_exact_output

```
swap_exact_output(
    token_in: str,
    token_out: str,
    amount_out: Decimal,
    slippage_bps: int | None = None,
    fee_tier: int | None = None,
    recipient: str | None = None,
) -> SwapResult
```

Build a swap transaction with exact output amount.

This swap type specifies exactly how much you want to receive and accepts variable input.

Parameters:

| Name           | Type      | Description                                      | Default                                                    |
| -------------- | --------- | ------------------------------------------------ | ---------------------------------------------------------- |
| `token_in`     | `str`     | Input token symbol or address                    | *required*                                                 |
| `token_out`    | `str`     | Output token symbol or address                   | *required*                                                 |
| `amount_out`   | `Decimal` | Amount of output token (in token units, not wei) | *required*                                                 |
| `slippage_bps` | \`int     | None\`                                           | Slippage tolerance in basis points (default from config)   |
| `fee_tier`     | \`int     | None\`                                           | Pool fee tier (default from config)                        |
| `recipient`    | \`str     | None\`                                           | Address to receive output tokens (default: wallet_address) |

Returns:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `SwapResult` | SwapResult with transaction data |

#### open_lp_position

```
open_lp_position(
    token0: str,
    token1: str,
    amount0: Decimal,
    amount1: Decimal,
    fee_tier: int | None = None,
    tick_lower: int = -887220,
    tick_upper: int = 887220,
    slippage_bps: int | None = None,
    recipient: str | None = None,
) -> LPResult
```

Build transactions to open a new LP position.

Parameters:

| Name           | Type      | Description                            | Default                                                  |
| -------------- | --------- | -------------------------------------- | -------------------------------------------------------- |
| `token0`       | `str`     | First token symbol or address          | *required*                                               |
| `token1`       | `str`     | Second token symbol or address         | *required*                                               |
| `amount0`      | `Decimal` | Amount of token0 to provide            | *required*                                               |
| `amount1`      | `Decimal` | Amount of token1 to provide            | *required*                                               |
| `fee_tier`     | \`int     | None\`                                 | Pool fee tier (default from config)                      |
| `tick_lower`   | `int`     | Lower tick bound (default: full range) | `-887220`                                                |
| `tick_upper`   | `int`     | Upper tick bound (default: full range) | `887220`                                                 |
| `slippage_bps` | \`int     | None\`                                 | Slippage tolerance in basis points (default from config) |
| `recipient`    | \`str     | None\`                                 | Address to receive the NFT (default: wallet_address)     |

Returns:

| Type       | Description                    |
| ---------- | ------------------------------ |
| `LPResult` | LPResult with transaction data |

#### close_lp_position

```
close_lp_position(
    token_id: int,
    liquidity: int,
    amount0_min: int = 0,
    amount1_min: int = 0,
    recipient: str | None = None,
) -> LPResult
```

Build transactions to close an LP position.

Parameters:

| Name          | Type  | Description                                                            | Default                                             |
| ------------- | ----- | ---------------------------------------------------------------------- | --------------------------------------------------- |
| `token_id`    | `int` | NFT token ID of the position                                           | *required*                                          |
| `liquidity`   | `int` | Amount of liquidity to remove (use position's full liquidity to close) | *required*                                          |
| `amount0_min` | `int` | Minimum amount of token0 to receive                                    | `0`                                                 |
| `amount1_min` | `int` | Minimum amount of token1 to receive                                    | `0`                                                 |
| `recipient`   | \`str | None\`                                                                 | Address to receive tokens (default: wallet_address) |

Returns:

| Type       | Description                    |
| ---------- | ------------------------------ |
| `LPResult` | LPResult with transaction data |

#### set_allowance

```
set_allowance(
    token: str, spender: str, amount: int
) -> None
```

Set cached allowance (for testing).

Parameters:

| Name      | Type  | Description      | Default    |
| --------- | ----- | ---------------- | ---------- |
| `token`   | `str` | Token address    | *required* |
| `spender` | `str` | Spender address  | *required* |
| `amount`  | `int` | Allowance amount | *required* |

#### clear_allowance_cache

```
clear_allowance_cache() -> None
```

Clear the allowance cache.

### SushiSwapV3Config

```
SushiSwapV3Config(
    chain: str,
    wallet_address: str,
    default_slippage_bps: int = 50,
    default_fee_tier: int = DEFAULT_FEE_TIER,
    deadline_seconds: int = 300,
    price_provider: dict[str, Decimal] | None = None,
    allow_placeholder_prices: bool = False,
)
```

Configuration for SushiSwapV3Adapter.

Attributes:

| Name                       | Type                 | Description                                                                                             |
| -------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------- |
| `chain`                    | `str`                | Target blockchain (ethereum, arbitrum, base, polygon, avalanche, bsc, optimism)                         |
| `wallet_address`           | `str`                | Address executing transactions                                                                          |
| `default_slippage_bps`     | `int`                | Default slippage tolerance in basis points (default 50 = 0.5%)                                          |
| `default_fee_tier`         | `int`                | Default fee tier for pools (default 3000 = 0.3%)                                                        |
| `deadline_seconds`         | `int`                | Transaction deadline in seconds (default 300 = 5 minutes)                                               |
| `price_provider`           | \`dict[str, Decimal] | None\`                                                                                                  |
| `allow_placeholder_prices` | `bool`               | If False (default), raises ValueError when no price_provider is given. Set to True ONLY for unit tests. |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapQuote

```
SwapQuote(
    token_in: str,
    token_out: str,
    amount_in: int,
    amount_out: int,
    fee_tier: int,
    sqrt_price_x96_after: int = 0,
    gas_estimate: int = SUSHISWAP_V3_GAS_ESTIMATES[
        "swap_exact_input"
    ],
    price_impact_bps: int = 0,
    effective_price: Decimal = Decimal("0"),
    quoted_at: datetime = (lambda: datetime.now(UTC))(),
)
```

Quote for a swap operation.

Attributes:

| Name                   | Type       | Description                      |
| ---------------------- | ---------- | -------------------------------- |
| `token_in`             | `str`      | Input token address              |
| `token_out`            | `str`      | Output token address             |
| `amount_in`            | `int`      | Input amount in wei              |
| `amount_out`           | `int`      | Output amount in wei             |
| `fee_tier`             | `int`      | Fee tier of the pool             |
| `sqrt_price_x96_after` | `int`      | Price after swap (sqrt format)   |
| `gas_estimate`         | `int`      | Estimated gas for the swap       |
| `price_impact_bps`     | `int`      | Price impact in basis points     |
| `effective_price`      | `Decimal`  | Effective price of the swap      |
| `quoted_at`            | `datetime` | Timestamp when quote was fetched |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapResult

```
SwapResult(
    success: bool,
    transactions: list[TransactionData] = list(),
    quote: SwapQuote | None = None,
    amount_in: int = 0,
    amount_out_minimum: int = 0,
    error: str | None = None,
    gas_estimate: int = 0,
)
```

Result of a swap operation.

Attributes:

| Name                 | Type                    | Description                             |
| -------------------- | ----------------------- | --------------------------------------- |
| `success`            | `bool`                  | Whether the swap was built successfully |
| `transactions`       | `list[TransactionData]` | List of transactions to execute         |
| `quote`              | \`SwapQuote             | None\`                                  |
| `amount_in`          | `int`                   | Actual input amount                     |
| `amount_out_minimum` | `int`                   | Minimum output amount (with slippage)   |
| `error`              | \`str                   | None\`                                  |
| `gas_estimate`       | `int`                   | Total gas estimate for all transactions |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapType

Bases: `Enum`

Type of swap operation.

### TransactionData

```
TransactionData(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
    tx_type: str = "swap",
)
```

Transaction data for execution.

Attributes:

| Name           | Type  | Description                                     |
| -------------- | ----- | ----------------------------------------------- |
| `to`           | `str` | Target contract address                         |
| `value`        | `int` | Native token value to send                      |
| `data`         | `str` | Encoded calldata                                |
| `gas_estimate` | `int` | Estimated gas                                   |
| `description`  | `str` | Human-readable description                      |
| `tx_type`      | `str` | Type of transaction (approve, swap, mint, etc.) |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### ParsedSwapResult

```
ParsedSwapResult(
    token_in: str,
    token_out: str,
    token_in_symbol: str,
    token_out_symbol: str,
    amount_in: int,
    amount_out: int,
    amount_in_decimal: Decimal,
    amount_out_decimal: Decimal,
    effective_price: Decimal,
    slippage_bps: int,
    pool_address: str,
    sqrt_price_x96_after: int = 0,
    tick_after: int = 0,
)
```

High-level swap result extracted from receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> ParsedSwapResult
```

Create from dictionary.

#### to_swap_result_payload

```
to_swap_result_payload() -> SwapResultPayload
```

Convert to SwapResultPayload for event emission.

### ParseResult

```
ParseResult(
    success: bool,
    events: list[SushiSwapV3Event] = list(),
    swap_events: list[SwapEventData] = list(),
    transfer_events: list[TransferEventData] = list(),
    swap_result: ParsedSwapResult | None = None,
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
    transaction_success: bool = True,
)
```

Result of parsing a receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SushiSwapV3Event

```
SushiSwapV3Event(
    event_type: SushiSwapV3EventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)
```

Parsed SushiSwap V3 event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> SushiSwapV3Event
```

Create from dictionary.

### SushiSwapV3EventType

Bases: `Enum`

SushiSwap V3 event types.

### SushiSwapV3ReceiptParser

```
SushiSwapV3ReceiptParser(
    chain: str = "arbitrum",
    token0_address: str | None = None,
    token1_address: str | None = None,
    token0_symbol: str | None = None,
    token1_symbol: str | None = None,
    token0_decimals: int | None = None,
    token1_decimals: int | None = None,
    quoted_price: Decimal | None = None,
    **kwargs: Any,
)
```

Parser for SushiSwap V3 transaction receipts.

Note: This parser uses HexDecoder and EventRegistry from the base module. A future refactor could inherit from BaseReceiptParser for full standardization.

This parser handles:

- Swap events (exact input and exact output)
- Mint events (new LP positions)
- Burn events (decrease liquidity)
- Collect events (claim fees/tokens)
- Transfer events (ERC-20 and ERC-721)

Example

parser = SushiSwapV3ReceiptParser( chain="arbitrum", token0_address="", # e.g., WETH token1_address="", # e.g., USDC ) result = parser.parse_receipt(receipt)

Initialize the parser.

Parameters:

| Name              | Type      | Description                                      | Default                                 |
| ----------------- | --------- | ------------------------------------------------ | --------------------------------------- |
| `chain`           | `str`     | Blockchain network (for token symbol resolution) | `'arbitrum'`                            |
| `token0_address`  | \`str     | None\`                                           | Address of token0 in the pool           |
| `token1_address`  | \`str     | None\`                                           | Address of token1 in the pool           |
| `token0_symbol`   | \`str     | None\`                                           | Symbol of token0                        |
| `token1_symbol`   | \`str     | None\`                                           | Symbol of token1                        |
| `token0_decimals` | \`int     | None\`                                           | Decimals for token0                     |
| `token1_decimals` | \`int     | None\`                                           | Decimals for token1                     |
| `quoted_price`    | \`Decimal | None\`                                           | Expected price for slippage calculation |

#### parse_receipt

```
parse_receipt(
    receipt: dict[str, Any],
    quoted_amount_out: int | None = None,
) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name                | Type             | Description              | Default                                         |
| ------------------- | ---------------- | ------------------------ | ----------------------------------------------- |
| `receipt`           | `dict[str, Any]` | Transaction receipt dict | *required*                                      |
| `quoted_amount_out` | \`int            | None\`                   | Expected output amount for slippage calculation |

Returns:

| Type          | Description                                     |
| ------------- | ----------------------------------------------- |
| `ParseResult` | ParseResult with extracted events and swap data |

#### parse_logs

```
parse_logs(
    logs: list[dict[str, Any]],
) -> list[SushiSwapV3Event]
```

Parse a list of logs.

Parameters:

| Name   | Type                   | Description       | Default    |
| ------ | ---------------------- | ----------------- | ---------- |
| `logs` | `list[dict[str, Any]]` | List of log dicts | *required* |

Returns:

| Type                     | Description           |
| ------------------------ | --------------------- |
| `list[SushiSwapV3Event]` | List of parsed events |

#### extract_position_id

```
extract_position_id(receipt: dict[str, Any]) -> int | None
```

Extract LP position ID (NFT tokenId) from a transaction receipt.

Looks for ERC-721 Transfer events from the NonfungiblePositionManager where from=address(0), indicating a mint (new position created).

For ERC-721 Transfer events, the signature is: Transfer(address indexed from, address indexed to, uint256 indexed tokenId) All parameters are indexed, so tokenId is in topics[3], not in data.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

Example

> > > parser = SushiSwapV3ReceiptParser(chain="arbitrum") position_id = parser.extract_position_id(receipt) if position_id: ... print(f"Opened position: {position_id}")

#### extract_position_id_from_logs

```
extract_position_id_from_logs(
    logs: list[dict[str, Any]], chain: str = "arbitrum"
) -> int | None
```

Static method to extract position ID from logs without instantiating parser.

Convenience method for cases where you just need to extract the position ID without parsing other events.

Parameters:

| Name    | Type                   | Description                                    | Default      |
| ------- | ---------------------- | ---------------------------------------------- | ------------ |
| `logs`  | `list[dict[str, Any]]` | List of log dicts from transaction receipt     | *required*   |
| `chain` | `str`                  | Chain name for position manager address lookup | `'arbitrum'` |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

Example

> > > position_id = SushiSwapV3ReceiptParser.extract_position_id_from_logs( ... receipt["logs"], chain="arbitrum" ... )

#### extract_swap_amounts

```
extract_swap_amounts(
    receipt: dict[str, Any],
) -> SwapAmounts | None
```

Extract swap amounts from a transaction receipt.

This method is called by the ResultEnricher to automatically populate ExecutionResult.swap_amounts for SWAP intents.

Resolves token decimals independently from ERC-20 Transfer events in the receipt, so it produces correct human-readable amounts even when the parser was constructed without token metadata (the enrichment path).

Parameters:

| Name      | Type             | Description                                            | Default    |
| --------- | ---------------- | ------------------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' and 'from' fields | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`SwapAmounts | None\`      |

#### extract_tick_lower

```
extract_tick_lower(receipt: dict[str, Any]) -> int | None
```

Extract tick lower from LP mint transaction receipt.

Looks for Mint events from SushiSwap V3 pools. tickLower is an indexed parameter in topics[2].

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_tick_upper

```
extract_tick_upper(receipt: dict[str, Any]) -> int | None
```

Extract tick upper from LP mint transaction receipt.

Looks for Mint events from SushiSwap V3 pools. tickUpper is an indexed parameter in topics[3].

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_liquidity

```
extract_liquidity(receipt: dict[str, Any]) -> int | None
```

Extract liquidity from LP mint transaction receipt.

Looks for Mint events from SushiSwap V3 pools. Liquidity amount is in the data field.

Mint event data layout (non-indexed fields, 32-byte padded):

- sender (address): offset 0
- amount (uint128): offset 32
- amount0 (uint256): offset 64
- amount1 (uint256): offset 96

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> LPCloseData | None
```

Extract LP close data from transaction receipt.

Looks for Collect events from SushiSwap V3 pools which indicate fees and principal being collected when closing/reducing a position.

Collect(address indexed owner, int24 indexed tickLower,

int24 indexed tickUpper, uint128 amount0, uint128 amount1)

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`LPCloseData | None\`      |

#### is_sushiswap_event

```
is_sushiswap_event(topic: str | bytes) -> bool
```

Check if a topic is a known SushiSwap V3 event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                                 |
| ------ | ------------------------------------------- |
| `bool` | True if topic is a known SushiSwap V3 event |

#### get_event_type

```
get_event_type(topic: str | bytes) -> SushiSwapV3EventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type                   | Description           |
| ---------------------- | --------------------- |
| `SushiSwapV3EventType` | Event type or UNKNOWN |

### SwapEventData

```
SwapEventData(
    sender: str,
    recipient: str,
    amount0: int,
    amount1: int,
    sqrt_price_x96: int,
    liquidity: int,
    tick: int,
    pool_address: str,
)
```

Parsed data from Swap event.

In V3 swaps:

- Positive amount0/amount1 = tokens going INTO the pool (user spent)
- Negative amount0/amount1 = tokens going OUT of the pool (user received)

#### token0_is_input

```
token0_is_input: bool
```

Check if token0 is the input token (user paid token0).

#### token1_is_input

```
token1_is_input: bool
```

Check if token1 is the input token (user paid token1).

#### amount_in

```
amount_in: int
```

Get the absolute input amount (what user paid).

#### amount_out

```
amount_out: int
```

Get the absolute output amount (what user received).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> SwapEventData
```

Create from dictionary.

### TransferEventData

```
TransferEventData(
    from_addr: str,
    to_addr: str,
    value: int,
    token_address: str,
)
```

Parsed data from Transfer event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### InvalidFeeError

```
InvalidFeeError(fee: int)
```

Bases: `SushiSwapV3SDKError`

Invalid fee tier provided.

### InvalidTickError

```
InvalidTickError(tick: int, reason: str)
```

Bases: `SushiSwapV3SDKError`

Invalid tick value provided.

### LPTransaction

```
LPTransaction(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
    operation: str,
)
```

Transaction data for LP operations.

Attributes:

| Name           | Type  | Description                                           |
| -------------- | ----- | ----------------------------------------------------- |
| `to`           | `str` | Position manager address                              |
| `value`        | `int` | ETH value to send                                     |
| `data`         | `str` | Encoded calldata                                      |
| `gas_estimate` | `int` | Estimated gas                                         |
| `description`  | `str` | Human-readable description                            |
| `operation`    | `str` | LP operation type (mint, increase, decrease, collect) |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### MintParams

```
MintParams(
    token0: str,
    token1: str,
    fee: int,
    tick_lower: int,
    tick_upper: int,
    amount0_desired: int,
    amount1_desired: int,
    amount0_min: int,
    amount1_min: int,
    recipient: str,
    deadline: int,
)
```

Parameters for minting a new LP position.

Attributes:

| Name              | Type  | Description                                    |
| ----------------- | ----- | ---------------------------------------------- |
| `token0`          | `str` | First token address                            |
| `token1`          | `str` | Second token address                           |
| `fee`             | `int` | Fee tier                                       |
| `tick_lower`      | `int` | Lower tick bound                               |
| `tick_upper`      | `int` | Upper tick bound                               |
| `amount0_desired` | `int` | Desired amount of token0                       |
| `amount1_desired` | `int` | Desired amount of token1                       |
| `amount0_min`     | `int` | Minimum amount of token0 (slippage protection) |
| `amount1_min`     | `int` | Minimum amount of token1 (slippage protection) |
| `recipient`       | `str` | Address to receive the NFT                     |
| `deadline`        | `int` | Transaction deadline timestamp                 |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PoolInfo

```
PoolInfo(
    address: str,
    token0: str,
    token1: str,
    fee: int,
    tick_spacing: int,
)
```

Information about a SushiSwap V3 pool.

Attributes:

| Name           | Type  | Description                     |
| -------------- | ----- | ------------------------------- |
| `address`      | `str` | Computed pool address           |
| `token0`       | `str` | First token address (sorted)    |
| `token1`       | `str` | Second token address (sorted)   |
| `fee`          | `int` | Fee tier in hundredths of a bip |
| `tick_spacing` | `int` | Tick spacing for this fee tier  |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PoolNotFoundError

```
PoolNotFoundError(token0: str, token1: str, fee: int)
```

Bases: `SushiSwapV3SDKError`

Pool does not exist.

### PoolState

```
PoolState(
    sqrt_price_x96: int,
    tick: int,
    liquidity: int,
    fee_growth_global_0: int = 0,
    fee_growth_global_1: int = 0,
)
```

Current state of a SushiSwap V3 pool.

Attributes:

| Name                  | Type  | Description                        |
| --------------------- | ----- | ---------------------------------- |
| `sqrt_price_x96`      | `int` | Current sqrt price (Q64.96 format) |
| `tick`                | `int` | Current tick                       |
| `liquidity`           | `int` | Current in-range liquidity         |
| `fee_growth_global_0` | `int` | Fee growth for token0              |
| `fee_growth_global_1` | `int` | Fee growth for token1              |

#### price

```
price: Decimal
```

Get current price (token1 per token0).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### QuoteError

```
QuoteError(message: str, token_in: str, token_out: str)
```

Bases: `SushiSwapV3SDKError`

Error fetching quote.

### SushiSwapV3SDK

```
SushiSwapV3SDK(
    chain: str,
    rpc_url: str | None = None,
    web3: Any | None = None,
)
```

SDK for SushiSwap V3 operations.

This class provides methods for:

- Computing pool addresses
- Fetching quotes (requires RPC)
- Building swap transactions
- Building LP position transactions
- Tick math utilities

Example

sdk = SushiSwapV3SDK(chain="arbitrum", rpc_url="https://arb1.arbitrum.io/rpc")

#### Compute pool address (no RPC needed)

pool_info = sdk.get_pool_address( "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC fee_tier=3000, )

#### Get quote (requires RPC)

quote = await sdk.get_quote( token_in=weth_address, token_out=usdc_address, amount_in=10\*\*18, # 1 WETH fee_tier=3000, )

Initialize the SDK.

Parameters:

| Name      | Type  | Description                                                                     | Default                                 |
| --------- | ----- | ------------------------------------------------------------------------------- | --------------------------------------- |
| `chain`   | `str` | Target blockchain (ethereum, arbitrum, base, polygon, avalanche, bsc, optimism) | *required*                              |
| `rpc_url` | \`str | None\`                                                                          | RPC URL for on-chain queries (optional) |
| `web3`    | \`Any | None\`                                                                          | Existing Web3 instance (optional)       |

Raises:

| Type         | Description               |
| ------------ | ------------------------- |
| `ValueError` | If chain is not supported |

#### get_pool_address

```
get_pool_address(
    token0: str, token1: str, fee_tier: int
) -> PoolInfo
```

Get the pool address for a token pair.

This computes the CREATE2 address deterministically without RPC calls.

Parameters:

| Name       | Type  | Description                      | Default    |
| ---------- | ----- | -------------------------------- | ---------- |
| `token0`   | `str` | First token address              | *required* |
| `token1`   | `str` | Second token address             | *required* |
| `fee_tier` | `int` | Fee tier (100, 500, 3000, 10000) | *required* |

Returns:

| Type       | Description                                       |
| ---------- | ------------------------------------------------- |
| `PoolInfo` | PoolInfo with computed address and token ordering |

Raises:

| Type              | Description              |
| ----------------- | ------------------------ |
| `InvalidFeeError` | If fee_tier is not valid |

Example

> > > pool = sdk.get_pool_address(weth, usdc, fee_tier=3000) print(f"Pool address: {pool.address}")

#### get_quote

```
get_quote(
    token_in: str,
    token_out: str,
    amount_in: int,
    fee_tier: int,
) -> SwapQuote
```

Get a quote for a swap.

This fetches the expected output amount for a given input amount by calling the QuoterV2 contract.

Parameters:

| Name        | Type  | Description          | Default    |
| ----------- | ----- | -------------------- | ---------- |
| `token_in`  | `str` | Input token address  | *required* |
| `token_out` | `str` | Output token address | *required* |
| `amount_in` | `int` | Input amount in wei  | *required* |
| `fee_tier`  | `int` | Fee tier             | *required* |

Returns:

| Type        | Description                    |
| ----------- | ------------------------------ |
| `SwapQuote` | SwapQuote with expected output |

Raises:

| Type              | Description              |
| ----------------- | ------------------------ |
| `QuoteError`      | If quote fails           |
| `InvalidFeeError` | If fee_tier is not valid |

Note

Requires RPC connection. Use get_quote_local for offline estimation.

#### get_quote_local

```
get_quote_local(
    token_in: str,
    token_out: str,
    amount_in: int,
    fee_tier: int,
    price_ratio: Decimal | None = None,
) -> SwapQuote
```

Get an estimated quote without RPC calls.

This provides an approximation based on fee tier only. For accurate quotes, use get_quote() with RPC.

Parameters:

| Name          | Type      | Description          | Default                                 |
| ------------- | --------- | -------------------- | --------------------------------------- |
| `token_in`    | `str`     | Input token address  | *required*                              |
| `token_out`   | `str`     | Output token address | *required*                              |
| `amount_in`   | `int`     | Input amount in wei  | *required*                              |
| `fee_tier`    | `int`     | Fee tier             | *required*                              |
| `price_ratio` | \`Decimal | None\`               | Optional token_out/token_in price ratio |

Returns:

| Type        | Description                     |
| ----------- | ------------------------------- |
| `SwapQuote` | SwapQuote with estimated output |

#### build_swap_tx

```
build_swap_tx(
    quote: SwapQuote,
    recipient: str,
    slippage_bps: int,
    deadline: int,
    value: int = 0,
) -> SwapTransaction
```

Build a swap transaction from a quote.

Parameters:

| Name           | Type        | Description                                | Default    |
| -------------- | ----------- | ------------------------------------------ | ---------- |
| `quote`        | `SwapQuote` | Quote from get_quote()                     | *required* |
| `recipient`    | `str`       | Address to receive output tokens           | *required* |
| `slippage_bps` | `int`       | Slippage tolerance in basis points         | *required* |
| `deadline`     | `int`       | Unix timestamp deadline                    | *required* |
| `value`        | `int`       | ETH value to send (for native token swaps) | `0`        |

Returns:

| Type              | Description                           |
| ----------------- | ------------------------------------- |
| `SwapTransaction` | SwapTransaction with encoded calldata |

Example

> > > quote = await sdk.get_quote(weth, usdc, 10\*\*18, 3000) tx = sdk.build_swap_tx( ... quote=quote, ... recipient="0x...", ... slippage_bps=50, ... deadline=int(time.time()) + 300, ... )

#### build_exact_output_swap_tx

```
build_exact_output_swap_tx(
    token_in: str,
    token_out: str,
    fee: int,
    recipient: str,
    deadline: int,
    amount_out: int,
    amount_in_maximum: int,
    value: int = 0,
) -> SwapTransaction
```

Build an exact output swap transaction.

For swaps where you specify the exact output amount.

Parameters:

| Name                | Type  | Description                                | Default    |
| ------------------- | ----- | ------------------------------------------ | ---------- |
| `token_in`          | `str` | Input token address                        | *required* |
| `token_out`         | `str` | Output token address                       | *required* |
| `fee`               | `int` | Fee tier                                   | *required* |
| `recipient`         | `str` | Address to receive output tokens           | *required* |
| `deadline`          | `int` | Unix timestamp deadline                    | *required* |
| `amount_out`        | `int` | Exact output amount desired                | *required* |
| `amount_in_maximum` | `int` | Maximum input amount (with slippage)       | *required* |
| `value`             | `int` | ETH value to send (for native token swaps) | `0`        |

Returns:

| Type              | Description                           |
| ----------------- | ------------------------------------- |
| `SwapTransaction` | SwapTransaction with encoded calldata |

#### build_mint_tx

```
build_mint_tx(
    params: MintParams, value: int = 0
) -> LPTransaction
```

Build a transaction to mint a new LP position.

Parameters:

| Name     | Type         | Description                                                        | Default    |
| -------- | ------------ | ------------------------------------------------------------------ | ---------- |
| `params` | `MintParams` | Mint parameters                                                    | *required* |
| `value`  | `int`        | ETH value to send (if one token is WETH and user wants to use ETH) | `0`        |

Returns:

| Type            | Description                         |
| --------------- | ----------------------------------- |
| `LPTransaction` | LPTransaction with encoded calldata |

#### build_increase_liquidity_tx

```
build_increase_liquidity_tx(
    token_id: int,
    amount0_desired: int,
    amount1_desired: int,
    amount0_min: int,
    amount1_min: int,
    deadline: int,
    value: int = 0,
) -> LPTransaction
```

Build a transaction to increase liquidity in an existing position.

Parameters:

| Name              | Type  | Description                                    | Default    |
| ----------------- | ----- | ---------------------------------------------- | ---------- |
| `token_id`        | `int` | NFT token ID of the position                   | *required* |
| `amount0_desired` | `int` | Desired amount of token0 to add                | *required* |
| `amount1_desired` | `int` | Desired amount of token1 to add                | *required* |
| `amount0_min`     | `int` | Minimum amount of token0 (slippage protection) | *required* |
| `amount1_min`     | `int` | Minimum amount of token1 (slippage protection) | *required* |
| `deadline`        | `int` | Transaction deadline timestamp                 | *required* |
| `value`           | `int` | ETH value to send                              | `0`        |

Returns:

| Type            | Description                         |
| --------------- | ----------------------------------- |
| `LPTransaction` | LPTransaction with encoded calldata |

#### build_decrease_liquidity_tx

```
build_decrease_liquidity_tx(
    token_id: int,
    liquidity: int,
    amount0_min: int,
    amount1_min: int,
    deadline: int,
) -> LPTransaction
```

Build a transaction to decrease liquidity in a position.

Parameters:

| Name          | Type  | Description                         | Default    |
| ------------- | ----- | ----------------------------------- | ---------- |
| `token_id`    | `int` | NFT token ID of the position        | *required* |
| `liquidity`   | `int` | Amount of liquidity to remove       | *required* |
| `amount0_min` | `int` | Minimum amount of token0 to receive | *required* |
| `amount1_min` | `int` | Minimum amount of token1 to receive | *required* |
| `deadline`    | `int` | Transaction deadline timestamp      | *required* |

Returns:

| Type            | Description                         |
| --------------- | ----------------------------------- |
| `LPTransaction` | LPTransaction with encoded calldata |

#### build_collect_tx

```
build_collect_tx(
    token_id: int,
    recipient: str,
    amount0_max: int = MAX_UINT128,
    amount1_max: int = MAX_UINT128,
) -> LPTransaction
```

Build a transaction to collect fees/tokens from a position.

Parameters:

| Name          | Type  | Description                                        | Default       |
| ------------- | ----- | -------------------------------------------------- | ------------- |
| `token_id`    | `int` | NFT token ID of the position                       | *required*    |
| `recipient`   | `str` | Address to receive collected tokens                | *required*    |
| `amount0_max` | `int` | Maximum amount of token0 to collect (default: all) | `MAX_UINT128` |
| `amount1_max` | `int` | Maximum amount of token1 to collect (default: all) | `MAX_UINT128` |

Returns:

| Type            | Description                         |
| --------------- | ----------------------------------- |
| `LPTransaction` | LPTransaction with encoded calldata |

### SushiSwapV3SDKError

Bases: `Exception`

Base exception for SushiSwap V3 SDK errors.

### SwapTransaction

```
SwapTransaction(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
)
```

Transaction data for a swap.

Attributes:

| Name           | Type  | Description                                |
| -------------- | ----- | ------------------------------------------ |
| `to`           | `str` | Router address                             |
| `value`        | `int` | ETH value to send (for native token swaps) |
| `data`         | `str` | Encoded calldata                           |
| `gas_estimate` | `int` | Estimated gas                              |
| `description`  | `str` | Human-readable description                 |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### compute_pool_address

```
compute_pool_address(
    factory: str,
    token0: str,
    token1: str,
    fee: int,
    init_code_hash: str = POOL_INIT_CODE_HASH,
) -> str
```

Compute the CREATE2 address for a SushiSwap V3 pool.

This deterministically computes the pool address without any RPC calls.

Parameters:

| Name             | Type  | Description                                    | Default               |
| ---------------- | ----- | ---------------------------------------------- | --------------------- |
| `factory`        | `str` | Factory contract address                       | *required*            |
| `token0`         | `str` | First token address                            | *required*            |
| `token1`         | `str` | Second token address                           | *required*            |
| `fee`            | `int` | Fee tier                                       | *required*            |
| `init_code_hash` | `str` | Pool init code hash (default for SushiSwap V3) | `POOL_INIT_CODE_HASH` |

Returns:

| Type  | Description  |
| ----- | ------------ |
| `str` | Pool address |

Raises:

| Type              | Description                |
| ----------------- | -------------------------- |
| `InvalidFeeError` | If fee is not a valid tier |

Example

> > > pool_addr = compute_pool_address( ... factory="0x1af415a1EbA07a4986a52B6f2e7dE7003D82231e", ... token0="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH ... token1="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC ... fee=3000, ... )

### get_max_tick

```
get_max_tick(fee: int) -> int
```

Get the maximum valid tick for a fee tier.

Parameters:

| Name  | Type  | Description | Default    |
| ----- | ----- | ----------- | ---------- |
| `fee` | `int` | Fee tier    | *required* |

Returns:

| Type  | Description        |
| ----- | ------------------ |
| `int` | Maximum valid tick |

Raises:

| Type              | Description                |
| ----------------- | -------------------------- |
| `InvalidFeeError` | If fee is not a valid tier |

### get_min_tick

```
get_min_tick(fee: int) -> int
```

Get the minimum valid tick for a fee tier.

Parameters:

| Name  | Type  | Description | Default    |
| ----- | ----- | ----------- | ---------- |
| `fee` | `int` | Fee tier    | *required* |

Returns:

| Type  | Description        |
| ----- | ------------------ |
| `int` | Minimum valid tick |

Raises:

| Type              | Description                |
| ----------------- | -------------------------- |
| `InvalidFeeError` | If fee is not a valid tier |

### get_nearest_tick

```
get_nearest_tick(tick: int, fee: int) -> int
```

Get the nearest valid tick for a given fee tier.

Parameters:

| Name   | Type  | Description    | Default    |
| ------ | ----- | -------------- | ---------- |
| `tick` | `int` | Raw tick value | *required* |
| `fee`  | `int` | Fee tier       | *required* |

Returns:

| Type  | Description        |
| ----- | ------------------ |
| `int` | Nearest valid tick |

Raises:

| Type              | Description                |
| ----------------- | -------------------------- |
| `InvalidFeeError` | If fee is not a valid tier |

### price_to_sqrt_price_x96

```
price_to_sqrt_price_x96(price: Decimal | float) -> int
```

Convert a decimal price to sqrt price X96 format.

Parameters:

| Name    | Type      | Description | Default          |
| ------- | --------- | ----------- | ---------------- |
| `price` | \`Decimal | float\`     | Price as decimal |

Returns:

| Type  | Description                 |
| ----- | --------------------------- |
| `int` | Sqrt price in Q64.96 format |

### price_to_tick

```
price_to_tick(
    price: Decimal | float,
    decimals0: int = 18,
    decimals1: int = 18,
) -> int
```

Convert a price to the nearest tick.

Parameters:

| Name        | Type      | Description        | Default                            |
| ----------- | --------- | ------------------ | ---------------------------------- |
| `price`     | \`Decimal | float\`            | Price of token0 in terms of token1 |
| `decimals0` | `int`     | Decimals of token0 | `18`                               |
| `decimals1` | `int`     | Decimals of token1 | `18`                               |

Returns:

| Type  | Description                                              |
| ----- | -------------------------------------------------------- |
| `int` | Tick value (may not be on a valid tick spacing boundary) |

### sort_tokens

```
sort_tokens(token0: str, token1: str) -> tuple[str, str]
```

Sort two token addresses in ascending order.

V3 pools always order tokens such that token0 < token1.

Parameters:

| Name     | Type  | Description          | Default    |
| -------- | ----- | -------------------- | ---------- |
| `token0` | `str` | First token address  | *required* |
| `token1` | `str` | Second token address | *required* |

Returns:

| Type              | Description                                         |
| ----------------- | --------------------------------------------------- |
| `tuple[str, str]` | Tuple of (token0, token1) sorted in ascending order |

### sqrt_price_x96_to_price

```
sqrt_price_x96_to_price(sqrt_price_x96: int) -> Decimal
```

Convert sqrt price X96 to a decimal price.

Parameters:

| Name             | Type  | Description                 | Default    |
| ---------------- | ----- | --------------------------- | ---------- |
| `sqrt_price_x96` | `int` | Sqrt price in Q64.96 format | *required* |

Returns:

| Type      | Description      |
| --------- | ---------------- |
| `Decimal` | Price as Decimal |

### sqrt_price_x96_to_tick

```
sqrt_price_x96_to_tick(sqrt_price_x96: int) -> int
```

Convert sqrt price in Q64.96 format to tick.

Parameters:

| Name             | Type  | Description                 | Default    |
| ---------------- | ----- | --------------------------- | ---------- |
| `sqrt_price_x96` | `int` | Sqrt price in Q64.96 format | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| `int` | Tick value  |

Raises:

| Type         | Description                  |
| ------------ | ---------------------------- |
| `ValueError` | If sqrt_price_x96 is invalid |

### tick_to_price

```
tick_to_price(
    tick: int, decimals0: int = 18, decimals1: int = 18
) -> Decimal
```

Convert a tick to a human-readable price.

Parameters:

| Name        | Type  | Description        | Default    |
| ----------- | ----- | ------------------ | ---------- |
| `tick`      | `int` | Tick value         | *required* |
| `decimals0` | `int` | Decimals of token0 | `18`       |
| `decimals1` | `int` | Decimals of token1 | `18`       |

Returns:

| Type      | Description                                                |
| --------- | ---------------------------------------------------------- |
| `Decimal` | Price of token0 in terms of token1 (adjusted for decimals) |

### tick_to_sqrt_price_x96

```
tick_to_sqrt_price_x96(tick: int) -> int
```

Convert a tick to sqrt price in Q64.96 format.

Uses the formula: sqrt(1.0001^tick) * 2^96

Parameters:

| Name   | Type  | Description | Default    |
| ------ | ----- | ----------- | ---------- |
| `tick` | `int` | Tick value  | *required* |

Returns:

| Type  | Description                 |
| ----- | --------------------------- |
| `int` | Sqrt price in Q64.96 format |

Raises:

| Type               | Description              |
| ------------------ | ------------------------ |
| `InvalidTickError` | If tick is out of bounds |

# TraderJoe V2

Connector for TraderJoe V2 Liquidity Book DEX.

## almanak.framework.connectors.traderjoe_v2

TraderJoe Liquidity Book V2 Connector.

This module provides the TraderJoe V2 adapter for executing swaps and managing liquidity positions on TraderJoe V2's Liquidity Book on Avalanche.

TraderJoe V2 Architecture:

- LBRouter: Main entry point for swaps and liquidity operations
- LBFactory: Creates and manages LBPair pools
- LBPair: Liquidity pool with discrete bins (not continuous ticks)

Key Concepts:

- Bin: Discrete price point (unlike Uniswap V3's continuous ticks)
- BinStep: Fee tier in basis points between bins (e.g., 20 = 0.2%)
- Fungible LP Tokens: ERC1155-like tokens for each bin (no NFTs)

Supported chains:

- Avalanche (Chain ID: 43114)

Example

from almanak.framework.connectors.traderjoe_v2 import TraderJoeV2Adapter, TraderJoeV2Config

config = TraderJoeV2Config( chain="avalanche", wallet_address="0x...", rpc_url="https://api.avax.network/ext/bc/C/rpc", ) adapter = TraderJoeV2Adapter(config)

### Get a swap quote

quote = adapter.get_swap_quote( token_in="WAVAX", token_out="USDC", amount_in=Decimal("1.0"), bin_step=20, )

### Use SDK for lower-level operations

from almanak.framework.connectors.traderjoe_v2 import TraderJoeV2SDK

sdk = TraderJoeV2SDK(chain="avalanche", rpc_url="https://api.avax.network/ext/bc/C/rpc") pool = sdk.get_pool_address(wavax_addr, usdc_addr, bin_step=20)

### LiquidityPosition

```
LiquidityPosition(
    pool_address: str,
    token_x: str,
    token_y: str,
    bin_step: int,
    bin_ids: list[int],
    balances: dict[int, int],
    amount_x: int,
    amount_y: int,
    active_bin: int,
)
```

Represents a liquidity position in TraderJoe V2.

Attributes:

| Name           | Type             | Description                                  |
| -------------- | ---------------- | -------------------------------------------- |
| `pool_address` | `str`            | Address of the LBPair pool                   |
| `token_x`      | `str`            | Address of token X                           |
| `token_y`      | `str`            | Address of token Y                           |
| `bin_step`     | `int`            | Bin step of the pool                         |
| `bin_ids`      | `list[int]`      | List of bin IDs where position has liquidity |
| `balances`     | `dict[int, int]` | Dict mapping bin ID to LB token balance      |
| `amount_x`     | `int`            | Total amount of token X in position          |
| `amount_y`     | `int`            | Total amount of token Y in position          |
| `active_bin`   | `int`            | Current active bin ID of the pool            |

### SwapQuote

```
SwapQuote(
    token_in: str,
    token_out: str,
    amount_in: Decimal,
    amount_out: Decimal,
    bin_step: int,
    price: Decimal,
    price_impact: Decimal,
    path: list[str],
    gas_estimate: int = DEFAULT_GAS_ESTIMATES["swap"],
)
```

Quote for a swap operation.

Attributes:

| Name           | Type        | Description                                      |
| -------------- | ----------- | ------------------------------------------------ |
| `token_in`     | `str`       | Input token symbol or address                    |
| `token_out`    | `str`       | Output token symbol or address                   |
| `amount_in`    | `Decimal`   | Amount of input token (in token units)           |
| `amount_out`   | `Decimal`   | Expected amount of output token (in token units) |
| `bin_step`     | `int`       | Bin step used for the swap                       |
| `price`        | `Decimal`   | Execution price (amount_out / amount_in)         |
| `price_impact` | `Decimal`   | Estimated price impact as percentage             |
| `path`         | `list[str]` | Token path for the swap                          |
| `gas_estimate` | `int`       | Estimated gas for the transaction                |

### SwapResult

```
SwapResult(
    success: bool,
    tx_hash: str | None = None,
    token_in: str | None = None,
    token_out: str | None = None,
    amount_in: Decimal | None = None,
    amount_out: Decimal | None = None,
    gas_used: int | None = None,
    block_number: int | None = None,
    timestamp: datetime | None = None,
    error: str | None = None,
)
```

Result of an executed swap.

Attributes:

| Name           | Type       | Description                |
| -------------- | ---------- | -------------------------- |
| `success`      | `bool`     | Whether the swap succeeded |
| `tx_hash`      | \`str      | None\`                     |
| `token_in`     | \`str      | None\`                     |
| `token_out`    | \`str      | None\`                     |
| `amount_in`    | \`Decimal  | None\`                     |
| `amount_out`   | \`Decimal  | None\`                     |
| `gas_used`     | \`int      | None\`                     |
| `block_number` | \`int      | None\`                     |
| `timestamp`    | \`datetime | None\`                     |
| `error`        | \`str      | None\`                     |

### SwapType

Bases: `StrEnum`

Type of swap to execute.

### TraderJoeV2Adapter

```
TraderJoeV2Adapter(
    config: TraderJoeV2Config,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for TraderJoe Liquidity Book V2 protocol.

Provides high-level methods for:

- Token swaps (exact input)
- Liquidity management (add/remove)
- Position queries
- Quote generation

The adapter handles token resolution, slippage calculations, and transaction building internally.

Example

config = TraderJoeV2Config( chain="avalanche", wallet_address="0x...", rpc_url="https://api.avax.network/ext/bc/C/rpc", ) adapter = TraderJoeV2Adapter(config)

#### Get quote

quote = adapter.get_swap_quote("WAVAX", "USDC", Decimal("1.0"), bin_step=20)

#### Execute swap

result = adapter.swap_exact_input("WAVAX", "USDC", Decimal("1.0"), bin_step=20)

Initialize the adapter.

Parameters:

| Name             | Type                | Description           | Default                                                   |
| ---------------- | ------------------- | --------------------- | --------------------------------------------------------- |
| `config`         | `TraderJoeV2Config` | Adapter configuration | *required*                                                |
| `token_resolver` | \`TokenResolver     | None\`                | Optional TokenResolver instance. If None, uses singleton. |

Raises:

| Type                  | Description                                   |
| --------------------- | --------------------------------------------- |
| `TraderJoeV2SDKError` | If chain is not supported or connection fails |

#### resolve_token_address

```
resolve_token_address(token: str) -> str
```

Resolve a token symbol or address to a checksummed address using TokenResolver.

Parameters:

| Name    | Type  | Description                             | Default    |
| ------- | ----- | --------------------------------------- | ---------- |
| `token` | `str` | Token symbol (e.g., "WAVAX") or address | *required* |

Returns:

| Type  | Description               |
| ----- | ------------------------- |
| `str` | Checksummed token address |

Raises:

| Type                   | Description                 |
| ---------------------- | --------------------------- |
| `TokenResolutionError` | If token cannot be resolved |

#### get_token_decimals

```
get_token_decimals(token: str) -> int
```

Get decimals for a token using TokenResolver.

Parameters:

| Name    | Type  | Description             | Default    |
| ------- | ----- | ----------------------- | ---------- |
| `token` | `str` | Token symbol or address | *required* |

Returns:

| Type  | Description    |
| ----- | -------------- |
| `int` | Token decimals |

Raises:

| Type                   | Description                      |
| ---------------------- | -------------------------------- |
| `TokenResolutionError` | If decimals cannot be determined |

#### to_wei

```
to_wei(amount: Decimal, token: str) -> int
```

Convert token amount to wei (smallest unit).

Parameters:

| Name     | Type      | Description             | Default    |
| -------- | --------- | ----------------------- | ---------- |
| `amount` | `Decimal` | Amount in token units   | *required* |
| `token`  | `str`     | Token symbol or address | *required* |

Returns:

| Type  | Description   |
| ----- | ------------- |
| `int` | Amount in wei |

#### from_wei

```
from_wei(amount: int, token: str) -> Decimal
```

Convert wei to token amount.

Parameters:

| Name     | Type  | Description             | Default    |
| -------- | ----- | ----------------------- | ---------- |
| `amount` | `int` | Amount in wei           | *required* |
| `token`  | `str` | Token symbol or address | *required* |

Returns:

| Type      | Description           |
| --------- | --------------------- |
| `Decimal` | Amount in token units |

#### get_swap_quote

```
get_swap_quote(
    token_in: str,
    token_out: str,
    amount_in: Decimal,
    bin_step: int = 20,
) -> SwapQuote
```

Get a quote for a swap.

Parameters:

| Name        | Type      | Description                               | Default    |
| ----------- | --------- | ----------------------------------------- | ---------- |
| `token_in`  | `str`     | Input token symbol or address             | *required* |
| `token_out` | `str`     | Output token symbol or address            | *required* |
| `amount_in` | `Decimal` | Amount of input token                     | *required* |
| `bin_step`  | `int`     | Bin step for the pair (default 20 = 0.2%) | `20`       |

Returns:

| Type        | Description                                   |
| ----------- | --------------------------------------------- |
| `SwapQuote` | SwapQuote with expected output and price info |

Raises:

| Type                | Description           |
| ------------------- | --------------------- |
| `PoolNotFoundError` | If pool doesn't exist |

#### build_swap_transaction

```
build_swap_transaction(
    token_in: str,
    token_out: str,
    amount_in: Decimal,
    bin_step: int = 20,
    slippage_bps: int | None = None,
    recipient: str | None = None,
) -> TransactionData
```

Build a swap transaction without executing it.

Parameters:

| Name           | Type      | Description                    | Default                                     |
| -------------- | --------- | ------------------------------ | ------------------------------------------- |
| `token_in`     | `str`     | Input token symbol or address  | *required*                                  |
| `token_out`    | `str`     | Output token symbol or address | *required*                                  |
| `amount_in`    | `Decimal` | Amount of input token          | *required*                                  |
| `bin_step`     | `int`     | Bin step for the pair          | `20`                                        |
| `slippage_bps` | \`int     | None\`                         | Slippage tolerance in basis points          |
| `recipient`    | \`str     | None\`                         | Recipient address (default: wallet_address) |

Returns:

| Type              | Description                                     |
| ----------------- | ----------------------------------------------- |
| `TransactionData` | TransactionData ready for signing and execution |

#### swap_exact_input

```
swap_exact_input(
    token_in: str,
    token_out: str,
    amount_in: Decimal,
    bin_step: int = 20,
    slippage_bps: int | None = None,
) -> SwapResult
```

Execute a swap with exact input amount.

Note: This method requires a private key to be configured. For building unsigned transactions, use build_swap_transaction().

Parameters:

| Name           | Type      | Description                    | Default                            |
| -------------- | --------- | ------------------------------ | ---------------------------------- |
| `token_in`     | `str`     | Input token symbol or address  | *required*                         |
| `token_out`    | `str`     | Output token symbol or address | *required*                         |
| `amount_in`    | `Decimal` | Exact amount of input token    | *required*                         |
| `bin_step`     | `int`     | Bin step for the pair          | `20`                               |
| `slippage_bps` | \`int     | None\`                         | Slippage tolerance in basis points |

Returns:

| Type         | Description                       |
| ------------ | --------------------------------- |
| `SwapResult` | SwapResult with execution details |

Raises:

| Type                  | Description        |
| --------------------- | ------------------ |
| `TraderJoeV2SDKError` | If execution fails |

#### get_position

```
get_position(
    token_x: str,
    token_y: str,
    bin_step: int = 20,
    wallet: str | None = None,
) -> LiquidityPosition | None
```

Get liquidity position for a wallet in a pool.

Parameters:

| Name       | Type  | Description               | Default                                     |
| ---------- | ----- | ------------------------- | ------------------------------------------- |
| `token_x`  | `str` | Token X symbol or address | *required*                                  |
| `token_y`  | `str` | Token Y symbol or address | *required*                                  |
| `bin_step` | `int` | Bin step of the pool      | `20`                                        |
| `wallet`   | \`str | None\`                    | Wallet address (default: configured wallet) |

Returns:

| Type                | Description |
| ------------------- | ----------- |
| \`LiquidityPosition | None\`      |

#### build_add_liquidity_transaction

```
build_add_liquidity_transaction(
    token_x: str,
    token_y: str,
    amount_x: Decimal,
    amount_y: Decimal,
    bin_step: int = 20,
    bin_range: int = 10,
    slippage_bps: int | None = None,
) -> TransactionData
```

Build an add liquidity transaction.

Creates a uniform distribution across bins around the active bin.

Parameters:

| Name           | Type      | Description                               | Default                            |
| -------------- | --------- | ----------------------------------------- | ---------------------------------- |
| `token_x`      | `str`     | Token X symbol or address                 | *required*                         |
| `token_y`      | `str`     | Token Y symbol or address                 | *required*                         |
| `amount_x`     | `Decimal` | Amount of token X to add                  | *required*                         |
| `amount_y`     | `Decimal` | Amount of token Y to add                  | *required*                         |
| `bin_step`     | `int`     | Bin step of the pool                      | `20`                               |
| `bin_range`    | `int`     | Number of bins on each side of active bin | `10`                               |
| `slippage_bps` | \`int     | None\`                                    | Slippage tolerance in basis points |

Returns:

| Type              | Description                                     |
| ----------------- | ----------------------------------------------- |
| `TransactionData` | TransactionData ready for signing and execution |

#### build_remove_liquidity_transaction

```
build_remove_liquidity_transaction(
    token_x: str,
    token_y: str,
    bin_step: int = 20,
    slippage_bps: int | None = None,
    position: LiquidityPosition | None = None,
) -> TransactionData | None
```

Build a remove liquidity transaction for all positions.

Parameters:

| Name           | Type                | Description               | Default                                                                                                                                                       |
| -------------- | ------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token_x`      | `str`               | Token X symbol or address | *required*                                                                                                                                                    |
| `token_y`      | `str`               | Token Y symbol or address | *required*                                                                                                                                                    |
| `bin_step`     | `int`               | Bin step of the pool      | `20`                                                                                                                                                          |
| `slippage_bps` | \`int               | None\`                    | Slippage tolerance in basis points                                                                                                                            |
| `position`     | \`LiquidityPosition | None\`                    | Optional pre-fetched position. If provided, skips the get_position() call (avoids redundant RPC round trips when the caller already holds the position data). |

Returns:

| Type              | Description |
| ----------------- | ----------- |
| \`TransactionData | None\`      |

#### get_pending_fees

```
get_pending_fees(
    token_x: str,
    token_y: str,
    bin_step: int = 20,
    wallet: str | None = None,
) -> CollectFeesResult | None
```

Query pending fees for an LP position.

Parameters:

| Name       | Type  | Description               | Default                                     |
| ---------- | ----- | ------------------------- | ------------------------------------------- |
| `token_x`  | `str` | Token X symbol or address | *required*                                  |
| `token_y`  | `str` | Token Y symbol or address | *required*                                  |
| `bin_step` | `int` | Bin step of the pool      | `20`                                        |
| `wallet`   | \`str | None\`                    | Wallet address (default: configured wallet) |

Returns:

| Type                | Description |
| ------------------- | ----------- |
| \`CollectFeesResult | None\`      |

#### build_collect_fees_transaction

```
build_collect_fees_transaction(
    token_x: str, token_y: str, bin_step: int = 20
) -> TransactionData | None
```

Build a transaction to collect fees from an LP position without closing it.

Calls LBPair.collectFees(account, binIds) which harvests accumulated fees while keeping the liquidity position intact.

Parameters:

| Name       | Type  | Description               | Default    |
| ---------- | ----- | ------------------------- | ---------- |
| `token_x`  | `str` | Token X symbol or address | *required* |
| `token_y`  | `str` | Token Y symbol or address | *required* |
| `bin_step` | `int` | Bin step of the pool      | `20`       |

Returns:

| Type              | Description |
| ----------------- | ----------- |
| \`TransactionData | None\`      |

### TraderJoeV2Config

```
TraderJoeV2Config(
    chain: str,
    wallet_address: str,
    rpc_url: str,
    private_key: str | None = None,
    default_slippage_bps: int = 50,
    default_deadline_seconds: int = 300,
)
```

Configuration for TraderJoe V2 adapter.

Parameters:

| Name                       | Type  | Description                                           | Default                                                |
| -------------------------- | ----- | ----------------------------------------------------- | ------------------------------------------------------ |
| `chain`                    | `str` | Chain name (must be "avalanche")                      | *required*                                             |
| `wallet_address`           | `str` | Address of the wallet executing transactions          | *required*                                             |
| `rpc_url`                  | `str` | RPC endpoint URL                                      | *required*                                             |
| `private_key`              | \`str | None\`                                                | Private key for signing (optional, for non-simulation) |
| `default_slippage_bps`     | `int` | Default slippage in basis points (default: 50 = 0.5%) | `50`                                                   |
| `default_deadline_seconds` | `int` | Default transaction deadline (default: 300 = 5 min)   | `300`                                                  |

### TransactionData

```
TransactionData(
    to: str,
    data: str,
    value: int,
    gas: int,
    chain_id: int = 43114,
)
```

Transaction data ready for execution.

Attributes:

| Name       | Type  | Description             |
| ---------- | ----- | ----------------------- |
| `to`       | `str` | Target contract address |
| `data`     | `str` | Encoded calldata        |
| `value`    | `int` | ETH/AVAX value to send  |
| `gas`      | `int` | Gas limit               |
| `chain_id` | `int` | Chain ID                |

### LiquidityEventData

```
LiquidityEventData(
    pool_address: str,
    sender: str,
    to: str,
    bin_ids: list[int],
    amounts_x: list[int] = list(),
    amounts_y: list[int] = list(),
    total_amount_x: int = 0,
    total_amount_y: int = 0,
)
```

Parsed data from add/remove liquidity events.

### ParsedLiquidityResult

```
ParsedLiquidityResult(
    success: bool,
    is_add: bool = True,
    pool_address: str | None = None,
    bin_ids: list[int] = list(),
    amount_x: int = 0,
    amount_y: int = 0,
    gas_used: int | None = None,
    block_number: int | None = None,
)
```

Result of parsing a liquidity transaction.

### ParsedSwapResult

```
ParsedSwapResult(
    success: bool,
    token_in: str | None = None,
    token_out: str | None = None,
    amount_in: int | None = None,
    amount_out: int | None = None,
    price: Decimal | None = None,
    gas_used: int | None = None,
    block_number: int | None = None,
    timestamp: datetime | None = None,
)
```

Result of parsing a swap transaction.

### ParseResult

```
ParseResult(
    success: bool,
    transaction_hash: str,
    block_number: int,
    gas_used: int,
    events: list[TraderJoeV2Event] = list(),
    swap_result: ParsedSwapResult | None = None,
    liquidity_result: ParsedLiquidityResult | None = None,
    error: str | None = None,
)
```

Result of parsing a transaction receipt.

### SwapEventData

```
SwapEventData(
    token_in: str,
    token_out: str,
    amount_in: int,
    amount_out: int,
    sender: str,
    recipient: str,
)
```

Parsed data from a swap (Transfer events).

### TraderJoeV2Event

```
TraderJoeV2Event(
    event_type: TraderJoeV2EventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime | None = None,
)
```

Parsed TraderJoe V2 event.

### TraderJoeV2EventType

Bases: `Enum`

TraderJoe V2 event types.

### TraderJoeV2ReceiptParser

```
TraderJoeV2ReceiptParser(**kwargs: Any)
```

Parser for TraderJoe V2 transaction receipts.

Refactored to use base infrastructure utilities for hex decoding and event registry management. Now properly parses dynamic arrays in DepositedToBins and WithdrawnFromBins events.

Initialize the parser.

Parameters:

| Name       | Type  | Description                                      | Default |
| ---------- | ----- | ------------------------------------------------ | ------- |
| `**kwargs` | `Any` | Additional arguments (ignored for compatibility) | `{}`    |

#### parse_receipt

```
parse_receipt(receipt: dict[str, Any]) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name      | Type             | Description                   | Default    |
| --------- | ---------------- | ----------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Web3 transaction receipt dict | *required* |

Returns:

| Type          | Description                     |
| ------------- | ------------------------------- |
| `ParseResult` | ParseResult with extracted data |

#### parse_swap_events

```
parse_swap_events(
    receipt: dict[str, Any],
) -> list[SwapEventData]
```

Parse swap events from a receipt.

Convenient method to extract all swap-related data.

Parameters:

| Name      | Type             | Description                   | Default    |
| --------- | ---------------- | ----------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Web3 transaction receipt dict | *required* |

Returns:

| Type                  | Description                   |
| --------------------- | ----------------------------- |
| `list[SwapEventData]` | List of SwapEventData objects |

#### extract_swap_amounts

```
extract_swap_amounts(
    receipt: dict[str, Any],
) -> SwapAmounts | None
```

Extract swap amounts from a transaction receipt.

Note: Decimal conversions assume 18 decimals. TraderJoe pools often include tokens with different decimals (e.g., USDC with 6, WBTC with 8). The raw amount_in/amount_out fields are always accurate; use those with your own decimal scaling for precise calculations.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`SwapAmounts | None\`      |

#### extract_bin_ids

```
extract_bin_ids(
    receipt: dict[str, Any],
) -> list[int] | None
```

Extract bin IDs from LP transaction receipt.

TraderJoe V2 uses bins for liquidity. This extracts the bin IDs from DepositedToBins or WithdrawnFromBins events.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type        | Description |
| ----------- | ----------- |
| \`list[int] | None\`      |

#### extract_liquidity

```
extract_liquidity(receipt: dict[str, Any]) -> int | None
```

Extract total liquidity from LP transaction receipt.

Note: TraderJoe V2 uses ERC-1155 tokens for LP positions (not ERC-20). Liquidity amounts are encoded in DepositedToBins/WithdrawnFromBins events as bytes32 arrays requiring complex decoding. This method returns None as the exact liquidity amount extraction is not yet implemented.

For LP operation detection, use parse_receipt().liquidity_result instead.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_collected_fees

```
extract_collected_fees(
    receipt: dict[str, Any],
) -> ParsedFeeCollectionResult | None
```

Extract collected fees data from a fee collection transaction receipt.

Looks for ClaimedFees events and Transfer events to determine fee amounts. Returns None if ClaimedFees is not found (older LBPair versions without this event).

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type                        | Description |
| --------------------------- | ----------- |
| \`ParsedFeeCollectionResult | None\`      |

#### extract_fees0

```
extract_fees0(receipt: dict[str, Any]) -> int | None
```

Extract fee amount for token X from fee collection receipt.

Parameters:

| Name      | Type             | Description              | Default    |
| --------- | ---------------- | ------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_fees1

```
extract_fees1(receipt: dict[str, Any]) -> int | None
```

Extract fee amount for token Y from fee collection receipt.

Parameters:

| Name      | Type             | Description              | Default    |
| --------- | ---------------- | ------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> LPCloseData | None
```

Extract LP close data from transaction receipt.

Looks for WithdrawnFromBins events and Transfer events.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`LPCloseData | None\`      |

### TransferEventData

```
TransferEventData(
    token: str,
    from_address: str,
    to_address: str,
    amount: int,
)
```

Parsed data from Transfer event.

### InvalidBinStepError

```
InvalidBinStepError(bin_step: int)
```

Bases: `TraderJoeV2SDKError`

Invalid bin step provided.

### PoolInfo

```
PoolInfo(
    address: str,
    token_x: str,
    token_y: str,
    bin_step: int,
    active_id: int,
    reserve_x: int,
    reserve_y: int,
)
```

Information about a TraderJoe V2 LBPair pool.

### PoolNotFoundError

```
PoolNotFoundError(
    token_x: str, token_y: str, bin_step: int
)
```

Bases: `TraderJoeV2SDKError`

Pool does not exist.

### TraderJoeV2SDK

```
TraderJoeV2SDK(
    chain: str,
    rpc_url: str,
    wallet_address: str | None = None,
)
```

TraderJoe Liquidity Book V2 SDK.

Provides methods for:

- Token swaps (exact input)
- Add/remove liquidity
- Pool queries
- Bin math utilities

Parameters:

| Name             | Type  | Description                    | Default                                  |
| ---------------- | ----- | ------------------------------ | ---------------------------------------- |
| `chain`          | `str` | Chain name (e.g., "avalanche") | *required*                               |
| `rpc_url`        | `str` | RPC endpoint URL               | *required*                               |
| `wallet_address` | \`str | None\`                         | Optional wallet address for transactions |

Example

sdk = TraderJoeV2SDK( chain="avalanche", rpc_url="https://api.avax.network/ext/bc/C/rpc", )

#### Get pool info

pool = sdk.get_pool_address(wavax, usdc, bin_step=20)

#### Build swap transaction

tx, gas = sdk.build_swap_exact_tokens_for_tokens( amount_in=10\*\*18, amount_out_min=0, path=[wavax, usdc], bin_steps=[20], recipient="0x...", )

#### get_token_contract

```
get_token_contract(token_address: str) -> Contract
```

Get or create a token contract instance.

#### get_balance

```
get_balance(token_address: str, account: str) -> int
```

Get the token balance of an account.

#### get_allowance

```
get_allowance(
    token_address: str, owner: str, spender: str
) -> int
```

Get the allowance of a spender for a token owner.

#### get_pool_address

```
get_pool_address(
    token_x: str, token_y: str, bin_step: int
) -> str
```

Get the LBPair (pool) address for a given token pair and binStep.

Results are cached in-process: the LBFactory pair address is immutable and does not change after pool creation.

Parameters:

| Name       | Type  | Description                              | Default    |
| ---------- | ----- | ---------------------------------------- | ---------- |
| `token_x`  | `str` | Address of token X                       | *required* |
| `token_y`  | `str` | Address of token Y                       | *required* |
| `bin_step` | `int` | Bin step of the pair (e.g., 20 for 0.2%) | *required* |

Returns:

| Type  | Description                    |
| ----- | ------------------------------ |
| `str` | Address of the LBPair contract |

Raises:

| Type                | Description                            |
| ------------------- | -------------------------------------- |
| `PoolNotFoundError` | If no pool exists for the pair/binStep |

#### get_pair_contract

```
get_pair_contract(pool_address: str) -> Contract
```

Get or create a pair contract instance.

#### get_pool_info

```
get_pool_info(pool_address: str) -> PoolInfo
```

Get information about a pool.

#### get_pool_spot_rate

```
get_pool_spot_rate(pool_address: str) -> float
```

Get the current spot price from a TraderJoe V2 LBPair pool.

Price is calculated from the active bin ID using the formula: price = (1 + binStep/10000)^(activeId - 8388608) * 10^(decimalsX - decimalsY)

Parameters:

| Name           | Type  | Description                    | Default    |
| -------------- | ----- | ------------------------------ | ---------- |
| `pool_address` | `str` | Address of the LBPair contract | *required* |

Returns:

| Type    | Description                            |
| ------- | -------------------------------------- |
| `float` | Current spot price (tokenY per tokenX) |

#### bin_id_to_price

```
bin_id_to_price(
    bin_id: int,
    bin_step: int,
    decimals_x: int = 18,
    decimals_y: int = 18,
) -> float
```

Convert a bin ID to price using TraderJoe V2 formula.

Formula: price = (1 + binStep/10000)^(binId - 8388608) * 10^(decimalsX - decimalsY)

Parameters:

| Name         | Type  | Description                     | Default    |
| ------------ | ----- | ------------------------------- | ---------- |
| `bin_id`     | `int` | The bin ID                      | *required* |
| `bin_step`   | `int` | The bin step for the pair       | *required* |
| `decimals_x` | `int` | Decimals of tokenX (default 18) | `18`       |
| `decimals_y` | `int` | Decimals of tokenY (default 18) | `18`       |

Returns:

| Type    | Description                                      |
| ------- | ------------------------------------------------ |
| `float` | Price (tokenY per tokenX), adjusted for decimals |

#### price_to_bin_id

```
price_to_bin_id(
    price: float,
    bin_step: int,
    decimals_x: int = 18,
    decimals_y: int = 18,
) -> int
```

Convert a price to the nearest bin ID using TraderJoe V2 formula.

Inverse formula: binId = (log(price) - (decimalsX - decimalsY) * log(10)) / log(1 + binStep/10000) + 8388608

Parameters:

| Name         | Type    | Description                      | Default    |
| ------------ | ------- | -------------------------------- | ---------- |
| `price`      | `float` | Target price (tokenY per tokenX) | *required* |
| `bin_step`   | `int`   | The bin step for the pair        | *required* |
| `decimals_x` | `int`   | Decimals of tokenX (default 18)  | `18`       |
| `decimals_y` | `int`   | Decimals of tokenY (default 18)  | `18`       |

Returns:

| Type  | Description    |
| ----- | -------------- |
| `int` | Nearest bin ID |

#### build_approve_transaction

```
build_approve_transaction(
    token_address: str,
    spender_address: str,
    amount: int,
    from_address: str,
) -> tuple[dict[str, Any], int]
```

Build an approve transaction for a token.

Parameters:

| Name              | Type  | Description                             | Default    |
| ----------------- | ----- | --------------------------------------- | ---------- |
| `token_address`   | `str` | Address of the token to approve         | *required* |
| `spender_address` | `str` | Address of the spender (usually router) | *required* |
| `amount`          | `int` | Amount to approve (in wei)              | *required* |
| `from_address`    | `str` | Address of the token owner              | *required* |

Returns:

| Type                         | Description                                |
| ---------------------------- | ------------------------------------------ |
| `tuple[dict[str, Any], int]` | Tuple of (transaction dict, estimated gas) |

#### build_approve_for_all_transaction

```
build_approve_for_all_transaction(
    pool_address: str,
    spender_address: str,
    from_address: str,
    approved: bool = True,
) -> tuple[dict[str, Any], int]
```

Build approveForAll transaction for LB token (ERC1155-like).

LB tokens require approveForAll before the router can remove liquidity.

Parameters:

| Name              | Type   | Description                             | Default    |
| ----------------- | ------ | --------------------------------------- | ---------- |
| `pool_address`    | `str`  | Address of the LBPair contract          | *required* |
| `spender_address` | `str`  | Address of the spender (usually router) | *required* |
| `from_address`    | `str`  | Address of the token owner              | *required* |
| `approved`        | `bool` | Whether to approve or revoke            | `True`     |

Returns:

| Type                         | Description                                |
| ---------------------------- | ------------------------------------------ |
| `tuple[dict[str, Any], int]` | Tuple of (transaction dict, estimated gas) |

#### build_swap_exact_tokens_for_tokens

```
build_swap_exact_tokens_for_tokens(
    amount_in: int,
    amount_out_min: int,
    path: list[str],
    bin_steps: list[int],
    recipient: str,
    deadline: int | None = None,
) -> tuple[dict[str, Any], int]
```

Build transaction for swapping exact tokens for tokens.

Parameters:

| Name             | Type        | Description                                              | Default                                                 |
| ---------------- | ----------- | -------------------------------------------------------- | ------------------------------------------------------- |
| `amount_in`      | `int`       | Amount of input token (in wei)                           | *required*                                              |
| `amount_out_min` | `int`       | Minimum amount of output token (in wei)                  | *required*                                              |
| `path`           | `list[str]` | List of token addresses [tokenIn, tokenOut] or multi-hop | *required*                                              |
| `bin_steps`      | `list[int]` | List of binSteps for each pair in the path               | *required*                                              |
| `recipient`      | `str`       | Address to receive output tokens                         | *required*                                              |
| `deadline`       | \`int       | None\`                                                   | Transaction deadline (default: current time + 100 days) |

Returns:

| Type                         | Description                                |
| ---------------------------- | ------------------------------------------ |
| `tuple[dict[str, Any], int]` | Tuple of (transaction dict, estimated gas) |

#### build_add_liquidity

```
build_add_liquidity(
    token_x: str,
    token_y: str,
    bin_step: int,
    amount_x: int,
    amount_y: int,
    amount_x_min: int,
    amount_y_min: int,
    active_id_desired: int,
    id_slippage: int,
    delta_ids: list[int],
    distribution_x: list[int],
    distribution_y: list[int],
    to: str,
    refund_to: str,
    deadline: int | None = None,
) -> tuple[dict[str, Any], int]
```

Build transaction for adding liquidity to a TraderJoe V2 pair.

Parameters:

| Name                | Type        | Description                                               | Default              |
| ------------------- | ----------- | --------------------------------------------------------- | -------------------- |
| `token_x`           | `str`       | Address of token X                                        | *required*           |
| `token_y`           | `str`       | Address of token Y                                        | *required*           |
| `bin_step`          | `int`       | Bin step of the pair                                      | *required*           |
| `amount_x`          | `int`       | Amount of token X to add                                  | *required*           |
| `amount_y`          | `int`       | Amount of token Y to add                                  | *required*           |
| `amount_x_min`      | `int`       | Minimum amount of token X (slippage protection)           | *required*           |
| `amount_y_min`      | `int`       | Minimum amount of token Y (slippage protection)           | *required*           |
| `active_id_desired` | `int`       | Desired active bin ID                                     | *required*           |
| `id_slippage`       | `int`       | Allowed slippage on active bin ID                         | *required*           |
| `delta_ids`         | `list[int]` | Delta IDs for liquidity distribution (relative to active) | *required*           |
| `distribution_x`    | `list[int]` | Distribution of token X across bins (sum to 10^18)        | *required*           |
| `distribution_y`    | `list[int]` | Distribution of token Y across bins (sum to 10^18)        | *required*           |
| `to`                | `str`       | Address to mint LB tokens to                              | *required*           |
| `refund_to`         | `str`       | Address to refund excess tokens to                        | *required*           |
| `deadline`          | \`int       | None\`                                                    | Transaction deadline |

Returns:

| Type                         | Description                                |
| ---------------------------- | ------------------------------------------ |
| `tuple[dict[str, Any], int]` | Tuple of (transaction dict, estimated gas) |

#### build_remove_liquidity

```
build_remove_liquidity(
    token_x: str,
    token_y: str,
    bin_step: int,
    amount_x_min: int,
    amount_y_min: int,
    ids: list[int],
    amounts: list[int],
    to: str,
    deadline: int | None = None,
) -> tuple[dict[str, Any], int]
```

Build transaction for removing liquidity from a TraderJoe V2 pair.

Parameters:

| Name           | Type        | Description                                        | Default              |
| -------------- | ----------- | -------------------------------------------------- | -------------------- |
| `token_x`      | `str`       | Address of token X                                 | *required*           |
| `token_y`      | `str`       | Address of token Y                                 | *required*           |
| `bin_step`     | `int`       | Bin step of the pair                               | *required*           |
| `amount_x_min` | `int`       | Minimum amount of token X to receive               | *required*           |
| `amount_y_min` | `int`       | Minimum amount of token Y to receive               | *required*           |
| `ids`          | `list[int]` | Array of bin IDs to remove liquidity from          | *required*           |
| `amounts`      | `list[int]` | Array of amounts of LB tokens to burn for each bin | *required*           |
| `to`           | `str`       | Address to receive tokens                          | *required*           |
| `deadline`     | \`int       | None\`                                             | Transaction deadline |

Returns:

| Type                         | Description                                |
| ---------------------------- | ------------------------------------------ |
| `tuple[dict[str, Any], int]` | Tuple of (transaction dict, estimated gas) |

#### build_collect_fees

```
build_collect_fees(
    pool_address: str, account: str, ids: list[int]
) -> tuple[dict[str, Any], int]
```

Build transaction for collecting accumulated fees from an LP position.

Calls LBPair.collectFees(account, ids) which collects fees without removing any liquidity. This is a V2.1 feature of TraderJoe's Liquidity Book.

The returned bytes32[] encodes fee amounts where each bytes32 has amountX in the upper 128 bits and amountY in the lower 128 bits.

Parameters:

| Name           | Type        | Description                                | Default    |
| -------------- | ----------- | ------------------------------------------ | ---------- |
| `pool_address` | `str`       | Address of the LBPair contract             | *required* |
| `account`      | `str`       | Address of the account to collect fees for | *required* |
| `ids`          | `list[int]` | Array of bin IDs to collect fees from      | *required* |

Returns:

| Type                         | Description                                |
| ---------------------------- | ------------------------------------------ |
| `tuple[dict[str, Any], int]` | Tuple of (transaction dict, estimated gas) |

Raises:

| Type                  | Description            |
| --------------------- | ---------------------- |
| `TraderJoeV2SDKError` | If no bin IDs provided |

#### get_pending_fees

```
get_pending_fees(
    pool_address: str, account: str, ids: list[int]
) -> tuple[int, int]
```

Query pending (uncollected) fees for a position.

Parameters:

| Name           | Type        | Description                        | Default    |
| -------------- | ----------- | ---------------------------------- | ---------- |
| `pool_address` | `str`       | Address of the LBPair contract     | *required* |
| `account`      | `str`       | Address of the account to query    | *required* |
| `ids`          | `list[int]` | Array of bin IDs to query fees for | *required* |

Returns:

| Type              | Description                                  |
| ----------------- | -------------------------------------------- |
| `tuple[int, int]` | Tuple of (total_fees_x, total_fees_y) in wei |

#### get_position_balances

```
get_position_balances(
    pool_address: str,
    wallet_address: str,
    bin_range: int = 50,
) -> dict[int, int]
```

Get LB token balances for a wallet across bins.

Parameters:

| Name             | Type  | Description                                        | Default    |
| ---------------- | ----- | -------------------------------------------------- | ---------- |
| `pool_address`   | `str` | Address of the LBPair contract                     | *required* |
| `wallet_address` | `str` | Address to query balances for                      | *required* |
| `bin_range`      | `int` | Number of bins to check on each side of active bin | `50`       |

Returns:

| Type             | Description                    |
| ---------------- | ------------------------------ |
| `dict[int, int]` | Dict mapping bin ID to balance |

#### get_total_position_value

```
get_total_position_value(
    pool_address: str,
    wallet_address: str,
    precomputed_balances: dict[int, int] | None = None,
) -> tuple[int, int]
```

Get total token amounts for a wallet's position in a pool.

Parameters:

| Name                   | Type             | Description                    | Default                                                                                                                                           |
| ---------------------- | ---------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `pool_address`         | `str`            | Address of the LBPair contract | *required*                                                                                                                                        |
| `wallet_address`       | `str`            | Address to query               | *required*                                                                                                                                        |
| `precomputed_balances` | \`dict[int, int] | None\`                         | Optional pre-fetched bin balances to avoid a redundant get_position_balances() call (pass the result from a prior call to get_position_balances). |

Returns:

| Type              | Description                                                                      |
| ----------------- | -------------------------------------------------------------------------------- |
| `tuple[int, int]` | Tuple of (amount_x, amount_y) the wallet would receive if removing all liquidity |

### TraderJoeV2SDKError

Bases: `Exception`

Base exception for TraderJoe V2 SDK errors.

### SDKSwapQuote

```
SDKSwapQuote(
    amount_in: int,
    amount_out: int,
    path: list[str],
    bin_steps: list[int],
    price_impact: Decimal,
    fee: int,
)
```

Quote for a swap operation.

# Uniswap V3

Connector for Uniswap V3 concentrated liquidity DEX.

## almanak.framework.connectors.uniswap_v3

Uniswap V3 Connector.

This module provides the Uniswap V3 adapter for executing token swaps on Uniswap V3 across multiple chains.

Supported chains:

- Ethereum
- Arbitrum
- Optimism
- Polygon
- Base

Example

from almanak.framework.connectors.uniswap_v3 import UniswapV3Adapter, UniswapV3Config

config = UniswapV3Config( chain="arbitrum", wallet_address="0x...", ) adapter = UniswapV3Adapter(config)

### Execute a swap

result = adapter.swap_exact_input( token_in="USDC", token_out="WETH", amount_in=Decimal("1000"), )

### Use SDK for lower-level operations

from almanak.framework.connectors.uniswap_v3 import UniswapV3SDK

sdk = UniswapV3SDK(chain="arbitrum", rpc_url="https://arb1.arbitrum.io/rpc") pool = sdk.get_pool_address(weth_addr, usdc_addr, fee_tier=3000)

### SwapQuote

```
SwapQuote(
    token_in: str,
    token_out: str,
    amount_in: int,
    amount_out: int,
    fee_tier: int,
    sqrt_price_x96_after: int = 0,
    gas_estimate: int = UNISWAP_V3_GAS_ESTIMATES[
        "swap_exact_input"
    ],
    price_impact_bps: int = 0,
    effective_price: Decimal = Decimal("0"),
    quoted_at: datetime = (lambda: datetime.now(UTC))(),
)
```

Quote for a swap operation.

Attributes:

| Name                   | Type       | Description                      |
| ---------------------- | ---------- | -------------------------------- |
| `token_in`             | `str`      | Input token address              |
| `token_out`            | `str`      | Output token address             |
| `amount_in`            | `int`      | Input amount in wei              |
| `amount_out`           | `int`      | Output amount in wei             |
| `fee_tier`             | `int`      | Fee tier of the pool             |
| `sqrt_price_x96_after` | `int`      | Price after swap (sqrt format)   |
| `gas_estimate`         | `int`      | Estimated gas for the swap       |
| `price_impact_bps`     | `int`      | Price impact in basis points     |
| `effective_price`      | `Decimal`  | Effective price of the swap      |
| `quoted_at`            | `datetime` | Timestamp when quote was fetched |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapResult

```
SwapResult(
    success: bool,
    transactions: list[TransactionData] = list(),
    quote: SwapQuote | None = None,
    amount_in: int = 0,
    amount_out_minimum: int = 0,
    error: str | None = None,
    gas_estimate: int = 0,
)
```

Result of a swap operation.

Attributes:

| Name                 | Type                    | Description                             |
| -------------------- | ----------------------- | --------------------------------------- |
| `success`            | `bool`                  | Whether the swap was built successfully |
| `transactions`       | `list[TransactionData]` | List of transactions to execute         |
| `quote`              | \`SwapQuote             | None\`                                  |
| `amount_in`          | `int`                   | Actual input amount                     |
| `amount_out_minimum` | `int`                   | Minimum output amount (with slippage)   |
| `error`              | \`str                   | None\`                                  |
| `gas_estimate`       | `int`                   | Total gas estimate for all transactions |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapType

Bases: `Enum`

Type of swap operation.

### TransactionData

```
TransactionData(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
    tx_type: str = "swap",
)
```

Transaction data for execution.

Attributes:

| Name           | Type  | Description                         |
| -------------- | ----- | ----------------------------------- |
| `to`           | `str` | Target contract address             |
| `value`        | `int` | Native token value to send          |
| `data`         | `str` | Encoded calldata                    |
| `gas_estimate` | `int` | Estimated gas                       |
| `description`  | `str` | Human-readable description          |
| `tx_type`      | `str` | Type of transaction (approve, swap) |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### UniswapV3Adapter

```
UniswapV3Adapter(
    config: UniswapV3Config,
    token_resolver: TokenResolver | None = None,
)
```

Adapter for Uniswap V3 DEX protocol.

This adapter provides methods for:

- Executing token swaps (exact input and exact output)
- Building swap transactions
- Handling ERC-20 approvals
- Managing slippage protection

Example

config = UniswapV3Config( chain="arbitrum", wallet_address="0x...", ) adapter = UniswapV3Adapter(config)

#### Execute a swap

result = adapter.swap_exact_input( token_in="USDC", token_out="WETH", amount_in=Decimal("1000"), # 1000 USDC slippage_bps=50, )

#### Compile a SwapIntent to ActionBundle

intent = SwapIntent( from_token="USDC", to_token="WETH", amount_usd=Decimal("1000"), ) bundle = adapter.compile_swap_intent(intent)

Initialize the adapter.

Parameters:

| Name             | Type              | Description                      | Default                                                   |
| ---------------- | ----------------- | -------------------------------- | --------------------------------------------------------- |
| `config`         | `UniswapV3Config` | Uniswap V3 adapter configuration | *required*                                                |
| `token_resolver` | \`TokenResolver   | None\`                           | Optional TokenResolver instance. If None, uses singleton. |

#### swap_exact_input

```
swap_exact_input(
    token_in: str,
    token_out: str,
    amount_in: Decimal,
    slippage_bps: int | None = None,
    fee_tier: int | None = None,
    recipient: str | None = None,
) -> SwapResult
```

Build a swap transaction with exact input amount.

This is the most common swap type where you specify exactly how much you want to spend and accept variable output.

Parameters:

| Name           | Type      | Description                                     | Default                                                    |
| -------------- | --------- | ----------------------------------------------- | ---------------------------------------------------------- |
| `token_in`     | `str`     | Input token symbol or address                   | *required*                                                 |
| `token_out`    | `str`     | Output token symbol or address                  | *required*                                                 |
| `amount_in`    | `Decimal` | Amount of input token (in token units, not wei) | *required*                                                 |
| `slippage_bps` | \`int     | None\`                                          | Slippage tolerance in basis points (default from config)   |
| `fee_tier`     | \`int     | None\`                                          | Pool fee tier (default from config)                        |
| `recipient`    | \`str     | None\`                                          | Address to receive output tokens (default: wallet_address) |

Returns:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `SwapResult` | SwapResult with transaction data |

#### swap_exact_output

```
swap_exact_output(
    token_in: str,
    token_out: str,
    amount_out: Decimal,
    slippage_bps: int | None = None,
    fee_tier: int | None = None,
    recipient: str | None = None,
) -> SwapResult
```

Build a swap transaction with exact output amount.

This swap type specifies exactly how much you want to receive and accepts variable input.

Parameters:

| Name           | Type      | Description                                      | Default                                                    |
| -------------- | --------- | ------------------------------------------------ | ---------------------------------------------------------- |
| `token_in`     | `str`     | Input token symbol or address                    | *required*                                                 |
| `token_out`    | `str`     | Output token symbol or address                   | *required*                                                 |
| `amount_out`   | `Decimal` | Amount of output token (in token units, not wei) | *required*                                                 |
| `slippage_bps` | \`int     | None\`                                           | Slippage tolerance in basis points (default from config)   |
| `fee_tier`     | \`int     | None\`                                           | Pool fee tier (default from config)                        |
| `recipient`    | \`str     | None\`                                           | Address to receive output tokens (default: wallet_address) |

Returns:

| Type         | Description                      |
| ------------ | -------------------------------- |
| `SwapResult` | SwapResult with transaction data |

#### compile_swap_intent

```
compile_swap_intent(
    intent: SwapIntent,
    price_oracle: dict[str, Decimal] | None = None,
) -> ActionBundle
```

Compile a SwapIntent to an ActionBundle.

This method integrates with the intent system to convert high-level swap intents into executable transaction bundles.

Parameters:

| Name           | Type                 | Description               | Default                                   |
| -------------- | -------------------- | ------------------------- | ----------------------------------------- |
| `intent`       | `SwapIntent`         | The SwapIntent to compile | *required*                                |
| `price_oracle` | \`dict[str, Decimal] | None\`                    | Optional price oracle for USD conversions |

Returns:

| Type           | Description                                        |
| -------------- | -------------------------------------------------- |
| `ActionBundle` | ActionBundle containing transactions for execution |

#### set_allowance

```
set_allowance(
    token: str, spender: str, amount: int
) -> None
```

Set cached allowance (for testing).

Parameters:

| Name      | Type  | Description      | Default    |
| --------- | ----- | ---------------- | ---------- |
| `token`   | `str` | Token address    | *required* |
| `spender` | `str` | Spender address  | *required* |
| `amount`  | `int` | Allowance amount | *required* |

#### clear_allowance_cache

```
clear_allowance_cache() -> None
```

Clear the allowance cache.

### UniswapV3Config

```
UniswapV3Config(
    chain: str,
    wallet_address: str,
    default_slippage_bps: int = 50,
    default_fee_tier: int = DEFAULT_FEE_TIER,
    deadline_seconds: int = 300,
    price_provider: dict[str, Decimal] | None = None,
    allow_placeholder_prices: bool = False,
)
```

Configuration for UniswapV3Adapter.

Attributes:

| Name                       | Type                 | Description                                                                                             |
| -------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------- |
| `chain`                    | `str`                | Target blockchain (ethereum, arbitrum, optimism, polygon, base)                                         |
| `wallet_address`           | `str`                | Address executing transactions                                                                          |
| `default_slippage_bps`     | `int`                | Default slippage tolerance in basis points (default 50 = 0.5%)                                          |
| `default_fee_tier`         | `int`                | Default fee tier for pools (default 3000 = 0.3%)                                                        |
| `deadline_seconds`         | `int`                | Transaction deadline in seconds (default 300 = 5 minutes)                                               |
| `price_provider`           | \`dict[str, Decimal] | None\`                                                                                                  |
| `allow_placeholder_prices` | `bool`               | If False (default), raises ValueError when no price_provider is given. Set to True ONLY for unit tests. |

#### __post_init__

```
__post_init__() -> None
```

Validate configuration.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### ParsedSwapResult

```
ParsedSwapResult(
    token_in: str,
    token_out: str,
    token_in_symbol: str,
    token_out_symbol: str,
    amount_in: int,
    amount_out: int,
    amount_in_decimal: Decimal,
    amount_out_decimal: Decimal,
    effective_price: Decimal,
    slippage_bps: int,
    pool_address: str,
    sqrt_price_x96_after: int = 0,
    tick_after: int = 0,
)
```

High-level swap result extracted from receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> ParsedSwapResult
```

Create from dictionary.

#### to_swap_result_payload

```
to_swap_result_payload() -> SwapResultPayload
```

Convert to SwapResultPayload for event emission.

### ParseResult

```
ParseResult(
    success: bool,
    events: list[UniswapV3Event] = list(),
    swap_events: list[SwapEventData] = list(),
    transfer_events: list[TransferEventData] = list(),
    swap_result: ParsedSwapResult | None = None,
    error: str | None = None,
    transaction_hash: str = "",
    block_number: int = 0,
    transaction_success: bool = True,
)
```

Result of parsing a receipt.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### SwapEventData

```
SwapEventData(
    sender: str,
    recipient: str,
    amount0: int,
    amount1: int,
    sqrt_price_x96: int,
    liquidity: int,
    tick: int,
    pool_address: str,
)
```

Parsed data from Swap event.

#### token0_is_input

```
token0_is_input: bool
```

Check if token0 is the input token.

#### token1_is_input

```
token1_is_input: bool
```

Check if token1 is the input token.

#### amount_in

```
amount_in: int
```

Get the absolute input amount.

#### amount_out

```
amount_out: int
```

Get the absolute output amount.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> SwapEventData
```

Create from dictionary.

### TransferEventData

```
TransferEventData(
    from_addr: str,
    to_addr: str,
    value: int,
    token_address: str,
)
```

Parsed data from Transfer event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### UniswapV3Event

```
UniswapV3Event(
    event_type: UniswapV3EventType,
    event_name: str,
    log_index: int,
    transaction_hash: str,
    block_number: int,
    contract_address: str,
    data: dict[str, Any],
    raw_topics: list[str] = list(),
    raw_data: str = "",
    timestamp: datetime = (lambda: datetime.now(UTC))(),
)
```

Parsed Uniswap V3 event.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> UniswapV3Event
```

Create from dictionary.

### UniswapV3EventType

Bases: `Enum`

Uniswap V3 event types.

### UniswapV3ReceiptParser

```
UniswapV3ReceiptParser(
    chain: str = "arbitrum",
    token0_address: str | None = None,
    token1_address: str | None = None,
    token0_symbol: str | None = None,
    token1_symbol: str | None = None,
    token0_decimals: int | None = None,
    token1_decimals: int | None = None,
    quoted_price: Decimal | None = None,
    **kwargs: Any,
)
```

Parser for Uniswap V3 transaction receipts.

Refactored to use base infrastructure utilities for hex decoding and event registry management. Maintains full backward compatibility.

Initialize the parser.

Parameters:

| Name              | Type      | Description                                      | Default                                 |
| ----------------- | --------- | ------------------------------------------------ | --------------------------------------- |
| `chain`           | `str`     | Blockchain network (for token symbol resolution) | `'arbitrum'`                            |
| `token0_address`  | \`str     | None\`                                           | Address of token0 in the pool           |
| `token1_address`  | \`str     | None\`                                           | Address of token1 in the pool           |
| `token0_symbol`   | \`str     | None\`                                           | Symbol of token0                        |
| `token1_symbol`   | \`str     | None\`                                           | Symbol of token1                        |
| `token0_decimals` | \`int     | None\`                                           | Decimals for token0                     |
| `token1_decimals` | \`int     | None\`                                           | Decimals for token1                     |
| `quoted_price`    | \`Decimal | None\`                                           | Expected price for slippage calculation |

#### parse_receipt

```
parse_receipt(
    receipt: dict[str, Any],
    quoted_amount_out: int | None = None,
) -> ParseResult
```

Parse a transaction receipt.

Parameters:

| Name                | Type             | Description              | Default                                         |
| ------------------- | ---------------- | ------------------------ | ----------------------------------------------- |
| `receipt`           | `dict[str, Any]` | Transaction receipt dict | *required*                                      |
| `quoted_amount_out` | \`int            | None\`                   | Expected output amount for slippage calculation |

Returns:

| Type          | Description                                     |
| ------------- | ----------------------------------------------- |
| `ParseResult` | ParseResult with extracted events and swap data |

#### parse_logs

```
parse_logs(
    logs: list[dict[str, Any]],
) -> list[UniswapV3Event]
```

Parse a list of logs.

Parameters:

| Name   | Type                   | Description       | Default    |
| ------ | ---------------------- | ----------------- | ---------- |
| `logs` | `list[dict[str, Any]]` | List of log dicts | *required* |

Returns:

| Type                   | Description           |
| ---------------------- | --------------------- |
| `list[UniswapV3Event]` | List of parsed events |

#### extract_position_id

```
extract_position_id(receipt: dict[str, Any]) -> int | None
```

Extract LP position ID (NFT tokenId) from a transaction receipt.

Looks for ERC-721 Transfer events from the NonfungiblePositionManager where from=address(0), indicating a mint (new position created).

For ERC-721 Transfer events, the signature is: Transfer(address indexed from, address indexed to, uint256 indexed tokenId) All parameters are indexed, so tokenId is in topics[3], not in data.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

Example

> > > parser = UniswapV3ReceiptParser(chain="arbitrum") position_id = parser.extract_position_id(receipt) if position_id: ... print(f"Opened position: {position_id}")

#### extract_position_id_from_logs

```
extract_position_id_from_logs(
    logs: list[dict[str, Any]], chain: str = "arbitrum"
) -> int | None
```

Static method to extract position ID from logs without instantiating parser.

Convenience method for cases where you just need to extract the position ID without parsing other events.

Parameters:

| Name    | Type                   | Description                                    | Default      |
| ------- | ---------------------- | ---------------------------------------------- | ------------ |
| `logs`  | `list[dict[str, Any]]` | List of log dicts from transaction receipt     | *required*   |
| `chain` | `str`                  | Chain name for position manager address lookup | `'arbitrum'` |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

Example

> > > position_id = UniswapV3ReceiptParser.extract_position_id_from_logs( ... receipt["logs"], chain="arbitrum" ... )

#### extract_swap_amounts

```
extract_swap_amounts(
    receipt: dict[str, Any],
) -> SwapAmounts | None
```

Extract swap amounts from a transaction receipt.

This method is called by the ResultEnricher to automatically populate ExecutionResult.swap_amounts for SWAP intents.

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`SwapAmounts | None\`      |

Example

> > > parser = UniswapV3ReceiptParser(chain="arbitrum") swap_amounts = parser.extract_swap_amounts(receipt) if swap_amounts: ... print(f"Swapped: {swap_amounts.amount_in_decimal}")

#### extract_tick_lower

```
extract_tick_lower(receipt: dict[str, Any]) -> int | None
```

Extract tick lower from LP mint transaction receipt.

Looks for Mint events from Uniswap V3 pools. tickLower is an indexed parameter in topics[2].

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_tick_upper

```
extract_tick_upper(receipt: dict[str, Any]) -> int | None
```

Extract tick upper from LP mint transaction receipt.

Looks for Mint events from Uniswap V3 pools. tickUpper is an indexed parameter in topics[3].

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_liquidity

```
extract_liquidity(receipt: dict[str, Any]) -> int | None
```

Extract liquidity from LP mint transaction receipt.

Looks for Mint events from Uniswap V3 pools. Liquidity amount is in the data field.

Mint event data layout:

- amount (uint128): offset 0
- amount0 (uint256): offset 16
- amount1 (uint256): offset 48

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> LPCloseData | None
```

Extract LP close data from transaction receipt.

Looks for Collect events from Uniswap V3 pools which indicate fees and principal being collected when closing/reducing a position.

Collect(address indexed owner, int24 indexed tickLower,

int24 indexed tickUpper, uint128 amount0, uint128 amount1)

Parameters:

| Name      | Type             | Description                                | Default    |
| --------- | ---------------- | ------------------------------------------ | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict with 'logs' field | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`LPCloseData | None\`      |

#### is_uniswap_event

```
is_uniswap_event(topic: str | bytes) -> bool
```

Check if a topic is a known Uniswap V3 event.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type   | Description                               |
| ------ | ----------------------------------------- |
| `bool` | True if topic is a known Uniswap V3 event |

#### get_event_type

```
get_event_type(topic: str | bytes) -> UniswapV3EventType
```

Get the event type for a topic.

Parameters:

| Name    | Type  | Description | Default                                                            |
| ------- | ----- | ----------- | ------------------------------------------------------------------ |
| `topic` | \`str | bytes\`     | Event topic (supports bytes, hex string with/without 0x, any case) |

Returns:

| Type                 | Description           |
| -------------------- | --------------------- |
| `UniswapV3EventType` | Event type or UNKNOWN |

### InvalidFeeError

```
InvalidFeeError(fee: int)
```

Bases: `UniswapV3SDKError`

Invalid fee tier provided.

### InvalidTickError

```
InvalidTickError(tick: int, reason: str)
```

Bases: `UniswapV3SDKError`

Invalid tick value provided.

### PoolInfo

```
PoolInfo(
    address: str,
    token0: str,
    token1: str,
    fee: int,
    tick_spacing: int,
)
```

Information about a Uniswap V3 pool.

Attributes:

| Name           | Type  | Description                     |
| -------------- | ----- | ------------------------------- |
| `address`      | `str` | Computed pool address           |
| `token0`       | `str` | First token address (sorted)    |
| `token1`       | `str` | Second token address (sorted)   |
| `fee`          | `int` | Fee tier in hundredths of a bip |
| `tick_spacing` | `int` | Tick spacing for this fee tier  |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### PoolNotFoundError

```
PoolNotFoundError(token0: str, token1: str, fee: int)
```

Bases: `UniswapV3SDKError`

Pool does not exist.

### PoolState

```
PoolState(
    sqrt_price_x96: int,
    tick: int,
    liquidity: int,
    fee_growth_global_0: int = 0,
    fee_growth_global_1: int = 0,
)
```

Current state of a Uniswap V3 pool.

Attributes:

| Name                  | Type  | Description                        |
| --------------------- | ----- | ---------------------------------- |
| `sqrt_price_x96`      | `int` | Current sqrt price (Q64.96 format) |
| `tick`                | `int` | Current tick                       |
| `liquidity`           | `int` | Current in-range liquidity         |
| `fee_growth_global_0` | `int` | Fee growth for token0              |
| `fee_growth_global_1` | `int` | Fee growth for token1              |

#### price

```
price: Decimal
```

Get current price (token1 per token0).

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### QuoteError

```
QuoteError(message: str, token_in: str, token_out: str)
```

Bases: `UniswapV3SDKError`

Error fetching quote.

### SwapTransaction

```
SwapTransaction(
    to: str,
    value: int,
    data: str,
    gas_estimate: int,
    description: str,
)
```

Transaction data for a swap.

Attributes:

| Name           | Type  | Description                                |
| -------------- | ----- | ------------------------------------------ |
| `to`           | `str` | Router address                             |
| `value`        | `int` | ETH value to send (for native token swaps) |
| `data`         | `str` | Encoded calldata                           |
| `gas_estimate` | `int` | Estimated gas                              |
| `description`  | `str` | Human-readable description                 |

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

### UniswapV3SDK

```
UniswapV3SDK(
    chain: str,
    rpc_url: str | None = None,
    web3: Any | None = None,
)
```

SDK for Uniswap V3 operations.

This class provides methods for:

- Computing pool addresses
- Fetching quotes (requires RPC)
- Building swap transactions
- Tick math utilities

Example

sdk = UniswapV3SDK(chain="arbitrum", rpc_url="https://arb1.arbitrum.io/rpc")

#### Compute pool address (no RPC needed)

pool_info = sdk.get_pool_address( "0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH "0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC fee_tier=3000, )

#### Get quote (requires RPC)

quote = await sdk.get_quote( token_in=weth_address, token_out=usdc_address, amount_in=10\*\*18, # 1 WETH fee_tier=3000, )

Initialize the SDK.

Parameters:

| Name      | Type  | Description                                                     | Default                                 |
| --------- | ----- | --------------------------------------------------------------- | --------------------------------------- |
| `chain`   | `str` | Target blockchain (ethereum, arbitrum, optimism, polygon, base) | *required*                              |
| `rpc_url` | \`str | None\`                                                          | RPC URL for on-chain queries (optional) |
| `web3`    | \`Any | None\`                                                          | Existing Web3 instance (optional)       |

Raises:

| Type         | Description               |
| ------------ | ------------------------- |
| `ValueError` | If chain is not supported |

#### get_pool_address

```
get_pool_address(
    token0: str, token1: str, fee_tier: int
) -> PoolInfo
```

Get the pool address for a token pair.

This computes the CREATE2 address deterministically without RPC calls.

Parameters:

| Name       | Type  | Description                      | Default    |
| ---------- | ----- | -------------------------------- | ---------- |
| `token0`   | `str` | First token address              | *required* |
| `token1`   | `str` | Second token address             | *required* |
| `fee_tier` | `int` | Fee tier (100, 500, 3000, 10000) | *required* |

Returns:

| Type       | Description                                       |
| ---------- | ------------------------------------------------- |
| `PoolInfo` | PoolInfo with computed address and token ordering |

Raises:

| Type              | Description              |
| ----------------- | ------------------------ |
| `InvalidFeeError` | If fee_tier is not valid |

Example

> > > pool = sdk.get_pool_address(weth, usdc, fee_tier=3000) print(f"Pool address: {pool.address}")

#### get_quote

```
get_quote(
    token_in: str,
    token_out: str,
    amount_in: int,
    fee_tier: int,
) -> SwapQuote
```

Get a quote for a swap.

This fetches the expected output amount for a given input amount by calling the QuoterV2 contract.

Parameters:

| Name        | Type  | Description          | Default    |
| ----------- | ----- | -------------------- | ---------- |
| `token_in`  | `str` | Input token address  | *required* |
| `token_out` | `str` | Output token address | *required* |
| `amount_in` | `int` | Input amount in wei  | *required* |
| `fee_tier`  | `int` | Fee tier             | *required* |

Returns:

| Type        | Description                    |
| ----------- | ------------------------------ |
| `SwapQuote` | SwapQuote with expected output |

Raises:

| Type              | Description              |
| ----------------- | ------------------------ |
| `QuoteError`      | If quote fails           |
| `InvalidFeeError` | If fee_tier is not valid |

Note

Requires RPC connection. Use get_quote_local for offline estimation.

#### get_quote_local

```
get_quote_local(
    token_in: str,
    token_out: str,
    amount_in: int,
    fee_tier: int,
    price_ratio: Decimal | None = None,
) -> SwapQuote
```

Get an estimated quote without RPC calls.

This provides an approximation based on fee tier only. For accurate quotes, use get_quote() with RPC.

Parameters:

| Name          | Type      | Description          | Default                                 |
| ------------- | --------- | -------------------- | --------------------------------------- |
| `token_in`    | `str`     | Input token address  | *required*                              |
| `token_out`   | `str`     | Output token address | *required*                              |
| `amount_in`   | `int`     | Input amount in wei  | *required*                              |
| `fee_tier`    | `int`     | Fee tier             | *required*                              |
| `price_ratio` | \`Decimal | None\`               | Optional token_out/token_in price ratio |

Returns:

| Type        | Description                     |
| ----------- | ------------------------------- |
| `SwapQuote` | SwapQuote with estimated output |

#### build_swap_tx

```
build_swap_tx(
    quote: SwapQuote,
    recipient: str,
    slippage_bps: int,
    deadline: int,
    value: int = 0,
) -> SwapTransaction
```

Build a swap transaction from a quote.

Parameters:

| Name           | Type        | Description                                | Default    |
| -------------- | ----------- | ------------------------------------------ | ---------- |
| `quote`        | `SwapQuote` | Quote from get_quote()                     | *required* |
| `recipient`    | `str`       | Address to receive output tokens           | *required* |
| `slippage_bps` | `int`       | Slippage tolerance in basis points         | *required* |
| `deadline`     | `int`       | Unix timestamp deadline                    | *required* |
| `value`        | `int`       | ETH value to send (for native token swaps) | `0`        |

Returns:

| Type              | Description                           |
| ----------------- | ------------------------------------- |
| `SwapTransaction` | SwapTransaction with encoded calldata |

Example

> > > quote = await sdk.get_quote(weth, usdc, 10\*\*18, 3000) tx = sdk.build_swap_tx( ... quote=quote, ... recipient="0x...", ... slippage_bps=50, ... deadline=int(time.time()) + 300, ... )

#### build_exact_output_swap_tx

```
build_exact_output_swap_tx(
    token_in: str,
    token_out: str,
    fee: int,
    recipient: str,
    deadline: int,
    amount_out: int,
    amount_in_maximum: int,
    value: int = 0,
) -> SwapTransaction
```

Build an exact output swap transaction.

For swaps where you specify the exact output amount.

Parameters:

| Name                | Type  | Description                                | Default    |
| ------------------- | ----- | ------------------------------------------ | ---------- |
| `token_in`          | `str` | Input token address                        | *required* |
| `token_out`         | `str` | Output token address                       | *required* |
| `fee`               | `int` | Fee tier                                   | *required* |
| `recipient`         | `str` | Address to receive output tokens           | *required* |
| `deadline`          | `int` | Unix timestamp deadline                    | *required* |
| `amount_out`        | `int` | Exact output amount desired                | *required* |
| `amount_in_maximum` | `int` | Maximum input amount (with slippage)       | *required* |
| `value`             | `int` | ETH value to send (for native token swaps) | `0`        |

Returns:

| Type              | Description                           |
| ----------------- | ------------------------------------- |
| `SwapTransaction` | SwapTransaction with encoded calldata |

### UniswapV3SDKError

Bases: `Exception`

Base exception for Uniswap V3 SDK errors.

### SDKSwapQuote

```
SDKSwapQuote(
    token_in: str,
    token_out: str,
    amount_in: int,
    amount_out: int,
    fee: int,
    sqrt_price_x96_after: int = 0,
    initialized_ticks_crossed: int = 0,
    gas_estimate: int = 150000,
    quoted_at: datetime = (lambda: datetime.now(UTC))(),
)
```

Quote for a swap operation.

Attributes:

| Name                        | Type       | Description                         |
| --------------------------- | ---------- | ----------------------------------- |
| `token_in`                  | `str`      | Input token address                 |
| `token_out`                 | `str`      | Output token address                |
| `amount_in`                 | `int`      | Input amount in wei                 |
| `amount_out`                | `int`      | Expected output amount in wei       |
| `fee`                       | `int`      | Fee tier                            |
| `sqrt_price_x96_after`      | `int`      | Price after swap                    |
| `initialized_ticks_crossed` | `int`      | Number of initialized ticks crossed |
| `gas_estimate`              | `int`      | Estimated gas for the swap          |
| `quoted_at`                 | `datetime` | Timestamp when quote was fetched    |

#### effective_price

```
effective_price: Decimal
```

Calculate effective price of the swap.

#### to_dict

```
to_dict() -> dict[str, Any]
```

Convert to dictionary.

#### from_dict

```
from_dict(data: dict[str, Any]) -> SwapQuote
```

Create from dictionary.

### compute_pool_address

```
compute_pool_address(
    factory: str,
    token0: str,
    token1: str,
    fee: int,
    init_code_hash: str = POOL_INIT_CODE_HASH,
) -> str
```

Compute the CREATE2 address for a Uniswap V3 pool.

This deterministically computes the pool address without any RPC calls.

Parameters:

| Name      | Type  | Description              | Default    |
| --------- | ----- | ------------------------ | ---------- |
| `factory` | `str` | Factory contract address | *required* |
| `token0`  | `str` | First token address      | *required* |
| `token1`  | `str` | Second token address     | *required* |
| `fee`     | `int` | Fee tier                 | *required* |

Returns:

| Type  | Description  |
| ----- | ------------ |
| `str` | Pool address |

Raises:

| Type              | Description                |
| ----------------- | -------------------------- |
| `InvalidFeeError` | If fee is not a valid tier |

Example

> > > pool_addr = compute_pool_address( ... factory="0x1F98431c8aD98523631AE4a59f267346ea31F984", ... token0="0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", # WETH ... token1="0xaf88d065e77c8cC2239327C5EDb3A432268e5831", # USDC ... fee=3000, ... )

### get_max_tick

```
get_max_tick(fee: int) -> int
```

Get the maximum valid tick for a fee tier.

Parameters:

| Name  | Type  | Description | Default    |
| ----- | ----- | ----------- | ---------- |
| `fee` | `int` | Fee tier    | *required* |

Returns:

| Type  | Description        |
| ----- | ------------------ |
| `int` | Maximum valid tick |

Raises:

| Type              | Description                |
| ----------------- | -------------------------- |
| `InvalidFeeError` | If fee is not a valid tier |

### get_min_tick

```
get_min_tick(fee: int) -> int
```

Get the minimum valid tick for a fee tier.

Parameters:

| Name  | Type  | Description | Default    |
| ----- | ----- | ----------- | ---------- |
| `fee` | `int` | Fee tier    | *required* |

Returns:

| Type  | Description        |
| ----- | ------------------ |
| `int` | Minimum valid tick |

Raises:

| Type              | Description                |
| ----------------- | -------------------------- |
| `InvalidFeeError` | If fee is not a valid tier |

### get_nearest_tick

```
get_nearest_tick(tick: int, fee: int) -> int
```

Get the nearest valid tick for a given fee tier.

Parameters:

| Name   | Type  | Description    | Default    |
| ------ | ----- | -------------- | ---------- |
| `tick` | `int` | Raw tick value | *required* |
| `fee`  | `int` | Fee tier       | *required* |

Returns:

| Type  | Description        |
| ----- | ------------------ |
| `int` | Nearest valid tick |

Raises:

| Type              | Description                |
| ----------------- | -------------------------- |
| `InvalidFeeError` | If fee is not a valid tier |

### price_to_sqrt_price_x96

```
price_to_sqrt_price_x96(price: Decimal | float) -> int
```

Convert a decimal price to sqrt price X96 format.

Parameters:

| Name    | Type      | Description | Default          |
| ------- | --------- | ----------- | ---------------- |
| `price` | \`Decimal | float\`     | Price as decimal |

Returns:

| Type  | Description                 |
| ----- | --------------------------- |
| `int` | Sqrt price in Q64.96 format |

### price_to_tick

```
price_to_tick(
    price: Decimal | float,
    decimals0: int = 18,
    decimals1: int = 18,
) -> int
```

Convert a price to the nearest tick.

Parameters:

| Name        | Type      | Description        | Default                            |
| ----------- | --------- | ------------------ | ---------------------------------- |
| `price`     | \`Decimal | float\`            | Price of token0 in terms of token1 |
| `decimals0` | `int`     | Decimals of token0 | `18`                               |
| `decimals1` | `int`     | Decimals of token1 | `18`                               |

Returns:

| Type  | Description                                              |
| ----- | -------------------------------------------------------- |
| `int` | Tick value (may not be on a valid tick spacing boundary) |

### sort_tokens

```
sort_tokens(token0: str, token1: str) -> tuple[str, str]
```

Sort two token addresses in ascending order.

Uniswap V3 pools always order tokens such that token0 < token1.

Parameters:

| Name     | Type  | Description          | Default    |
| -------- | ----- | -------------------- | ---------- |
| `token0` | `str` | First token address  | *required* |
| `token1` | `str` | Second token address | *required* |

Returns:

| Type              | Description                                         |
| ----------------- | --------------------------------------------------- |
| `tuple[str, str]` | Tuple of (token0, token1) sorted in ascending order |

### sqrt_price_x96_to_price

```
sqrt_price_x96_to_price(sqrt_price_x96: int) -> Decimal
```

Convert sqrt price X96 to a decimal price.

Parameters:

| Name             | Type  | Description                 | Default    |
| ---------------- | ----- | --------------------------- | ---------- |
| `sqrt_price_x96` | `int` | Sqrt price in Q64.96 format | *required* |

Returns:

| Type      | Description      |
| --------- | ---------------- |
| `Decimal` | Price as Decimal |

### sqrt_price_x96_to_tick

```
sqrt_price_x96_to_tick(sqrt_price_x96: int) -> int
```

Convert sqrt price in Q64.96 format to tick.

Parameters:

| Name             | Type  | Description                 | Default    |
| ---------------- | ----- | --------------------------- | ---------- |
| `sqrt_price_x96` | `int` | Sqrt price in Q64.96 format | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| `int` | Tick value  |

Raises:

| Type         | Description                  |
| ------------ | ---------------------------- |
| `ValueError` | If sqrt_price_x96 is invalid |

### tick_to_price

```
tick_to_price(
    tick: int, decimals0: int = 18, decimals1: int = 18
) -> Decimal
```

Convert a tick to a human-readable price.

Parameters:

| Name        | Type  | Description        | Default    |
| ----------- | ----- | ------------------ | ---------- |
| `tick`      | `int` | Tick value         | *required* |
| `decimals0` | `int` | Decimals of token0 | `18`       |
| `decimals1` | `int` | Decimals of token1 | `18`       |

Returns:

| Type      | Description                                                |
| --------- | ---------------------------------------------------------- |
| `Decimal` | Price of token0 in terms of token1 (adjusted for decimals) |

### tick_to_sqrt_price_x96

```
tick_to_sqrt_price_x96(tick: int) -> int
```

Convert a tick to sqrt price in Q64.96 format.

Uses the formula: sqrt(1.0001^tick) * 2^96

Parameters:

| Name   | Type  | Description | Default    |
| ------ | ----- | ----------- | ---------- |
| `tick` | `int` | Tick value  | *required* |

Returns:

| Type  | Description                 |
| ----- | --------------------------- |
| `int` | Sqrt price in Q64.96 format |

Raises:

| Type               | Description              |
| ------------------ | ------------------------ |
| `InvalidTickError` | If tick is out of bounds |

# Uniswap V4

Connector for Uniswap V4 with UniversalRouter swaps, PositionManager LP, and hook discovery.

## almanak.framework.connectors.uniswap_v4

Uniswap V4 protocol connector.

Provides swap compilation, receipt parsing, and pool utilities for Uniswap V4's singleton PoolManager architecture.

Key differences from V3:

- Singleton PoolManager contract (all pools in one contract)
- Pool keys include hooks address (currency0, currency1, fee, tickSpacing, hooks)
- Native ETH support (no mandatory WETH wrapping)
- Flash accounting model
- New Swap event signature from PoolManager

Example

from almanak.framework.connectors.uniswap_v4 import UniswapV4Adapter

adapter = UniswapV4Adapter(chain="arbitrum") bundle = adapter.compile_swap_intent(intent, price_oracle)

### UniswapV4Adapter

```
UniswapV4Adapter(
    chain: str | None = None,
    config: UniswapV4Config | None = None,
    token_resolver: TokenResolver | None = None,
)
```

Uniswap V4 swap adapter for intent compilation.

Compiles SwapIntents into ActionBundles containing approve + swap transactions targeting the V4 swap router.

Parameters:

| Name             | Type              | Description | Default                                                   |
| ---------------- | ----------------- | ----------- | --------------------------------------------------------- |
| `chain`          | \`str             | None\`      | Chain name.                                               |
| `config`         | \`UniswapV4Config | None\`      | Optional UniswapV4Config. If not provided, chain is used. |
| `token_resolver` | \`TokenResolver   | None\`      | Optional TokenResolver for symbol -> address resolution.  |

#### get_position_liquidity

```
get_position_liquidity(
    token_id: int, rpc_url: str | None = None
) -> int
```

Query on-chain liquidity for a V4 LP position.

Parameters:

| Name       | Type  | Description                      | Default                    |
| ---------- | ----- | -------------------------------- | -------------------------- |
| `token_id` | `int` | NFT token ID of the LP position. | *required*                 |
| `rpc_url`  | \`str | None\`                           | Optional RPC URL override. |

Returns:

| Type  | Description                                                                        |
| ----- | ---------------------------------------------------------------------------------- |
| `int` | Liquidity amount (uint128). Raises ValueError if position is empty or query fails. |

#### swap_exact_input

```
swap_exact_input(
    token_in: str,
    token_out: str,
    amount_in: Decimal,
    slippage_bps: int | None = None,
    fee_tier: int | None = None,
    price_ratio: Decimal | None = None,
) -> SwapResult
```

Build swap transactions for exact input amount.

Parameters:

| Name           | Type      | Description                           | Default                                                        |
| -------------- | --------- | ------------------------------------- | -------------------------------------------------------------- |
| `token_in`     | `str`     | Input token symbol or address.        | *required*                                                     |
| `token_out`    | `str`     | Output token symbol or address.       | *required*                                                     |
| `amount_in`    | `Decimal` | Input amount in human-readable units. | *required*                                                     |
| `slippage_bps` | \`int     | None\`                                | Slippage tolerance in bps. Default from config.                |
| `fee_tier`     | \`int     | None\`                                | Fee tier. Default from config.                                 |
| `price_ratio`  | \`Decimal | None\`                                | Price ratio (token_out per token_in) for cross-decimal quotes. |

Returns:

| Type         | Description                        |
| ------------ | ---------------------------------- |
| `SwapResult` | SwapResult with transactions list. |

#### compile_swap_intent

```
compile_swap_intent(
    intent: SwapIntent,
    price_oracle: dict[str, Decimal] | None = None,
) -> ActionBundle
```

Compile a SwapIntent to an ActionBundle.

This method integrates with the intent system to convert high-level swap intents into executable transaction bundles.

Parameters:

| Name           | Type                 | Description                | Default                                 |
| -------------- | -------------------- | -------------------------- | --------------------------------------- |
| `intent`       | `SwapIntent`         | The SwapIntent to compile. | *required*                              |
| `price_oracle` | \`dict[str, Decimal] | None\`                     | Optional price map for USD conversions. |

Returns:

| Type           | Description                                         |
| -------------- | --------------------------------------------------- |
| `ActionBundle` | ActionBundle containing transactions for execution. |

#### compile_lp_open_intent

```
compile_lp_open_intent(
    intent: LPOpenIntent,
    price_oracle: dict[str, Decimal] | None = None,
) -> ActionBundle
```

Compile an LPOpenIntent to an ActionBundle for V4 PositionManager.

Builds transactions for: 1-2. ERC-20 approve token0 + token1 to Permit2 3-4. Permit2.approve(PositionManager, token0/token1) 5. PositionManager.modifyLiquidities([MINT_POSITION, SETTLE_PAIR])

Parameters:

| Name           | Type                 | Description                                       | Default                                      |
| -------------- | -------------------- | ------------------------------------------------- | -------------------------------------------- |
| `intent`       | `LPOpenIntent`       | LPOpenIntent with pool, amounts, and price range. | *required*                                   |
| `price_oracle` | \`dict[str, Decimal] | None\`                                            | Optional price map for liquidity estimation. |

Returns:

| Type           | Description                                   |
| -------------- | --------------------------------------------- |
| `ActionBundle` | ActionBundle containing LP mint transactions. |

#### compile_lp_close_intent

```
compile_lp_close_intent(
    intent: LPCloseIntent,
    liquidity: int = 0,
    currency0: str = "",
    currency1: str = "",
) -> ActionBundle
```

Compile an LPCloseIntent to an ActionBundle for V4 PositionManager.

Builds a single transaction: PositionManager.modifyLiquidities([DECREASE_LIQUIDITY, TAKE_PAIR, BURN_POSITION])

Parameters:

| Name        | Type            | Description                                                                                       | Default    |
| ----------- | --------------- | ------------------------------------------------------------------------------------------------- | ---------- |
| `intent`    | `LPCloseIntent` | LPCloseIntent with position_id.                                                                   | *required* |
| `liquidity` | `int`           | Total liquidity to withdraw (must be provided by caller, typically from on-chain position query). | `0`        |
| `currency0` | `str`           | Token0 address (sorted). Required for TAKE_PAIR.                                                  | `''`       |
| `currency1` | `str`           | Token1 address (sorted). Required for TAKE_PAIR.                                                  | `''`       |

Returns:

| Type           | Description                                    |
| -------------- | ---------------------------------------------- |
| `ActionBundle` | ActionBundle containing LP close transactions. |

#### compile_collect_fees_intent

```
compile_collect_fees_intent(
    position_id: int,
    currency0: str,
    currency1: str,
    hook_data: bytes = b"",
) -> ActionBundle
```

Compile a collect-fees operation for a V4 LP position.

Parameters:

| Name          | Type    | Description                          | Default    |
| ------------- | ------- | ------------------------------------ | ---------- |
| `position_id` | `int`   | NFT token ID.                        | *required* |
| `currency0`   | `str`   | Token0 address (sorted).             | *required* |
| `currency1`   | `str`   | Token1 address (sorted).             | *required* |
| `hook_data`   | `bytes` | Optional hook data for hooked pools. | `b''`      |

Returns:

| Type           | Description                                         |
| -------------- | --------------------------------------------------- |
| `ActionBundle` | ActionBundle containing fee collection transaction. |

### UniswapV4Config

```
UniswapV4Config(
    chain: str,
    wallet_address: str = "",
    rpc_url: str | None = None,
    default_fee_tier: int = 3000,
    default_slippage_bps: int = 50,
)
```

Configuration for UniswapV4Adapter.

Attributes:

| Name                   | Type  | Description                                          |
| ---------------------- | ----- | ---------------------------------------------------- |
| `chain`                | `str` | Chain name (e.g. "arbitrum").                        |
| `wallet_address`       | `str` | Wallet address for building transactions.            |
| `rpc_url`              | \`str | None\`                                               |
| `default_fee_tier`     | `int` | Default fee tier for swaps. Default 3000 (0.3%).     |
| `default_slippage_bps` | `int` | Default slippage in basis points. Default 50 (0.5%). |

### HookDataEncoder

Bases: `ABC`

Base class for encoding protocol-specific hookData.

Strategy authors subclass this to provide typed encoding for known hook contracts. The encoder validates inputs and produces ABI-encoded bytes that the hook contract expects.

Example

class DynamicFeeEncoder(HookDataEncoder): def encode(self, \*\*kwargs) -> bytes: fee_override = kwargs.get("fee_override", 3000) return fee_override.to_bytes(32, "big")

```
@property
def hook_name(self) -> str:
    return "DynamicFeeHook"
```

encoder = DynamicFeeEncoder() hook_data = encoder.encode(fee_override=500)

#### hook_name

```
hook_name: str
```

Human-readable name of the hook this encoder targets.

#### encode

```
encode(**kwargs) -> bytes
```

Encode hookData for this specific hook contract.

Parameters:

| Name       | Type | Description               | Default |
| ---------- | ---- | ------------------------- | ------- |
| `**kwargs` |      | Hook-specific parameters. | `{}`    |

Returns:

| Type    | Description                               |
| ------- | ----------------------------------------- |
| `bytes` | ABI-encoded bytes for the hookData field. |

#### validate_flags

```
validate_flags(flags: HookFlags) -> bool
```

Validate that hook flags are compatible with this encoder.

Override this method to enforce that the hook address has the expected capability bits set.

Parameters:

| Name    | Type        | Description                              | Default    |
| ------- | ----------- | ---------------------------------------- | ---------- |
| `flags` | `HookFlags` | Decoded HookFlags from the hook address. | *required* |

Returns:

| Type   | Description                                    |
| ------ | ---------------------------------------------- |
| `bool` | True if flags are compatible, False otherwise. |

### HookFlags

```
HookFlags(
    before_initialize: bool = False,
    after_initialize: bool = False,
    before_add_liquidity: bool = False,
    after_add_liquidity: bool = False,
    before_remove_liquidity: bool = False,
    after_remove_liquidity: bool = False,
    before_swap: bool = False,
    after_swap: bool = False,
    before_donate: bool = False,
    after_donate: bool = False,
    before_swap_returns_delta: bool = False,
    after_swap_returns_delta: bool = False,
    after_add_liquidity_returns_delta: bool = False,
    after_remove_liquidity_returns_delta: bool = False,
)
```

Decoded 14-bit hook capability flags from a V4 hook address.

In Uniswap V4, hook contract addresses encode their capabilities in the last 14 bits of the address. This is enforced by CREATE2 address mining -- the PoolManager validates that a hook's address matches its declared capabilities.

Usage

flags = HookFlags.from_address("0x...hook_address...") if flags.before_swap: print("Hook modifies swap behavior") if flags.has_any_swap_hooks: print("Hook participates in swaps")

#### has_any_swap_hooks

```
has_any_swap_hooks: bool
```

True if the hook participates in swap operations.

#### has_any_liquidity_hooks

```
has_any_liquidity_hooks: bool
```

True if the hook participates in liquidity operations.

#### has_any_delta_flags

```
has_any_delta_flags: bool
```

True if the hook returns balance deltas (modifies amounts).

#### is_empty

```
is_empty: bool
```

True if no hook capabilities are set (no-hook address).

#### active_flags

```
active_flags: list[str]
```

Return list of active hook flag names.

#### from_address

```
from_address(hook_address: str) -> HookFlags
```

Decode hook capabilities from a hook contract address.

Parameters:

| Name           | Type  | Description                                        | Default    |
| -------------- | ----- | -------------------------------------------------- | ---------- |
| `hook_address` | `str` | Hook contract address (hex string with 0x prefix). | *required* |

Returns:

| Type        | Description                             |
| ----------- | --------------------------------------- |
| `HookFlags` | HookFlags with decoded capability bits. |

Raises:

| Type         | Description                               |
| ------------ | ----------------------------------------- |
| `ValueError` | If the address is not a valid hex string. |

#### from_bitmask

```
from_bitmask(bitmask: int) -> HookFlags
```

Create HookFlags from a raw 14-bit bitmask.

Parameters:

| Name      | Type  | Description                                   | Default    |
| --------- | ----- | --------------------------------------------- | ---------- |
| `bitmask` | `int` | Integer with hook flags in the lower 14 bits. | *required* |

Returns:

| Type        | Description                             |
| ----------- | --------------------------------------- |
| `HookFlags` | HookFlags with decoded capability bits. |

#### to_bitmask

```
to_bitmask() -> int
```

Convert flags back to a 14-bit integer bitmask.

#### requires_hook_data

```
requires_hook_data() -> bool
```

True if this hook likely requires non-empty hookData.

Hooks with before_swap, after_swap, or delta-returning flags typically need hookData to function correctly. Empty hookData may cause reverts.

### PoolDiscoveryResult

```
PoolDiscoveryResult(
    pool_key: PoolKey,
    pool_id: str,
    hook_address: str,
    hook_flags: HookFlags,
    state: PoolState | None = None,
)
```

Result of pool discovery for a token pair.

Fields

pool_key: The resolved PoolKey. pool_id: Keccak256 hash of the ABI-encoded PoolKey. hook_address: Hook contract address (zero address if no hooks). hook_flags: Decoded hook capabilities. state: Pool state from StateView (None if not queried on-chain).

### PoolState

```
PoolState(
    sqrt_price_x96: int = 0,
    tick: int = 0,
    protocol_fee: int = 0,
    lp_fee: int = 0,
    exists: bool = False,
)
```

State of a V4 pool from StateView.getSlot0().

Fields

sqrt_price_x96: Current sqrt(price) as Q64.96 fixed-point. tick: Current tick index. protocol_fee: Protocol fee setting. lp_fee: LP fee in hundredths of a bip. exists: Whether the pool has been initialized.

### UniswapV4ReceiptParser

```
UniswapV4ReceiptParser(
    chain: str = "ethereum",
    pool_manager_address: str | None = None,
    position_manager_address: str | None = None,
    token_resolver: Any | None = None,
)
```

Parse Uniswap V4 transaction receipts.

Extracts swap amounts, effective prices, and balance deltas from V4 PoolManager events.

Parameters:

| Name                   | Type  | Description             | Default                               |
| ---------------------- | ----- | ----------------------- | ------------------------------------- |
| `chain`                | `str` | Chain name for context. | `'ethereum'`                          |
| `pool_manager_address` | \`str | None\`                  | PoolManager address to filter events. |

#### parse_receipt

```
parse_receipt(
    receipt: dict[str, Any],
    quoted_amount_out: int | None = None,
) -> ParseResult
```

Parse a transaction receipt for V4 events.

Parameters:

| Name                | Type             | Description                                 | Default                                   |
| ------------------- | ---------------- | ------------------------------------------- | ----------------------------------------- |
| `receipt`           | `dict[str, Any]` | Transaction receipt dict with 'logs' field. | *required*                                |
| `quoted_amount_out` | \`int            | None\`                                      | Expected output for slippage calculation. |

Returns:

| Type          | Description                                       |
| ------------- | ------------------------------------------------- |
| `ParseResult` | ParseResult with decoded events and swap summary. |

#### extract_swap_amounts

```
extract_swap_amounts(
    receipt: dict[str, Any],
) -> SwapAmounts | None
```

Extract swap amounts for ResultEnricher integration.

Parameters:

| Name      | Type             | Description               | Default    |
| --------- | ---------------- | ------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict. | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`SwapAmounts | None\`      |

#### extract_position_id

```
extract_position_id(receipt: dict[str, Any]) -> int | None
```

Extract LP position NFT tokenId from ERC-721 Transfer event.

Looks for a Transfer event emitted by the PositionManager contract where from_address is the zero address (indicating a mint).

Falls back to ERC-721 mint Transfers from other known V4 PositionManager addresses if no exact chain match is found (handles address mismatches or proxy patterns). Rejects mints from unknown contracts to fail closed.

Called by ResultEnricher for LP_OPEN intents.

Parameters:

| Name      | Type             | Description               | Default    |
| --------- | ---------------- | ------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict. | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_liquidity

```
extract_liquidity(receipt: dict[str, Any]) -> int | None
```

Extract liquidity delta from ModifyLiquidity event.

Called by ResultEnricher for LP_OPEN intents.

Parameters:

| Name      | Type             | Description               | Default    |
| --------- | ---------------- | ------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict. | *required* |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### extract_lp_close_data

```
extract_lp_close_data(
    receipt: dict[str, Any],
) -> LPCloseData | None
```

Extract LP close data from ModifyLiquidity and Transfer events.

Called by ResultEnricher for LP_CLOSE intents.

Parameters:

| Name      | Type             | Description               | Default    |
| --------- | ---------------- | ------------------------- | ---------- |
| `receipt` | `dict[str, Any]` | Transaction receipt dict. | *required* |

Returns:

| Type          | Description |
| ------------- | ----------- |
| \`LPCloseData | None\`      |

### UniswapV4SDK

```
UniswapV4SDK(chain: str, rpc_url: str | None = None)
```

Uniswap V4 SDK for pool operations and swap encoding.

Routes swaps through the canonical UniversalRouter with Permit2 flow.

Parameters:

| Name      | Type  | Description                               | Default                                |
| --------- | ----- | ----------------------------------------- | -------------------------------------- |
| `chain`   | `str` | Chain name (e.g. "arbitrum", "ethereum"). | *required*                             |
| `rpc_url` | \`str | None\`                                    | Optional RPC URL for on-chain queries. |

#### get_position_liquidity

```
get_position_liquidity(
    token_id: int, rpc_url: str | None = None
) -> int
```

Query on-chain liquidity for a V4 LP position via PositionManager.getPositionLiquidity(uint256).

Parameters:

| Name       | Type  | Description                      | Default                                     |
| ---------- | ----- | -------------------------------- | ------------------------------------------- |
| `token_id` | `int` | NFT token ID of the LP position. | *required*                                  |
| `rpc_url`  | \`str | None\`                           | RPC URL to use. Falls back to self.rpc_url. |

Returns:

| Type  | Description                                  |
| ----- | -------------------------------------------- |
| `int` | Liquidity amount (uint128) for the position. |

Raises:

| Type         | Description                                   |
| ------------ | --------------------------------------------- |
| `ValueError` | If no RPC URL is available or the call fails. |

#### get_pool_sqrt_price

```
get_pool_sqrt_price(
    pool_key: PoolKey, rpc_url: str | None = None
) -> int | None
```

Query on-chain sqrtPriceX96 for a V4 pool via StateView.getSlot0().

Parameters:

| Name       | Type      | Description                      | Default                                     |
| ---------- | --------- | -------------------------------- | ------------------------------------------- |
| `pool_key` | `PoolKey` | V4 PoolKey identifying the pool. | *required*                                  |
| `rpc_url`  | \`str     | None\`                           | RPC URL to use. Falls back to self.rpc_url. |

Returns:

| Type  | Description |
| ----- | ----------- |
| \`int | None\`      |

#### compute_pool_key

```
compute_pool_key(
    token0: str,
    token1: str,
    fee: int = 3000,
    tick_spacing: int | None = None,
    hooks: str = NATIVE_CURRENCY,
) -> PoolKey
```

Compute a V4 pool key for a token pair.

Parameters:

| Name           | Type  | Description                                               | Default                                                 |
| -------------- | ----- | --------------------------------------------------------- | ------------------------------------------------------- |
| `token0`       | `str` | First token address.                                      | *required*                                              |
| `token1`       | `str` | Second token address.                                     | *required*                                              |
| `fee`          | `int` | Fee tier in hundredths of a bip (e.g., 3000 = 0.3%).      | `3000`                                                  |
| `tick_spacing` | \`int | None\`                                                    | Custom tick spacing. Defaults to standard for fee tier. |
| `hooks`        | `str` | Hooks contract address. Default: no hooks (zero address). | `NATIVE_CURRENCY`                                       |

Returns:

| Type      | Description                             |
| --------- | --------------------------------------- |
| `PoolKey` | PoolKey with sorted currency addresses. |

#### get_quote_local

```
get_quote_local(
    token_in: str,
    token_out: str,
    amount_in: int,
    fee_tier: int = 3000,
    token_in_decimals: int = 18,
    token_out_decimals: int = 18,
    price_ratio: Decimal | None = None,
) -> SwapQuote
```

Compute an offline swap quote estimate based on fee tier.

This is a best-effort estimate without on-chain data. For accurate quotes, use the V4 Quoter contract via RPC.

Parameters:

| Name                 | Type      | Description                     | Default                                    |
| -------------------- | --------- | ------------------------------- | ------------------------------------------ |
| `token_in`           | `str`     | Input token address.            | *required*                                 |
| `token_out`          | `str`     | Output token address.           | *required*                                 |
| `amount_in`          | `int`     | Input amount in smallest units. | *required*                                 |
| `fee_tier`           | `int`     | Fee tier (e.g. 3000 = 0.3%).    | `3000`                                     |
| `token_in_decimals`  | `int`     | Decimals for input token.       | `18`                                       |
| `token_out_decimals` | `int`     | Decimals for output token.      | `18`                                       |
| `price_ratio`        | \`Decimal | None\`                          | Optional price ratio (token_in/token_out). |

Returns:

| Type        | Description                      |
| ----------- | -------------------------------- |
| `SwapQuote` | SwapQuote with estimated output. |

#### build_approve_tx

```
build_approve_tx(
    token_address: str, spender: str, amount: int
) -> SwapTransaction
```

Build an ERC-20 approve transaction.

Parameters:

| Name            | Type  | Description                               | Default    |
| --------------- | ----- | ----------------------------------------- | ---------- |
| `token_address` | `str` | Token contract address.                   | *required* |
| `spender`       | `str` | Address to approve (Permit2 for V4 flow). | *required* |
| `amount`        | `int` | Amount to approve.                        | *required* |

Returns:

| Type              | Description                                    |
| ----------------- | ---------------------------------------------- |
| `SwapTransaction` | SwapTransaction with encoded approve calldata. |

#### build_permit2_approve_tx

```
build_permit2_approve_tx(
    token_address: str,
    spender: str,
    amount: int,
    expiration: int = 0,
) -> SwapTransaction
```

Build a Permit2.approve transaction to grant the UniversalRouter allowance.

Parameters:

| Name            | Type  | Description                                          | Default    |
| --------------- | ----- | ---------------------------------------------------- | ---------- |
| `token_address` | `str` | Token address to approve.                            | *required* |
| `spender`       | `str` | Address to grant allowance to (UniversalRouter).     | *required* |
| `amount`        | `int` | Amount to approve (uint160 max = 2^160-1).           | *required* |
| `expiration`    | `int` | Expiration timestamp (0 = default 30 days from now). | `0`        |

Returns:

| Type              | Description                                     |
| ----------------- | ----------------------------------------------- |
| `SwapTransaction` | SwapTransaction targeting the Permit2 contract. |

#### build_swap_tx

```
build_swap_tx(
    quote: SwapQuote,
    recipient: str,
    slippage_bps: int = 50,
    deadline: int = 0,
) -> SwapTransaction
```

Build a V4 swap transaction via the UniversalRouter.

Uses the two-layer V4_SWAP encoding verified against real Ethereum mainnet txns

Outer: UniversalRouter.execute([V4_SWAP], [v4_input], deadline) Inner: v4_input = abi.encode(bytes actions, bytes[] params) actions = [SWAP_EXACT_IN_SINGLE, SETTLE, TAKE]

WETH routing: V4 pools primarily use native ETH (address(0)), not WETH. When token_in or token_out is WETH, the swap routes through the native ETH pool and adds UNWRAP_WETH or WRAP_ETH commands at the UniversalRouter level.

Parameters:

| Name           | Type        | Description                                     | Default    |
| -------------- | ----------- | ----------------------------------------------- | ---------- |
| `quote`        | `SwapQuote` | Swap quote with amounts.                        | *required* |
| `recipient`    | `str`       | Address to receive output tokens.               | *required* |
| `slippage_bps` | `int`       | Slippage tolerance in basis points.             | `50`       |
| `deadline`     | `int`       | Transaction deadline (0 = 30 minutes from now). | `0`        |

Returns:

| Type              | Description                            |
| ----------------- | -------------------------------------- |
| `SwapTransaction` | SwapTransaction with encoded calldata. |

#### build_mint_position_tx

```
build_mint_position_tx(
    params: LPMintParams, deadline: int = 0
) -> SwapTransaction
```

Build a PositionManager.modifyLiquidities TX to mint a new LP position.

Encodes actions [MINT_POSITION, SETTLE_PAIR] to:

1. Create the position NFT with the specified liquidity
1. Settle (pay) both currencies via Permit2

Parameters:

| Name       | Type           | Description                                             | Default    |
| ---------- | -------------- | ------------------------------------------------------- | ---------- |
| `params`   | `LPMintParams` | LPMintParams with pool key, tick range, liquidity, etc. | *required* |
| `deadline` | `int`          | TX deadline (0 = 30 minutes from now).                  | `0`        |

Returns:

| Type              | Description                                |
| ----------------- | ------------------------------------------ |
| `SwapTransaction` | SwapTransaction targeting PositionManager. |

#### build_decrease_liquidity_tx

```
build_decrease_liquidity_tx(
    params: LPDecreaseParams,
    currency0: str,
    currency1: str,
    recipient: str,
    deadline: int = 0,
    burn: bool = True,
) -> SwapTransaction
```

Build a PositionManager.modifyLiquidities TX to decrease/close an LP position.

Encodes actions [DECREASE_LIQUIDITY, TAKE_PAIR] and optionally [BURN_POSITION].

Parameters:

| Name        | Type               | Description                                          | Default    |
| ----------- | ------------------ | ---------------------------------------------------- | ---------- |
| `params`    | `LPDecreaseParams` | LPDecreaseParams with token ID, liquidity, minimums. | *required* |
| `currency0` | `str`              | Token0 address (sorted).                             | *required* |
| `currency1` | `str`              | Token1 address (sorted).                             | *required* |
| `recipient` | `str`              | Address to receive withdrawn tokens.                 | *required* |
| `deadline`  | `int`              | TX deadline (0 = 30 minutes from now).               | `0`        |
| `burn`      | `bool`             | Whether to burn the NFT after withdrawal.            | `True`     |

Returns:

| Type              | Description                                |
| ----------------- | ------------------------------------------ |
| `SwapTransaction` | SwapTransaction targeting PositionManager. |

#### build_collect_fees_tx

```
build_collect_fees_tx(
    token_id: int,
    currency0: str,
    currency1: str,
    recipient: str,
    hook_data: bytes = b"",
    deadline: int = 0,
) -> SwapTransaction
```

Build a PositionManager.modifyLiquidities TX to collect fees only.

Decreases liquidity by 0 (triggers fee accrual update) then takes pair.

Parameters:

| Name        | Type    | Description                            | Default    |
| ----------- | ------- | -------------------------------------- | ---------- |
| `token_id`  | `int`   | Position NFT token ID.                 | *required* |
| `currency0` | `str`   | Token0 address (sorted).               | *required* |
| `currency1` | `str`   | Token1 address (sorted).               | *required* |
| `recipient` | `str`   | Address to receive fees.               | *required* |
| `hook_data` | `bytes` | Optional hook data for hooked pools.   | `b''`      |
| `deadline`  | `int`   | TX deadline (0 = 30 minutes from now). | `0`        |

Returns:

| Type              | Description                                |
| ----------------- | ------------------------------------------ |
| `SwapTransaction` | SwapTransaction targeting PositionManager. |

#### compute_liquidity_from_amounts

```
compute_liquidity_from_amounts(
    sqrt_price_x96: int,
    tick_lower: int,
    tick_upper: int,
    amount0: int,
    amount1: int,
) -> int
```

Compute liquidity from token amounts and price range.

Uses the same math as Uniswap V3/V4:

- If current price is below range: liquidity from amount0 only
- If current price is above range: liquidity from amount1 only
- If current price is in range: min(liquidity from amount0, liquidity from amount1)

Parameters:

| Name             | Type  | Description                                   | Default    |
| ---------------- | ----- | --------------------------------------------- | ---------- |
| `sqrt_price_x96` | `int` | Current pool sqrtPriceX96 (or estimate).      | *required* |
| `tick_lower`     | `int` | Lower tick boundary.                          | *required* |
| `tick_upper`     | `int` | Upper tick boundary.                          | *required* |
| `amount0`        | `int` | Desired amount of token0 (in smallest units). | *required* |
| `amount1`        | `int` | Desired amount of token1 (in smallest units). | *required* |

Returns:

| Type  | Description                |
| ----- | -------------------------- |
| `int` | Estimated liquidity value. |

#### estimate_sqrt_price_x96

```
estimate_sqrt_price_x96(
    price: Decimal, decimals0: int = 18, decimals1: int = 18
) -> int
```

Estimate sqrtPriceX96 from a human-readable price (token1 per token0).

Parameters:

| Name        | Type      | Description                         | Default    |
| ----------- | --------- | ----------------------------------- | ---------- |
| `price`     | `Decimal` | Price of token0 in terms of token1. | *required* |
| `decimals0` | `int`     | Decimals of token0.                 | `18`       |
| `decimals1` | `int`     | Decimals of token1.                 | `18`       |

Returns:

| Type  | Description                   |
| ----- | ----------------------------- |
| `int` | Estimated sqrtPriceX96 value. |

#### tick_to_price

```
tick_to_price(
    tick: int, decimals0: int = 18, decimals1: int = 18
) -> Decimal
```

Convert tick to human-readable price.

Uses Decimal arithmetic to avoid float overflow at extreme ticks.

#### price_to_tick

```
price_to_tick(
    price: Decimal, decimals0: int = 18, decimals1: int = 18
) -> int
```

Convert human-readable price to tick.

Uses math.log for the inverse computation. Safe for typical price ranges.

### discover_pool

```
discover_pool(
    token0: str,
    token1: str,
    fee: int = 3000,
    tick_spacing: int | None = None,
    hooks: str = NO_HOOKS,
) -> PoolDiscoveryResult
```

Discover a V4 pool and decode its hook capabilities.

Two-step hook discovery:

1. Resolve PoolKey for the token pair/fee/tickSpacing to get the hook address
1. Decode the 14-bit capability bitmask from the hook address

Parameters:

| Name           | Type  | Description                                        | Default                                                  |
| -------------- | ----- | -------------------------------------------------- | -------------------------------------------------------- |
| `token0`       | `str` | First token address.                               | *required*                                               |
| `token1`       | `str` | Second token address.                              | *required*                                               |
| `fee`          | `int` | Fee tier in hundredths of a bip.                   | `3000`                                                   |
| `tick_spacing` | \`int | None\`                                             | Custom tick spacing (defaults to standard for fee tier). |
| `hooks`        | `str` | Hook contract address (zero address for no hooks). | `NO_HOOKS`                                               |

Returns:

| Type                  | Description                                                   |
| --------------------- | ------------------------------------------------------------- |
| `PoolDiscoveryResult` | PoolDiscoveryResult with pool key, ID, and hook capabilities. |
