Skip to main content

Actions & Action Bundle

Actions and ActionBundles form the core mechanism for executing strategies. Each strategy must have function run which returns an ActionBundle - this is the supported way to broadcast transactions. This architecture ensures a flexible yet structured approach to managing complex strategies while maintaining clear execution states, transaction records, and error (or "sad") flow handling.

Actions

An Action represents a single intended transaction. It contains:

  • Type
    The specific trading operation to perform (e.g., swap, provide liquidity, withdraw).
    Supported Values:

    • TRANSFER
    • WRAP
    • APPROVE
    • SWAP
    • OPEN_LP_POSITION
    • CLOSE_LP_POSITION
  • Protocol
    The DeFi protocol being interacted with (e.g., Uniswap, Aave v3).
    Supported Values:

    • UNISWAP_V3
  • Parameters
    Details needed for the operation (e.g., token amounts, slippage tolerances).

    class TransferParams(Params):
    type: ActionType = ActionType.TRANSFER
    from_address: str
    to_address: str
    amount: int # token decimal unit (wei)
    nonce_counter: Optional[int] = Field(default=None)

    class WrapParams(Params):
    type: ActionType = ActionType.WRAP
    from_address: str
    amount: int # token decimal unit (wei)

    class UnwrapParams(Params):
    type: ActionType = ActionType.UNWRAP
    from_address: str
    token_address: str # wrap token address
    amount: int # token decimal unit (wei)

    class ApproveParams(Params):
    type: ActionType = ActionType.APPROVE
    token_address: str
    spender_address: str
    from_address: str
    amount: Optional[int] = None # token decimal unit (wei)

    class SwapParams(Params):
    type: ActionType = ActionType.SWAP
    side: SwapSide
    tokenIn: str
    tokenOut: str
    fee: int
    recipient: str
    amount: int # token decimal unit
    slippage: Optional[float] = Field(default=None) # e.g. 0.05 is 5%
    amountOutMinimum: Optional[int] = Field(default=None) # for sell side
    amountInMaximum: Optional[int] = Field(default=None) # for buy side
    transfer_eth_in: Optional[bool] = Field(default=False) # optional
    sqrtPriceLimitX96: Optional[int] = Field(default=None) # not used for now

    class OpenPositionParams(Params):
    type: ActionType = ActionType.OPEN_LP_POSITION
    token0: str
    token1: str
    fee: int
    price_lower: float
    price_upper: float
    amount0_desired: int
    amount1_desired: int
    recipient: str
    amount0_min: Optional[int] = Field(default=None)
    amount1_min: Optional[int] = Field(default=None)
    slippage: Optional[float] = Field(default=None)

    class ClosePositionParams(Params):
    type: ActionType = ActionType.CLOSE_LP_POSITION
    position_id: int
    recipient: str
    token0: str
    token1: str
    amount0_min: Optional[int] = Field(default=None)
    amount1_min: Optional[int] = Field(default=None)
    slippage: Optional[float] = Field(default=None)
    pool_address: Optional[str] = Field(default=None)
  • Execution Details
    Records of what happened during execution (e.g., timestamps, outcome).

  • Transaction Information
    Blockchain transaction hashes, gas information, and any relevant metadata.

Python Class

class Action(BaseModel):
type: ActionType
params: Params
protocol: Protocol
id: uuid.UUID = Field(default_factory=uuid.uuid4)
execution_details: Optional[Receipt] = None
transactions: List[Transaction] = Field(default_factory=default_list)
transaction_hashes: List[str] = Field(default_factory=default_list)
bundle_id: Optional[uuid.UUID] = None

ActionBundles

An ActionBundle is a container for one or more Actions and is the only supported way to broadcast transactions. It manages:

  • Actions
    A single ActionBundle can include one or many Actions. Even if your strategy has just one Action, it must still be wrapped in an ActionBundle.
    Supported Values:

  • Network Details
    Specifies which blockchain network and chain each Action will execute on.
    Suppored Values:

    • Network.ANVIL (this is for local testing)
    • Network.MAINNET
  • Execution State
    Tracks the status of the entire bundle.
    Supported Values:

    • CREATED: The ActionBundle has been defined but not yet executed
    • PENDING: The ActionBundle is awaiting execution by the Execution Manager
    • EXECUTING: The Actions within the bundle are actively being processed
    • COMPLETED: All Actions have successfully executed
    • FAILED: At least one Action encountered an error (a "sad flow")
  • Transaction Management
    Handles signed transactions and includes their blockchain receipts.

  • Timing Controls
    Manages deadlines and execution timestamps to ensure Actions are executed promptly or canceled when expired.

There is a limit of 3 Actions per action bundle.

Python Class

class ActionBundle(BaseModel):
actions: List[Action]
network: Network
chain: Chain
id: uuid.UUID = Field(default_factory=uuid.uuid4)
transactions: List[Transaction] = Field(default_factory=default_list)
signed_transactions: List[dict] = Field(
default_factory=default_list, exclude=True
) # exclude from model_dump
raw_transactions: List[str] = Field(
default_factory=default_list
) # make model_dump easier for signed transactions
transaction_hashes: List[str] = Field(
default_factory=default_list
) # make model_dump easier for signed transactions
cached_receipts: Dict[str, dict] = Field(
default_factory=dict, exclude=True
) # exclude from model_dump
deadline: Optional[float] = None
created_at: float = Field(
default_factory=lambda: datetime.now(pytz.utc).timestamp()
)
executed_at: Optional[float] = None
status: ExecutionStatus = ExecutionStatus.CREATED
strategy_id: str
config: Any
persistent_state: Any

Example Use Case

Consider a straightforward arbitrage strategy (Swapping USDC-WETH):

  1. Define Actions
  • Action 1: Approve USDC to be used by the Uniswap contract.
  • Action 2: Approve WETH to be used by the Uniswap contract.
  • Action 3: Swap action of USDC-WETH using the Uniswap contract.
  1. Create an ActionBundle
  • The strategy must return these Actions inside a single ActionBundle so the transactions can be signed and broadcast.
  1. Execution and State Tracking
  • Each Action is translated into transactions that can be broadcasted using the TransactionManager.
  • The state of the ActionBundle is updated from CREATED to BUILT.
  • Each Action is signed using the AccountManager.
  • The state of the ActionBundle is updated from BUILT to SIGNED.
  • The transactions are broadcasted using ExecutionManager.
  • If all actions complete successfully, the bundle moves to COMPLETED.
  • If any error occurs (e.g., a failed transaction), the bundle transitions to PARTIAL_EXECUTION, and the persistent store captures the error details. Code should be created to handle the several failed execution status.

Execution Status Values

class ExecutionStatus(Enum):
CREATED = "CREATED"
BUILT = "BUILT"
SIGNED = "SIGNED"
# In executioner module
PRE_SEND = "PRE_SEND" # Immediately before sending the bundle/transaction
SENT = "SENT" # Immediately after sending the bundle/transaction
SUCCESS = "SUCCESS" # Successfully executed bundle/transaction
FAILED = "FAILED" # Failed to execute bundle/transaction
NOT_INCLUDED = (
"NOT_INCLUDED" # Bundle/transaction was not included in the blockchain
)
CANCELLED = "CANCELLED" # Bundle/transaction was cancelled
PARTIAL_EXECUTION = "PARTIAL_EXECUTION" # Not all transactions in the bundle were executed successfully
RECEIPT_UNKNOWN = "RECEIPT_UNKNOWN" # bundle/transaction receipt cannot be obtained
RECEIPT_PARSED_FAILURE = (
"RECEIPT_PARSED_FAILURE" # Failed to parse the receipt successfully
)
UNKNOWN = (
"UNKNOWN" # The transactions in the bundle have statuses that are not expected
)
  1. Monitoring and Analysis
  • Transaction receipts, execution timestamps, and any error details are stored and can be reviewed later for monitoring and analysis. You can review them in the local_storage when testing locally, on deployment you should be able to see them on the web app.