Skip to content
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

Release 0.1.1 #269

Merged
merged 3 commits into from
Jun 19, 2024
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
2 changes: 1 addition & 1 deletion autotx/AutoTx.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ async def try_run(self, prompt: str, non_interactive: bool, summary_method: str
is_goal_supported = chat.chat_history[-1]["content"] != "Goal not supported: TERMINATE"

try:
result = self.wallet.on_intents_ready(self.intents)
result = await self.wallet.on_intents_ready(self.intents)

if isinstance(result, str):
intents_info ="\n".join(
Expand Down
14 changes: 7 additions & 7 deletions autotx/agents/SwapTokensAgent.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
from decimal import Decimal
from textwrap import dedent
from typing import Annotated, Callable
from typing import Annotated, Any, Callable, Coroutine
from autotx.AutoTx import AutoTx
from autotx.autotx_agent import AutoTxAgent
from autotx.autotx_tool import AutoTxTool
from autotx.intents import BuyIntent, Intent, SellIntent
from autotx.token import Token
from autotx.eth_address import ETHAddress
from autotx.utils.ethereum.lifi.swap import SUPPORTED_NETWORKS_BY_LIFI, can_build_swap_transaction
from autotx.utils.ethereum.lifi.swap import SUPPORTED_NETWORKS_BY_LIFI, a_can_build_swap_transaction
from autotx.utils.ethereum.networks import NetworkInfo
from gnosis.eth import EthereumNetworkNotSupported as ChainIdNotSupported

Expand Down Expand Up @@ -81,7 +81,7 @@ def get_tokens_address(token_in: str, token_out: str, network_info: NetworkInfo)
class InvalidInput(Exception):
pass

def swap(autotx: AutoTx, token_to_sell: str, token_to_buy: str) -> Intent:
async def swap(autotx: AutoTx, token_to_sell: str, token_to_buy: str) -> Intent:
sell_parts = token_to_sell.split(" ")
buy_parts = token_to_buy.split(" ")

Expand Down Expand Up @@ -118,7 +118,7 @@ def swap(autotx: AutoTx, token_to_sell: str, token_to_buy: str) -> Intent:
token_in, token_out, autotx.network
)

can_build_swap_transaction(
await a_can_build_swap_transaction(
autotx.web3,
Decimal(exact_amount),
ETHAddress(token_in_address),
Expand Down Expand Up @@ -151,8 +151,8 @@ class BulkSwapTool(AutoTxTool):
"""
)

def build_tool(self, autotx: AutoTx) -> Callable[[str], str]:
def run(
def build_tool(self, autotx: AutoTx) -> Callable[[str], Coroutine[Any, Any, str]]:
async def run(
tokens: Annotated[
str,
"""
Expand All @@ -171,7 +171,7 @@ def run(
for swap_str in swaps:
(token_to_sell, token_to_buy) = swap_str.strip().split(" to ")
try:
all_intents.append(swap(autotx, token_to_sell, token_to_buy))
all_intents.append(await swap(autotx, token_to_sell, token_to_buy))
except InvalidInput as e:
all_errors.append(e)
except Exception as e:
Expand Down
14 changes: 7 additions & 7 deletions autotx/intents.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from autotx.utils.ethereum.build_transfer_native import build_transfer_native
from autotx.utils.ethereum.constants import NATIVE_TOKEN_ADDRESS
from autotx.eth_address import ETHAddress
from autotx.utils.ethereum.lifi.swap import build_swap_transaction
from autotx.utils.ethereum.lifi.swap import a_build_swap_transaction
from autotx.utils.ethereum.networks import NetworkInfo
from autotx.utils.format_amount import format_amount

Expand All @@ -27,7 +27,7 @@ class IntentBase(BaseModel):
summary: str

@abstractmethod
def build_transactions(self, web3: Web3, network: NetworkInfo, smart_wallet_address: ETHAddress) -> list[Transaction]:
async def build_transactions(self, web3: Web3, network: NetworkInfo, smart_wallet_address: ETHAddress) -> list[Transaction]:
pass

class SendIntent(IntentBase):
Expand All @@ -45,7 +45,7 @@ def create(cls, token: Token, amount: float, receiver: ETHAddress) -> 'SendInten
summary=f"Transfer {format_amount(amount)} {token.symbol} to {receiver}",
)

def build_transactions(self, web3: Web3, network: NetworkInfo, smart_wallet_address: ETHAddress) -> list[Transaction]:
async def build_transactions(self, web3: Web3, network: NetworkInfo, smart_wallet_address: ETHAddress) -> list[Transaction]:
tx: TxParams

if self.token.address == NATIVE_TOKEN_ADDRESS:
Expand Down Expand Up @@ -79,8 +79,8 @@ def create(cls, from_token: Token, to_token: Token, amount: float) -> 'BuyIntent
summary=f"Buy {format_amount(amount)} {to_token.symbol} with {from_token.symbol}",
)

def build_transactions(self, web3: Web3, network: NetworkInfo, smart_wallet_address: ETHAddress) -> list[Transaction]:
transactions = build_swap_transaction(
async def build_transactions(self, web3: Web3, network: NetworkInfo, smart_wallet_address: ETHAddress) -> list[Transaction]:
transactions = await a_build_swap_transaction(
web3,
Decimal(str(self.amount)),
ETHAddress(self.from_token.address),
Expand All @@ -107,9 +107,9 @@ def create(cls, from_token: Token, to_token: Token, amount: float) -> 'SellInten
summary=f"Sell {format_amount(amount)} {from_token.symbol} for {to_token.symbol}",
)

def build_transactions(self, web3: Web3, network: NetworkInfo, smart_wallet_address: ETHAddress) -> list[Transaction]:
async def build_transactions(self, web3: Web3, network: NetworkInfo, smart_wallet_address: ETHAddress) -> list[Transaction]:

transactions = build_swap_transaction(
transactions = await a_build_swap_transaction(
web3,
Decimal(str(self.amount)),
ETHAddress(self.from_token.address),
Expand Down
14 changes: 7 additions & 7 deletions autotx/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def __init__(self,
self.max_rounds = max_rounds
self.is_dev = is_dev

autotx_params: AutoTxParams = AutoTxParams(verbose=False, logs=None, cache=False, max_rounds=None, is_dev=False)
autotx_params: AutoTxParams = AutoTxParams(verbose=False, logs=None, cache=False, max_rounds=200, is_dev=False)

app_router = APIRouter()

Expand Down Expand Up @@ -82,7 +82,7 @@ def authorize_app_and_user(authorization: str | None, user_id: str) -> tuple[mod

return (app, app_user)

def build_transactions(app_id: str, user_id: str, chain_id: int, address: str, task: models.Task) -> List[Transaction]:
async def build_transactions(app_id: str, user_id: str, chain_id: int, address: str, task: models.Task) -> List[Transaction]:
if task.running:
raise HTTPException(status_code=400, detail="Task is still running")

Expand All @@ -94,7 +94,7 @@ def build_transactions(app_id: str, user_id: str, chain_id: int, address: str, t
transactions: list[Transaction] = []

for intent in task.intents:
transactions.extend(intent.build_transactions(app_config.web3, app_config.network_info, app_config.manager.address))
transactions.extend(await intent.build_transactions(app_config.web3, app_config.network_info, app_config.manager.address))

return transactions

Expand Down Expand Up @@ -211,7 +211,7 @@ def get_intents(task_id: str, authorization: Annotated[str | None, Header()] = N
return task.intents

@app_router.get("/api/v1/tasks/{task_id}/transactions", response_model=List[Transaction])
def get_transactions(
async def get_transactions(
task_id: str,
address: str,
chain_id: int,
Expand All @@ -227,7 +227,7 @@ def get_transactions(
if task.chain_id != chain_id:
raise HTTPException(status_code=400, detail="Chain ID does not match task")

transactions = build_transactions(app.id, user_id, chain_id, address, task)
transactions = await build_transactions(app.id, user_id, chain_id, address, task)

return transactions

Expand All @@ -236,7 +236,7 @@ class PreparedTransactionsDto(BaseModel):
transactions: List[Transaction]

@app_router.post("/api/v1/tasks/{task_id}/transactions/prepare", response_model=PreparedTransactionsDto)
def prepare_transactions(
async def prepare_transactions(
task_id: str,
address: str,
chain_id: int,
Expand All @@ -252,7 +252,7 @@ def prepare_transactions(
if task.chain_id != chain_id:
raise HTTPException(status_code=400, detail="Chain ID does not match task")

transactions = build_transactions(app.id, app_user.user_id, chain_id, address, task)
transactions = await build_transactions(app.id, app_user.user_id, chain_id, address, task)

if len(transactions) == 0:
raise HTTPException(status_code=400, detail="No transactions to send")
Expand Down
78 changes: 72 additions & 6 deletions autotx/utils/ethereum/lifi/swap.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import asyncio
from dataclasses import dataclass
from decimal import Decimal
from typing import Any, cast
Expand Down Expand Up @@ -103,6 +104,49 @@ def get_quote(
quote["toolDetails"]["name"],
)

async def fetch_quote_with_retries(
token_in_address: ETHAddress,
token_in_decimals: int,
token_in_symbol: str,
token_out_address: ETHAddress,
token_out_decimals: int,
token_out_symbol: str,
chain: ChainId,
amount: Decimal,
is_exact_input: bool,
_from: ETHAddress,
) -> QuoteInformation:
retries = 0
while True:
try:
quote = get_quote(
token_in_address,
token_in_decimals,
token_in_symbol,
token_out_address,
token_out_decimals,
token_out_symbol,
chain,
amount,
not is_exact_input,
_from,
)
return quote
except Exception as e:
if "The from amount must be greater than zero." in str(e):
if is_exact_input:
raise Exception(f"The specified amount of {token_in_symbol} is too low")
else:
raise Exception(f"The specified amount of {token_out_symbol} is too low")

elif "No available quotes for the requested transfer" in str(e):
if retries < 5:
retries += 1
await asyncio.sleep(1)
continue
raise e


def build_swap_transaction(
web3: Web3,
amount: Decimal,
Expand All @@ -111,6 +155,17 @@ def build_swap_transaction(
_from: ETHAddress,
is_exact_input: bool,
chain: ChainId,
) -> list[Transaction]:
return asyncio.run(a_build_swap_transaction(web3, amount, token_in_address, token_out_address, _from, is_exact_input, chain))

async def a_build_swap_transaction(
web3: Web3,
amount: Decimal,
token_in_address: ETHAddress,
token_out_address: ETHAddress,
_from: ETHAddress,
is_exact_input: bool,
chain: ChainId,
) -> list[Transaction]:
native_token_symbol = get_native_token_symbol(chain)
token_in_is_native = token_in_address.hex == NATIVE_TOKEN_ADDRESS
Expand Down Expand Up @@ -139,7 +194,8 @@ def build_swap_transaction(
if token_out_is_native
else token_out.functions.symbol().call()
)
quote = get_quote(

quote = await fetch_quote_with_retries(
token_in_address,
token_in_decimals,
token_in_symbol,
Expand All @@ -148,10 +204,9 @@ def build_swap_transaction(
token_out_symbol,
chain,
amount,
not is_exact_input,
is_exact_input,
_from,
)

transactions: list[Transaction] = []
if not token_in_is_native:
approval_address = quote.approval_address
Expand Down Expand Up @@ -194,6 +249,17 @@ def can_build_swap_transaction(
_from: ETHAddress,
is_exact_input: bool,
chain: ChainId,
) -> bool:
return asyncio.run(a_can_build_swap_transaction(web3, amount, token_in_address, token_out_address, _from, is_exact_input, chain))

async def a_can_build_swap_transaction(
web3: Web3,
amount: Decimal,
token_in_address: ETHAddress,
token_out_address: ETHAddress,
_from: ETHAddress,
is_exact_input: bool,
chain: ChainId,
) -> bool:
native_token_symbol = get_native_token_symbol(chain)
token_in_is_native = token_in_address.hex == NATIVE_TOKEN_ADDRESS
Expand Down Expand Up @@ -222,7 +288,8 @@ def can_build_swap_transaction(
if token_out_is_native
else token_out.functions.symbol().call()
)
quote = get_quote(

quote = await fetch_quote_with_retries(
token_in_address,
token_in_decimals,
token_in_symbol,
Expand All @@ -231,10 +298,9 @@ def can_build_swap_transaction(
token_out_symbol,
chain,
amount,
not is_exact_input,
is_exact_input,
_from,
)

if not token_in_is_native:
approval_address = quote.approval_address
allowance = token_in.functions.allowance(_from.hex, approval_address).call()
Expand Down
2 changes: 1 addition & 1 deletion autotx/wallets/api_smart_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,5 @@ def on_intents_prepared(self, intents: list[Intent]) -> None:
saved_task.intents.extend(intents)
self.tasks.update(saved_task)

def on_intents_ready(self, _intents: list[Intent]) -> bool | str:
async def on_intents_ready(self, _intents: list[Intent]) -> bool | str:
return True
4 changes: 2 additions & 2 deletions autotx/wallets/safe_smart_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ def __init__(self, manager: SafeManager, auto_submit_tx: bool):
def on_intents_prepared(self, intents: list[Intent]) -> None:
pass

def on_intents_ready(self, intents: list[Intent]) -> bool | str:
async def on_intents_ready(self, intents: list[Intent]) -> bool | str:
transactions: list[TransactionBase] = []

for intent in intents:
transactions.extend(intent.build_transactions(self.manager.web3, self.manager.network, self.manager.address))
transactions.extend(await intent.build_transactions(self.manager.web3, self.manager.network, self.manager.address))

return self.manager.send_multisend_tx_batch(transactions, not self.auto_submit_tx)
2 changes: 1 addition & 1 deletion autotx/wallets/smart_wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def on_intents_prepared(self, intents: list[Intent]) -> None:
pass

@abstractmethod
def on_intents_ready(self, intents: list[Intent]) -> bool | str: # True if sent, False if declined, str if feedback
async def on_intents_ready(self, intents: list[Intent]) -> bool | str: # True if sent, False if declined, str if feedback
pass

def balance_of(self, token_address: ETHAddress | None = None) -> float:
Expand Down
Loading