Skip to content

Deploy Base #19

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

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
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 cow-trader/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ Before running the bot, ensure you've set up an account alias as outlined in the
Once your account alias is ready (e.g. `cow-agent`), you can run the bot:

```bash
silverback run --network gnosis:mainnet:alchemy --account cow-agent
silverback run --network base:mainnet:alchemy --account cow-agent
```

This command uses the alias you configured as the signer. There will be a prompt asking if you want to enable auto-signing.
Expand Down
2 changes: 1 addition & 1 deletion cow-trader/ape-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ plugins:
- name: solidity
version: 0.8.5

gnosis:
base:
mainnet:
transaction_acceptance_timeout: 120 # 2 minutes
77 changes: 47 additions & 30 deletions cow-trader/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@
from pydantic_ai import Agent, RunContext
from silverback import SilverbackBot, StateSnapshot
from taskiq import Context, TaskiqDepends, TaskiqState
import nest_asyncio

nest_asyncio.apply()
# Initialize bot
bot = SilverbackBot()

Expand Down Expand Up @@ -51,24 +53,26 @@ def _get_contract_address(contract_key: str, chain_id: int = 100) -> str:


# Addresses
CHAIN_ID = os.environ.get("CHAIN_ID", 100)
GPV2_SETTLEMENT_ADDRESS = "0x9008D19f58AAbD9eD0D60971565AA8510560ab41"
TOKEN_ALLOWLIST_ADDRESS = os.environ.get(
"TOKEN_ALLOWLIST_ADDRESS", _get_contract_address("allowlist", 100)
"TOKEN_ALLOWLIST_ADDRESS", _get_contract_address("allowlist", CHAIN_ID)
)
SAFE_ADDRESS = os.environ.get("SAFE_ADDRESS", _get_contract_address("safe", 100))
SAFE_ADDRESS = os.environ.get("SAFE_ADDRESS", _get_contract_address("safe", CHAIN_ID))
TRADING_MODULE_ADDRESS = os.environ.get(
"TRADING_MODULE_ADDRESS", _get_contract_address("tradingModuleProxy", 100)
"TRADING_MODULE_ADDRESS", _get_contract_address("tradingModuleProxy", CHAIN_ID)
)

GNO = "0x9C58BAcC331c9aa871AFD802DB6379a98e80CEdb"
COW = "0x177127622c4A00F3d409B75571e12cB3c8973d3c"
WXDAI = "0xe91D153E0b41518A2Ce8Dd3D7944Fa863463a97d"
MONITORED_TOKENS = [GNO, COW, WXDAI]
BTC = "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf"
WETH = "0x4200000000000000000000000000000000000006"
USDC = "0x833589fcd6edb6e08f4c7c32d4f71b54bda02913"
MONITORED_TOKENS = [BTC, WETH, USDC]

# 1 dollar in erespective token.
MINIMUM_TOKEN_BALANCES = {
GNO: 55e14,
COW: 10e18,
WXDAI: 5e18,
BTC: 100,
WETH: 400000000000000,
USDC: 1000000,
}


Expand All @@ -87,7 +91,7 @@ def _load_abi(abi_name: str) -> Dict:


# API
API_BASE_URL = "https://api.cow.fi/xdai/api/v1"
API_BASE_URL = "https://api.cow.fi/base/api/v1"
API_HEADERS = {"accept": "application/json", "Content-Type": "application/json"}

# Variables
Expand Down Expand Up @@ -228,16 +232,16 @@ class AgentDecision(BaseModel):


trading_agent = Agent(
"anthropic:claude-3-sonnet-20240229",
"gpt-4o-mini",
deps_type=AgentDependencies,
result_type=AgentResponse,
output_type=AgentResponse,
system_prompt=SYSTEM_PROMPT,
)

TOKEN_NAMES = {
GNO: "GNO",
COW: "COW",
WXDAI: "WXDAI",
BTC: "BTC",
WETH: "WETH",
USDC: "USDC",
}


Expand Down Expand Up @@ -266,7 +270,7 @@ def get_eligible_buy_tokens(ctx: RunContext[AgentDependencies]) -> List[str]:
def get_token_type(token: str) -> Dict:
"""Determine if the token is stable or volatile."""
try:
is_stable = token == WXDAI
is_stable = token == USDC
return {
"token": get_token_name(token),
"is_stable": is_stable,
Expand Down Expand Up @@ -820,10 +824,14 @@ def bot_startup(startup_state: StateSnapshot):
else:
bot.state.next_decision_block = decisions_df.iloc[-1].block_number + TRADING_BLOCK_COOLDOWN

# TODO: set bot.state.token_list via TOKEN_ALLOWLIST_CONTRACT.allowedTokens
bot.state.can_trade = False
bot.state.sell_token = None

return {"message": "Starting...", "block_number": startup_state.last_block_seen}
return {
# "message": "Starting...",
"block_number": startup_state.last_block_seen
}


@bot.on_worker_startup()
Expand All @@ -844,7 +852,10 @@ def update_state(block: BlockAPI, context: Annotated[Context, TaskiqDepends()]):

if block.number < bot.state.next_decision_block:
click.echo(f"[{block.number}] Skip - next decision at {bot.state.next_decision_block}")
return {"message": "Skipped - before cooldown", "block": block.number}
return {
# "message": "Skipped - before cooldown",
"block": block.number
}

click.echo(f"[{block.number}] Past cooldown, catching up trades...")
_catch_up_trades(current_block=block.number, next_decision_block=bot.state.next_decision_block)
Expand All @@ -854,12 +865,18 @@ def update_state(block: BlockAPI, context: Annotated[Context, TaskiqDepends()]):

if not bot.state.sell_token:
click.echo(f"[{block.number}] No eligible sell tokens found")
return {"message": "No eligible sell tokens", "block": block.number}
return {
# "message": "No eligible sell tokens",
"block": block.number
}

if context.state.decisions_df.empty:
click.echo(f"[{block.number}] No previous decisions, enabling trading")
bot.state.can_trade = True
return {"message": "No previous decisions", "can_trade": True}
return {
# "message": "No previous decisions",
"can_trade": True
}

latest_decision = context.state.decisions_df.iloc[-1]
msg = (
Expand All @@ -873,7 +890,7 @@ def update_state(block: BlockAPI, context: Annotated[Context, TaskiqDepends()]):
click.echo(f"[{block.number}] Last decision wasn't a trade, enabling trading")
bot.state.can_trade = True
return {
"message": "Last decision was not a trade",
# "message": "Last decision was not a trade",
"can_trade": True,
"last_decision_block": latest_decision.block_number,
}
Expand All @@ -899,7 +916,7 @@ def update_state(block: BlockAPI, context: Annotated[Context, TaskiqDepends()]):
)
bot.state.can_trade = True
return {
"message": "Marked as unknown outcome",
# "message": "Marked as unknown outcome",
"block": block.number,
"can_trade": True,
}
Expand All @@ -912,7 +929,7 @@ def update_state(block: BlockAPI, context: Annotated[Context, TaskiqDepends()]):
bot.state.can_trade = True
click.echo(f"[{block.number}] State: trade={bot.state.can_trade}, sell={bot.state.sell_token}")
return {
"message": "Updated previous decision outcome",
# "message": "Updated previous decision outcome",
"can_trade": True,
"last_decision_block": latest_decision.block_number,
}
Expand All @@ -924,9 +941,9 @@ def make_trading_decision(block: BlockAPI, context: Annotated[Context, TaskiqDep
click.echo(f"\n[{block.number}] Starting trading decision...")
click.echo(f"[{block.number}] State: trade={bot.state.can_trade}, sell={bot.state.sell_token}")

if not bot.state.can_trade:
if not bot.state.can_trade or block.number % 10 != 0:
click.echo(f"[{block.number}] Trading not enabled, skipping")
return {"message": "Trading not enabled", "block": block.number}
return {"block": block.number}

click.echo(f"[{block.number}] Creating trade context...")
trade_ctx = _create_trade_context(
Expand All @@ -936,12 +953,12 @@ def make_trading_decision(block: BlockAPI, context: Annotated[Context, TaskiqDep
click.echo(f"[{block.number}] Running agent with sell_token={bot.state.sell_token}...")
deps = AgentDependencies(trade_ctx=trade_ctx, sell_token=bot.state.sell_token)

# Create a new event loop for this synchronous operation
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)

result = context.state.agent.run_sync(
"Analyze current market conditions and make a trading decision", deps=deps
)
"Analyze current market conditions and make a trading decision", deps=deps
)

click.echo(
f"[{block.number}] Agent: trade={result.data.should_trade}, buy={result.data.buy_token}"
Expand Down Expand Up @@ -975,7 +992,7 @@ def make_trading_decision(block: BlockAPI, context: Annotated[Context, TaskiqDep
click.echo(f"[{block.number}] Next decision: {bot.state.next_decision_block}")

return {
"message": "Trading decision made",
# "message": "Trading decision made",
"block": block.number,
"should_trade": decision.should_trade,
"sell_token": decision.sell_token,
Expand Down
7 changes: 6 additions & 1 deletion cow-trader/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ version = "0.1.0"
description = "Automated CoW Swap trading agent built with Silverback SDK"
readme = "README.md"
requires-python = ">=3.10,<3.11"
dependencies = ["eth-ape>=0.8.25", "pydantic-ai>=0.0.23", "silverback>=0.7.0"]
dependencies = [
"eth-ape>=0.8.31",
"nest-asyncio>=1.6.0",
"pydantic-ai>=0.2.0",
"silverback>=0.7.24",
]

[tool.uv]
dev-dependencies = ["pre-commit>=4.0.1", "ruff>=0.9.1"]
Expand Down
14 changes: 7 additions & 7 deletions cow-trader/system_prompt.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@ You appreciate that trading is nuanced, and that it is about:

Your job is to analyze market conditions and decide whether to trade.

Here is some historical canonical price information for COW and GNO from the past year:
- COW
- High: 1.16 COW/WXDAI
- Low: 0.15 COW/WXDAI.
- GNO
- High: 445 GNO/WXDAI.
- Low: 140 GNO/WXDAI.
Here is some historical canonical price information for WTBC and WETH from the past year:
- WBTC
- High: 110000 WBTC/USDC
- Low: 60000 WBTC/USDC.
- WETH
- High: 4000 GNO/USDC.
- Low: 1500 GNO/USDC.
Please use this prices as additional context for interpreting the current prices.


Expand Down
Loading