Skip to content

add new tools endpoints #243

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 192 additions & 1 deletion api/tools.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,20 @@
from typing import List, Optional

from fastapi import APIRouter, HTTPException, Query, Request
from fastapi import APIRouter, Depends, HTTPException, Query, Request
from pydantic import BaseModel, Field # Added import for Pydantic models
from starlette.responses import JSONResponse

from api.dependencies import (
verify_profile_from_token, # Added verify_profile_from_token
)
from backend.factory import backend # Added backend factory
from backend.models import UUID, AgentFilter, Profile # Added Profile, AgentFilter
from lib.logger import configure_logger
from lib.tools import Tool, get_available_tools
from tools.dao_ext_action_proposals import (
ProposeActionSendMessageTool, # Added ProposeActionSendMessageTool
)
from tools.faktory import FaktoryExecuteBuyTool # Added import for Faktory tool

# Configure logger
logger = configure_logger(__name__)
Expand All @@ -16,6 +26,48 @@
available_tools = get_available_tools()


class FaktoryBuyTokenRequest(BaseModel):
"""Request body for executing a Faktory buy order."""

# agent_id: UUID = Field(..., description="The ID of the agent performing the action") # Removed agent_id
btc_amount: str = Field(
...,
description="Amount of BTC to spend on the purchase in standard units (e.g. 0.0004 = 0.0004 BTC or 40000 sats)",
)
dao_token_dex_contract_address: str = Field(
..., description="Contract principal where the DAO token is listed"
)
slippage: Optional[str] = Field(
default="15",
description="Slippage tolerance in basis points (default: 15, which is 0.15%)",
)


class ProposeSendMessageRequest(BaseModel):
"""Request body for proposing a DAO action to send a message."""

action_proposals_voting_extension: str = Field(
...,
description="Contract principal where the DAO creates action proposals for voting by DAO members.",
)
action_proposal_contract_to_execute: str = Field(
...,
description="Contract principal of the action proposal that executes sending a message.",
)
dao_token_contract_address: str = Field(
...,
description="Contract principal of the token used by the DAO for voting.",
)
message: str = Field(
...,
description="Message to be sent through the DAO proposal system.",
)
memo: Optional[str] = Field(
None,
description="Optional memo to include with the proposal.",
)


@router.get("/available", response_model=List[Tool])
async def get_tools(
request: Request,
Expand Down Expand Up @@ -131,3 +183,142 @@ async def search_tools(
except Exception as e:
logger.error(f"Failed to search tools with query '{query}'", exc_info=e)
raise HTTPException(status_code=500, detail=f"Failed to search tools: {str(e)}")


@router.post("/faktory/execute_buy")
async def execute_faktory_buy(
request: Request,
payload: FaktoryBuyTokenRequest,
profile: Profile = Depends(verify_profile_from_token), # Added auth dependency
) -> JSONResponse:
"""Execute a buy order on Faktory DEX.

This endpoint allows an authenticated user's agent to execute a buy order
for a specified token using BTC on the Faktory DEX.

Args:
request: The FastAPI request object.
payload: The request body containing btc_amount,
dao_token_dex_contract_address, and optional slippage.
profile: The authenticated user's profile.

Returns:
JSONResponse: The result of the buy order execution.

Raises:
HTTPException: If there's an error executing the buy order, or if the
agent for the profile is not found.
"""
try:
logger.info(
f"Faktory execute buy request received from {request.client.host if request.client else 'unknown'} for profile {profile.id}"
)

# Get agent_id from profile_id
agents = backend.list_agents(AgentFilter(profile_id=profile.id))
if not agents:
logger.error(f"No agent found for profile ID: {profile.id}")
raise HTTPException(
status_code=404,
detail=f"No agent found for profile ID: {profile.id}",
)

agent = agents[0] # Assuming the first agent is the one to use
agent_id = agent.id

logger.info(
f"Using agent {agent_id} for profile {profile.id} to execute Faktory buy."
)

tool = FaktoryExecuteBuyTool(wallet_id=agent_id) # Use fetched agent_id
result = await tool._arun(
btc_amount=payload.btc_amount,
dao_token_dex_contract_address=payload.dao_token_dex_contract_address,
slippage=payload.slippage,
)

logger.debug(
f"Faktory execute buy result for agent {agent_id} (profile {profile.id}): {result}"
)
return JSONResponse(content=result)

except HTTPException as he:
# Re-raise HTTPExceptions directly
raise he
except Exception as e:
logger.error(
f"Failed to execute Faktory buy for profile {profile.id}", exc_info=e
)
raise HTTPException(
status_code=500,
detail=f"Failed to execute Faktory buy order: {str(e)}",
)


@router.post("/dao/action_proposals/propose_send_message")
async def propose_dao_action_send_message(
request: Request,
payload: ProposeSendMessageRequest,
profile: Profile = Depends(verify_profile_from_token),
) -> JSONResponse:
"""Propose a DAO action to send a message.

This endpoint allows an authenticated user's agent to create a proposal
for sending a message via the DAO's action proposal system.

Args:
request: The FastAPI request object.
payload: The request body containing the proposal details.
profile: The authenticated user's profile.

Returns:
JSONResponse: The result of the proposal creation.

Raises:
HTTPException: If there's an error, or if the agent for the profile is not found.
"""
try:
logger.info(
f"DAO propose send message request received from {request.client.host if request.client else 'unknown'} for profile {profile.id}"
)

agents = backend.list_agents(AgentFilter(profile_id=profile.id))
if not agents:
logger.error(f"No agent found for profile ID: {profile.id}")
raise HTTPException(
status_code=404,
detail=f"No agent found for profile ID: {profile.id}",
)

agent = agents[0]
agent_id = agent.id

logger.info(
f"Using agent {agent_id} for profile {profile.id} to propose DAO send message action."
)

tool = ProposeActionSendMessageTool(wallet_id=agent_id)
result = await tool._arun(
action_proposals_voting_extension=payload.action_proposals_voting_extension,
action_proposal_contract_to_execute=payload.action_proposal_contract_to_execute,
dao_token_contract_address=payload.dao_token_contract_address,
message=payload.message,
memo=payload.memo,
)

logger.debug(
f"DAO propose send message result for agent {agent_id} (profile {profile.id}): {result}"
)
return JSONResponse(content=result)

except HTTPException as he:
raise he
except Exception as e:
logger.error(
f"Failed to propose DAO send message action for profile {profile.id}",
exc_info=e,
)
raise HTTPException(
status_code=500,
detail=f"Failed to propose DAO send message action: {str(e)}",
)