Saltar a contenido

Intent Compiler

The compiler transforms high-level intents into executable transaction bundles.

IntentCompiler

almanak.framework.intents.compiler.IntentCompiler

IntentCompiler(
    chain: str = DEFAULT_CHAIN,
    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 = LP_SLIPPAGE_DEFAULT,
    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.)

DEFAULT_CHAIN
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.

None
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.

None
rpc_timeout float

HTTP timeout for direct RPC calls in seconds.

10.0
default_lp_slippage Decimal

Default tolerance for BALANCED LP operations (:data:LP_SLIPPAGE_DEFAULT, 0.01 = 1%), applied when the intent declares neither max_slippage nor protocol_params["lp_slippage"]. It is a price tolerance, not a per-leg amount haircut: on the V3-family / Slipstream mint path cl_math.compute_lp_slippage_mins maps it through lp_mint_mins_for_price_band into the amount0Min / amount1Min pair, so "1%" means "refuse if spot moved more than 1% from what this bundle compiled against" rather than "accept 99% of desired on each leg".

ALM-3186 / VIB-6225 changed this from 0.99 (a placeholder that floored each leg at 1% of desired — effectively unfloored) and removed the provenance gate that kept the default off the price-band instrument. Read the LP SLIPPAGE DOCTRINE comment above this class before changing it: the split-not-value argument holds only while the price is honest, and it does NOT extend to swaps or to swap-embedding LP paths (imbalanced/single-sided/zap deposits, which set their own swap-grade floor). Set LPOpenIntent.max_slippage per intent rather than moving this global — 0.005 suits stables, 0.02 suits volatile pairs, and a tolerance wider than the position's range half-width will zero a leg's minimum (warned) or fall back to the flat haircut and revert.

LP_SLIPPAGE_DEFAULT
config IntentCompilerConfig | None

Optional configuration. If not provided, defaults to IntentCompilerConfig() which requires price_oracle.

None
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.

None
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.

None

Raises:

Type Description
ValueError

If no price_oracle is provided and allow_placeholder_prices is False.

update_prices

update_prices(prices: dict[str, Decimal]) -> None

Update the price oracle with real prices, clearing placeholder state.

VIB-3136: Copies the incoming dict so subsequent alias expansion does not mutate the caller's dict.

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).

VIB-3136: Copies the incoming dict so subsequent alias expansion does not mutate the caller's dict.

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

assert_prices_available

assert_prices_available(tokens: list[str | None]) -> None

Fail closed if any token lacks a real USD price (VIB-2928 HARD STOP).

Raises ValueError listing every token that cannot be resolved to a present, non-zero USD price. The price oracle is keyed by symbol, so a token that does not price directly is treated as a possible token address and resolved to its symbol before a final retry — this covers both EVM 0x addresses and non-EVM (e.g. Solana base58) mints, so a priceable token identified by address is never falsely rejected. Known stablecoins and native/wrapped aliases resolve legitimately through _require_token_price and therefore never trip the gate — a false positive here would strand funds by blocking a safe unwind, so the gate only fires on a genuinely unpriceable token.

A compiler in placeholder mode (_using_placeholders) cannot price anything for real — every requested token is reported missing, because _require_token_price would otherwise return a fake $1.

The teardown lane uses this to refuse compiling a price-dependent leg (e.g. a swap) on the $1 value that swap adapters silently substitute when a symbol is absent from the oracle.

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 dataclass

IntentCompilerConfig(
    allow_placeholder_prices: bool = False,
    placeholder_price_use: Any = None,
    polymarket_config: Any = None,
    swap_pool_selection_mode: Literal[
        "auto", "fixed"
    ] = "auto",
    fixed_swap_fee_tier: int | None = None,
    max_price_impact_pct: Decimal = Decimal("0.10"),
    permission_discovery: bool = False,
    offline_discovery: bool = False,
    gateway_internal_preflight: bool = False,
    managed_fork: bool | None = None,
)

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 Any

Optional PolymarketConfig for prediction market intents. Required when compiling PredictionBuyIntent, PredictionSellIntent, or PredictionRedeemIntent on Polygon. If not provided when on Polygon, a warning is logged and prediction intents will fail to compile.

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

Optional fixed fee tier used when swap_pool_selection_mode="fixed". Must be valid for the selected protocol.

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.10 (10%). Configurable at compiler construction; override per-swap via SwapIntent.max_price_impact (e.g. thin venues / Pendle YT).

permission_discovery bool

If True, the compiler is being used for offline permission discovery. Enables fallbacks for RPC-dependent operations: - Uses synthetic LP balances when on-chain balance is 0 or unavailable This ensures LP_CLOSE compilation produces full transaction sets (approve + removeLiquidity) so the permission generator can extract the required target addresses and function selectors.

offline_discovery bool

If True, _get_chain_rpc_url will NOT resolve an implicit transport (a managed Anvil fork, then a free public RPC) when no rpc_url was explicitly configured. An explicit rpc_url is still honoured.

Set from PermissionHints.offline_discovery, which a connector opts into once its compiler can produce complete calldata with no network reads. It exists because a manifest built from implicit live reads is a function of RPC weather, not of the registry: curve LP discovery on arbitrum was issuing 43 eth_calls to a public RPC and producing 7/3/7 targets across three consecutive runs (VIB-6046 D5).

Opt-IN rather than default-on: several connectors (gmx_v2, pendle, traderjoe_v2, uniswap_v4 hooks) currently depend on that implicit fallback and discover nothing without it. Flipping the default would turn their flakiness into a hard failure. They have the same nondeterminism, tracked separately — see the module note in almanak/connectors/curve/permission_hints.py.

gateway_internal_preflight bool

Set True ONLY when the compiler is constructed INSIDE the gateway process (see almanak/gateway/services/execution_service.py). Compile-time safety pre-flights that read protocol risk parameters — Aave's frozen-reserve, borrowable and zero-LTV collateral checks — reach for the compiler's gateway_client. A gateway-side compiler has none (it IS the gateway), so those pre-flights silently failed open on the one path the production runner actually uses: the runner compiles via execution.CompileIntent, not in-process (almanak/framework/runner/_inner_runner_helpers.py). Measured consequence on Aave V3 Mantle after governance zeroed ltv — the gateway emitted approve + supply + setUserUseReserveAsCollateral and the toggle leg reverted 0x21e5c4ae UserHasAssetWithZeroLtv() on-chain (VIB-6111).

When True, those pre-flights may issue their reads through the framework eth_call service instead. This is NOT a strategy- container egress bypass: it is only ever set inside almanak/gateway/, which IS the egress layer, and it stays False for every strategy-side and offline compile so those keep failing open exactly as before.

managed_fork bool | None

Tri-state declaration of "this compile targets a managed Anvil fork" (ALM-3184). Swap compilers relax the oracle price-impact guard — the only independent cross-check that an on-chain quote has not been manipulated or drained — when this resolves True, because fork block state and live oracle prices are not time-aligned.

Declaration only — there is no runtime detection. The production gateway compile path declares it from GatewaySettings.network (Network.ANVIL); offline permission discovery declares False; Anvil test harnesses declare True. None means nobody declared, which resolves to production (almanak.framework.execution.fork_signal).

It replaces the previous is_local_rpc(rpc_url) test, which returned True for any host on port 8545-8550 — so a production RPC proxy on :8545 compiled mainnet swaps with the guard off. Absent/unknown now resolves to production, not to fork.

__post_init__

__post_init__() -> None

Validate swap pool selection settings.

CompilationResult

almanak.framework.intents.compiler.CompilationResult dataclass

CompilationResult(
    status: CompilationStatus,
    action_bundle: ActionBundle | None = None,
    transactions: list[TransactionData] = list(),
    total_gas_estimate: int = 0,
    error: str | None = None,
    is_transient: bool = False,
    is_safety_refusal: bool = False,
    retry_after_seconds: float | 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

The compiled ActionBundle (if successful)

transactions list[TransactionData]

List of transaction data

total_gas_estimate int

Sum of all gas estimates

error str | None

Error message (if failed)

is_transient bool

Whether the failure is retryable orchestration-level I/O

is_safety_refusal bool

Whether a FAILED status is a pre-execution SAFETY-GUARD refusal rather than an execution/compile fault (VIB-5746). Set by compile-time guards that refuse to build a transaction because acting would be unsafe — e.g. price impact above the configured max, or the on-chain quoter returned no amount so pool liquidity could not be verified. When True, ZERO transactions were built and the on-chain position is untouched: the guard did its job. The runner maps this to :class:FailureKind.GUARD_REFUSED so it does NOT count toward the circuit breaker's consecutive-failure trip thresholds (a correct refusal is a safety success, not a fault). Only meaningful when status is CompilationStatus.FAILED.

retry_after_seconds float | None

Optional retry delay hinted by the failing backend

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 dataclass

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 dataclass

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 dataclass

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