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
staticmethod
¶
swap(
from_token: str,
to_token: str,
amount_usd: Decimal | None = None,
amount: ChainedAmount | None = None,
max_slippage: Decimal = Decimal("0.005"),
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 |
None
|
amount
|
ChainedAmount | None
|
Amount to swap in token terms, or "all" to use previous step output |
None
|
max_slippage
|
Decimal
|
Maximum acceptable slippage (default 0.5%) |
Decimal('0.005')
|
protocol
|
str | None
|
Preferred protocol for the swap |
None
|
chain
|
str | None
|
Source chain for execution (defaults to strategy's primary chain) |
None
|
destination_chain
|
str | None
|
Destination chain for cross-chain swaps (None for same-chain) |
None
|
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
staticmethod
¶
lp_open(
pool: str,
amount0: Decimal,
amount1: Decimal,
range_lower: Decimal,
range_upper: Decimal,
protocol: str = "uniswap_v3",
chain: str | 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) |
None
|
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
staticmethod
¶
lp_close(
position_id: str,
pool: str | None = None,
collect_fees: bool = True,
protocol: str = "uniswap_v3",
chain: str | 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) |
None
|
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) |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
LPCloseIntent |
LPCloseIntent
|
The created LP close intent |
borrow
staticmethod
¶
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' | 'stable'). Only applies to protocols that support rate mode selection. For Aave V3, defaults to 'variable' if not specified. |
None
|
market_id
|
str | None
|
Market identifier for isolated lending protocols (e.g., Morpho Blue). Required for morpho/morpho_blue, ignored for aave_v3. |
None
|
chain
|
str | None
|
Target chain for execution (defaults to strategy's primary chain) |
None
|
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
staticmethod
¶
repay(
protocol: str,
token: str,
amount: ChainedAmount,
repay_full: bool = False,
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
|
market_id
|
str | None
|
Market identifier for isolated lending protocols (e.g., Morpho Blue). Required for morpho/morpho_blue, ignored for aave_v3. |
None
|
chain
|
str | None
|
Target chain for execution (defaults to strategy's primary chain) |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
RepayIntent |
RepayIntent
|
The created repay intent |
Example
Repay 500 USDC on Aave¶
intent = Intent.repay( protocol="aave_v3", token="USDC", amount=Decimal("500"), )
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
staticmethod
¶
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. |
None
|
chain
|
str | None
|
Target chain for execution (defaults to strategy's primary chain) |
None
|
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
staticmethod
¶
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. |
None
|
chain
|
str | None
|
Target chain for execution (defaults to strategy's primary chain) |
None
|
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
staticmethod
¶
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) |
None
|
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
staticmethod
¶
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) |
None
|
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) |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
PerpCloseIntent |
PerpCloseIntent
|
The created perp close intent |
bridge
staticmethod
¶
bridge(
token: str,
amount: Decimal | Literal["all"],
from_chain: str,
to_chain: str,
max_slippage: Decimal = Decimal("0.005"),
preferred_bridge: 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 |
required |
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") |
None
|
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
staticmethod
¶
flash_loan(
provider: Literal["aave", "balancer", "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', '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) |
None
|
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
staticmethod
¶
Create a hold intent (no action).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
reason
|
str | None
|
Optional reason for holding (for logging/debugging) |
None
|
chain
|
str | None
|
Target chain for execution (defaults to strategy's primary chain) |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
HoldIntent |
HoldIntent
|
The created hold intent |
stake
staticmethod
¶
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) |
None
|
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
staticmethod
¶
unstake(
protocol: str,
token_in: str,
amount: ChainedAmount,
chain: str | 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) |
None
|
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", )
ensure_balance
staticmethod
¶
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:
- If target chain has sufficient balance -> HoldIntent (no action)
- If another chain has sufficient balance -> BridgeIntent (transfer)
- 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 |
None
|
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
staticmethod
¶
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) |
None
|
shares
|
Decimal | None
|
Number of shares to buy (mutually exclusive with amount_usd) |
None
|
max_price
|
Decimal | None
|
Maximum price per share (0.01-0.99) for limit orders |
None
|
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) |
None
|
protocol
|
str
|
Protocol to use (defaults to "polymarket") |
'polymarket'
|
chain
|
str | None
|
Target chain (defaults to "polygon" for Polymarket) |
None
|
exit_conditions
|
PredictionExitConditions | None
|
Optional exit conditions for automatic position monitoring (stop-loss, take-profit, trailing stop, pre-resolution exit) |
None
|
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
staticmethod
¶
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 |
required |
min_price
|
Decimal | None
|
Minimum price per share (0.01-0.99) for limit orders |
None
|
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) |
None
|
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
staticmethod
¶
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) |
None
|
shares
|
Decimal | Literal['all']
|
Number of shares to redeem, or "all" (default) |
'all'
|
protocol
|
str
|
Protocol to use (defaults to "polymarket") |
'polymarket'
|
chain
|
str | None
|
Target chain (defaults to "polygon" for Polymarket) |
None
|
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"), )
sequence
staticmethod
¶
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 |
None
|
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
staticmethod
¶
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
staticmethod
¶
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
staticmethod
¶
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
staticmethod
¶
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. |
None
|
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
staticmethod
¶
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
|
Optional[str]: The chain name if specified, None otherwise |
is_sequence
staticmethod
¶
Check if an item is an IntentSequence.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
item
|
AnyIntent | IntentSequence
|
Intent or IntentSequence to check |
required |
Returns:
| Name | Type | Description |
|---|---|---|
bool |
bool
|
True if item is an IntentSequence |
normalize_decide_result
staticmethod
¶
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 of intents and/or sequences to execute. |
list[AnyIntent | IntentSequence]
|
Items in the list can execute in parallel. |
list[AnyIntent | IntentSequence]
|
Intents within a sequence execute sequentially. |
count_intents
staticmethod
¶
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
staticmethod
¶
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
|
Serialized result, or None if result was None |
deserialize_result
staticmethod
¶
Deserialize a decide() result from a dictionary.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any] | None
|
Serialized result data |
required |
Returns:
| Type | Description |
|---|---|
DecideResult
|
Deserialized DecideResult |
has_chained_amount
staticmethod
¶
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
staticmethod
¶
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 2. 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
staticmethod
¶
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
|
The amount value (Decimal or "all"), or None if not applicable |
set_resolved_amount
staticmethod
¶
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
|
Optional reason for holding (for logging/debugging) |
chain |
str | None
|
Optional target chain for execution (defaults to strategy's primary chain) |
intent_id |
str
|
Unique identifier for this intent |
created_at |
datetime
|
Timestamp when the intent was created |
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%) |
protocol |
str | None
|
Preferred protocol for the swap (e.g., "uniswap_v3", "enso") |
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) |
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
property
¶
Check if this intent uses a chained amount from previous step.
validate_swap_intent
¶
Validate that either amount_usd or amount is provided.
serialize
¶
Serialize the intent to a dictionary.
Backward compatible with existing serialization format.
deserialize
classmethod
¶
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
|
Optional target chain for execution (defaults to strategy's primary chain) |
intent_id |
str
|
Unique identifier for this intent |
created_at |
datetime
|
Timestamp when the intent was created |
deserialize
classmethod
¶
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
|
Pool address (optional, for validation) |
collect_fees |
bool
|
Whether to collect accumulated fees |
protocol |
str
|
LP protocol (e.g., "uniswap_v3", "camelot") |
chain |
str | None
|
Optional target chain for execution (defaults to strategy's primary chain) |
intent_id |
str
|
Unique identifier for this intent |
created_at |
datetime
|
Timestamp when the intent was created |
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
|
Interest rate mode for protocols that support it (Aave: 'variable' | 'stable') |
chain |
str | None
|
Optional target chain for execution (defaults to strategy's primary chain) |
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) and 'stable' modes - Morpho: Does not support rate mode selection (parameter is ignored) - Compound V3: Does not support rate mode selection (parameter is ignored)
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
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 |
chain |
str | None
|
Optional target chain for execution (defaults to strategy's primary chain) |
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 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
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
|
Optional target chain for execution (defaults to strategy's primary chain) |
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
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
|
Optional target chain for execution (defaults to strategy's primary chain) |
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
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
|
Optional target chain for execution (defaults to strategy's primary chain) |
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
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
|
Optional target chain for execution (defaults to strategy's primary chain) |
intent_id |
str
|
Unique identifier for this intent |
created_at |
datetime
|
Timestamp when the intent was created |
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
|
Optional target chain for execution (defaults to strategy's primary chain) |
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", )
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
|
Optional target chain for execution (defaults to strategy's primary chain) |
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", )
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
|
Optional preferred bridge adapter name |
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", )
is_chained_amount
property
¶
Check if this intent uses a chained amount from previous step.
chain
property
¶
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.
normalize_chain
classmethod
¶
Validate and normalize chain names to lowercase.
validate_bridge_intent
¶
Validate bridge intent parameters.
deserialize
classmethod
¶
Deserialize a dictionary to a BridgeIntent.
validate_chains
staticmethod
¶
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
|
Hours until order expires (None = no expiry) |
protocol |
str
|
Protocol to use (defaults to "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) |
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", )
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
|
Target chain (defaults to "polygon" for Polymarket) |
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", )
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
|
Which outcome to redeem ("YES", "NO", or None for both) |
shares |
PredictionShareAmount
|
Number of shares to redeem, or "all" (default) |
protocol |
str
|
Protocol to use (defaults to "polymarket") |
chain |
str | None
|
Target chain (defaults to "polygon" for Polymarket) |
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", )
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:
- Check current balance on target chain
- If balance >= min_amount: Return HoldIntent (no action needed)
- If balance < min_amount: Find a source chain with sufficient balance
- 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
|
Optional preferred bridge adapter name for transfer |
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 )
chain
property
¶
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.
normalize_chain
classmethod
¶
Validate and normalize chain name to lowercase.
validate_ensure_balance_intent
¶
Validate ensure balance intent parameters.
deserialize
classmethod
¶
Deserialize a dictionary to an EnsureBalanceIntent.
validate_target_chain
staticmethod
¶
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 2. If a source chain has sufficient balance -> BridgeIntent 3. 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
dataclass
¶
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
|
Optional description of the sequence purpose |
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
deserialize
classmethod
¶
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.