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):
- 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.
- Create an ActionBundle
- The strategy must return these Actions inside a single ActionBundle so the transactions can be signed and broadcast.
- 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
)
- 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.