|
| 1 | +from eth_typing import ChecksumAddress |
| 2 | +from httpx import HTTPStatusError, RequestError, TimeoutException |
| 3 | +from web3.contract.async_contract import AsyncContractFunction |
| 4 | +from web3.types import TxParams |
| 5 | + |
| 6 | +from flare_ai_kit.agent import adk |
| 7 | +from flare_ai_kit.common import AbiError, ExplorerError |
| 8 | + |
| 9 | +# --- Flare Network --- |
| 10 | + |
| 11 | + |
| 12 | +@adk.tool |
| 13 | +async def check_balance(address: str) -> float: |
| 14 | + """ |
| 15 | + Check the balance of a given Flare address in FLR. |
| 16 | + """ |
| 17 | + from flare_ai_kit.ecosystem.flare import Flare |
| 18 | + from flare_ai_kit.ecosystem.settings import EcosystemSettings |
| 19 | + |
| 20 | + settings = EcosystemSettings() |
| 21 | + flare = Flare(settings) |
| 22 | + return await flare.check_balance(address) |
| 23 | + |
| 24 | + |
| 25 | +@adk.tool |
| 26 | +async def check_connection() -> bool: |
| 27 | + """ |
| 28 | + Check the connection status to the configured RPC endpoint. |
| 29 | +
|
| 30 | + Returns: |
| 31 | + True if connected, False otherwise. |
| 32 | +
|
| 33 | + """ |
| 34 | + from flare_ai_kit.ecosystem.flare import Flare |
| 35 | + from flare_ai_kit.ecosystem.settings import EcosystemSettings |
| 36 | + |
| 37 | + settings = EcosystemSettings() |
| 38 | + flare = Flare(settings) |
| 39 | + return await flare.check_connection() |
| 40 | + |
| 41 | + |
| 42 | +@adk.tool |
| 43 | +async def build_transaction( |
| 44 | + function_call: AsyncContractFunction, from_addr: ChecksumAddress |
| 45 | +) -> TxParams | None: |
| 46 | + """Builds a transaction with dynamic gas and nonce parameters.""" |
| 47 | + from flare_ai_kit.ecosystem.flare import Flare |
| 48 | + from flare_ai_kit.ecosystem.settings import EcosystemSettings |
| 49 | + |
| 50 | + settings = EcosystemSettings() |
| 51 | + flare = Flare(settings) |
| 52 | + return await flare.build_transaction(function_call, from_addr) |
| 53 | + |
| 54 | + |
| 55 | +@adk.tool |
| 56 | +async def sign_and_send_transaction(tx: TxParams) -> str | None: |
| 57 | + """ |
| 58 | + Sign and send a transaction to the network. |
| 59 | +
|
| 60 | + Args: |
| 61 | + tx (TxParams): Transaction parameters to be sent |
| 62 | +
|
| 63 | + Returns: |
| 64 | + str: Transaction hash of the sent transaction |
| 65 | +
|
| 66 | + Raises: |
| 67 | + ValueError: If account is not initialized |
| 68 | +
|
| 69 | + """ |
| 70 | + from flare_ai_kit.ecosystem.flare import Flare |
| 71 | + from flare_ai_kit.ecosystem.settings import EcosystemSettings |
| 72 | + |
| 73 | + settings = EcosystemSettings() |
| 74 | + flare = Flare(settings) |
| 75 | + return await flare.sign_and_send_transaction(tx) |
| 76 | + |
| 77 | + |
| 78 | +@adk.tool |
| 79 | +async def create_send_flr_tx( |
| 80 | + from_address: str, to_address: str, amount: float |
| 81 | +) -> TxParams: |
| 82 | + """ |
| 83 | + Create a transaction to send FLR tokens. |
| 84 | +
|
| 85 | + Args: |
| 86 | + from_address (str): Sender address |
| 87 | + to_address (str): Recipient address |
| 88 | + amount (float): Amount of FLR to send |
| 89 | +
|
| 90 | + Returns: |
| 91 | + TxParams: Transaction parameters for sending FLR |
| 92 | +
|
| 93 | + Raises: |
| 94 | + ValueError: If account does not exist |
| 95 | +
|
| 96 | + """ |
| 97 | + from flare_ai_kit.ecosystem.flare import Flare |
| 98 | + from flare_ai_kit.ecosystem.settings import EcosystemSettings |
| 99 | + |
| 100 | + settings = EcosystemSettings() |
| 101 | + flare = Flare(settings) |
| 102 | + return await flare.create_send_flr_tx(from_address, to_address, amount) |
| 103 | + |
| 104 | + |
| 105 | +# --- FTSO Protocol --- |
| 106 | + |
| 107 | + |
| 108 | +@adk.tool |
| 109 | +async def get_ftso_latest_price(feed_name: str) -> float: |
| 110 | + """ |
| 111 | + Retrieves the latest price for a single feed. |
| 112 | +
|
| 113 | + Args: |
| 114 | + feed_name: The human-readable feed name (e.g., "BTC/USD"). |
| 115 | + category: The feed category (default: CRYPTO i.e. "01"). |
| 116 | +
|
| 117 | + Returns: |
| 118 | + The latest price as a float, adjusted for decimals. |
| 119 | + Returns 0.0 if the price or decimals returned by the contract are zero, |
| 120 | + which might indicate an invalid or unprovided feed. |
| 121 | +
|
| 122 | + Raises: |
| 123 | + FtsoV2Error: If the category is invalid, feed name cannot be converted |
| 124 | + or the contract call fails. |
| 125 | +
|
| 126 | + """ |
| 127 | + from flare_ai_kit.ecosystem.protocols.ftsov2 import FtsoV2 |
| 128 | + from flare_ai_kit.ecosystem.settings import EcosystemSettings |
| 129 | + |
| 130 | + settings = EcosystemSettings() |
| 131 | + ftso = await FtsoV2.create(settings) |
| 132 | + if not ftso: |
| 133 | + raise ValueError("FtsoV2 instance not fully initialized. Use FtsoV2.create().") |
| 134 | + |
| 135 | + return await ftso.get_latest_price(feed_name) |
| 136 | + |
| 137 | + |
| 138 | +@adk.tool |
| 139 | +async def get_ftso_latest_prices(feed_names: list[str]) -> list[float]: |
| 140 | + """ |
| 141 | + Retrieves the latest prices for multiple feeds within the same category. |
| 142 | +
|
| 143 | + Args: |
| 144 | + feed_names: A list of human-readable feed names. |
| 145 | + category: The feed category for all requested feeds (default: "01"). |
| 146 | +
|
| 147 | + Returns: |
| 148 | + A list of prices as floats, corresponding to the order of `feed_names`. |
| 149 | + Individual prices will be 0.0 if the contract returned zero values. |
| 150 | +
|
| 151 | + Raises: |
| 152 | + FtsoV2Error: If the category is invalid, feed name cannot be converted |
| 153 | + or the contract call fails. |
| 154 | +
|
| 155 | + """ |
| 156 | + from flare_ai_kit.ecosystem.protocols.ftsov2 import FtsoV2 |
| 157 | + from flare_ai_kit.ecosystem.settings import EcosystemSettings |
| 158 | + |
| 159 | + settings = EcosystemSettings() |
| 160 | + ftso = await FtsoV2.create(settings) |
| 161 | + if not ftso: |
| 162 | + raise ValueError("FtsoV2 instance not fully initialized. Use FtsoV2.create().") |
| 163 | + |
| 164 | + return await ftso.get_latest_prices(feed_names) |
| 165 | + |
| 166 | + |
| 167 | +# --- Explorer --- |
| 168 | + |
| 169 | + |
| 170 | +@adk.tool |
| 171 | +async def get_contract_abi(contract_address: str) -> list[dict[str, str]]: |
| 172 | + """ |
| 173 | + Asynchronously get the ABI for a contract from the Chain Explorer API. |
| 174 | +
|
| 175 | + Args: |
| 176 | + contract_address: Address of the contract. |
| 177 | +
|
| 178 | + Returns: |
| 179 | + list[dict]: Contract ABI parsed from the JSON string response. |
| 180 | +
|
| 181 | + Raises: |
| 182 | + ValueError: If the ABI string in the response is not valid JSON |
| 183 | + or if the underlying API request fails. |
| 184 | + (Exceptions from _get): RequestError, TimeoutException, HTTPStatusError |
| 185 | +
|
| 186 | + """ |
| 187 | + from flare_ai_kit.ecosystem.explorer import BlockExplorer |
| 188 | + from flare_ai_kit.ecosystem.settings import EcosystemSettings |
| 189 | + |
| 190 | + settings = EcosystemSettings() |
| 191 | + explorer = BlockExplorer(settings) |
| 192 | + try: |
| 193 | + async with explorer: |
| 194 | + return await explorer.get_contract_abi(contract_address) |
| 195 | + except (HTTPStatusError, RequestError, TimeoutException) as e: |
| 196 | + raise ExplorerError(f"Failed to fetch contract ABI: {e}") |
| 197 | + except AbiError as e: |
| 198 | + raise ValueError(f"Invalid ABI response for contract {contract_address}: {e}") |
| 199 | + |
| 200 | + |
| 201 | +# --- Social: X (Twitter) --- |
| 202 | + |
| 203 | + |
| 204 | +@adk.tool |
| 205 | +async def post_to_x(content: str) -> bool: |
| 206 | + """Posts a message to X (Twitter).""" |
| 207 | + from flare_ai_kit.social.settings import SocialSettings |
| 208 | + from flare_ai_kit.social.x import XClient |
| 209 | + |
| 210 | + settings = SocialSettings() # type: ignore[call-arg] |
| 211 | + x_client = XClient(settings) |
| 212 | + if not x_client.is_configured: |
| 213 | + raise ValueError( |
| 214 | + "XClient is not configured. Ensure API keys are set in the environment." |
| 215 | + ) |
| 216 | + |
| 217 | + return await x_client.post_tweet(content) |
| 218 | + |
| 219 | + |
| 220 | +# --- Social: Telegram --- |
| 221 | + |
| 222 | + |
| 223 | +@adk.tool |
| 224 | +async def send_telegram_message(chat_id: str, message: str) -> bool: |
| 225 | + """ |
| 226 | + Sends a message to a Telegram chat. |
| 227 | +
|
| 228 | + This is an asynchronous operation. |
| 229 | +
|
| 230 | + Args: |
| 231 | + chat_id: The unique identifier for the target chat |
| 232 | + (e.g., '@channelname' or a user ID). |
| 233 | + text: The text of the message to send. |
| 234 | +
|
| 235 | + Returns: |
| 236 | + True if the message was sent successfully, False otherwise. |
| 237 | +
|
| 238 | + """ |
| 239 | + from flare_ai_kit.social.settings import SocialSettings |
| 240 | + from flare_ai_kit.social.telegram import TelegramClient |
| 241 | + |
| 242 | + settings = SocialSettings() # type: ignore[call-arg] |
| 243 | + telegram_client = TelegramClient(settings) |
| 244 | + if not telegram_client.is_configured: |
| 245 | + raise ValueError( |
| 246 | + "TelegramClient is not configured. Ensure API token is set in the environment." |
| 247 | + ) |
| 248 | + |
| 249 | + return await telegram_client.send_message(chat_id, message) |
0 commit comments