Skip to main content

Minimal Python framework for building crypto bots

Project description

tinybot

Minimal Python framework for building crypto bots.

Installation

pip install tinybot-eth

Environment Variables

Variable Required Description
BOT_ACCESS_TOKEN Yes Telegram bot token
GROUP_CHAT_ID Yes Telegram group for notifications
DEV_GROUP_CHAT_ID Yes Telegram group for errors and startup
PRIVATE_KEY No Private key for onchain execution

Quick Start

bot = TinyBot(rpc_url, name="my bot")

bot.listen(event="AuctionKicked", handler=on_kick, ...)
bot.every(180, check_expired)
bot.cron("0 * * * *", hourly_check)

await bot.run()
import asyncio
import os
from tinybot import TinyBot, multicall, notify_group_chat

ERC20_ABI = [...]
STRATEGY_ABI = [...]

async def on_transfer(bot, log):
    print(f"{log.args.sender} -> {log.args.receiver}: {log.args.value}")
    await notify_group_chat(f"Transfer from {log.args.sender}")

async def check_and_tend(bot):
    strategy = bot.w3.eth.contract(address="0x...", abi=STRATEGY_ABI)
    needs_tend, _ = strategy.functions.tendTrigger().call()
    if needs_tend:
        tx_hash = bot.executor.execute(
            strategy.functions.tend(),
            gas_limit=5_000_000,
        )
        await notify_group_chat(f"Tend submitted: {tx_hash}")

async def main():
    bot = TinyBot(
        rpc_url=os.environ["RPC_URL"],
        name="my bot",
        private_key=os.environ.get("PRIVATE_KEY", ""),
    )

    bot.listen(
        event="Transfer",
        addresses=["0x..."],
        abi=ERC20_ABI,
        handler=on_transfer,
        poll_interval=180,
    )

    bot.every(3600, check_and_tend)

    await bot.run()

asyncio.run(main())

API

TinyBot(rpc_url, name="tinybot", private_key="")

Creates a bot instance.

  • bot.w3web3.Web3 instance
  • bot.stateState instance (see below)
  • bot.executorExecutor instance if private_key is provided, else None
  • bot.name — used in logs and Telegram startup message

On run(), sends a startup message to DEV_GROUP_CHAT_ID and prints a polling heartbeat every tick.


bot.listen(...) -> EventListener

Register an event listener.

bot.listen(
    event="AuctionKicked",   # event name (must exist in ABI)
    addresses=["0x..."],     # contracts to monitor
    abi=[...],               # ABI containing the event
    handler=on_kick,         # async fn(bot, log)
    name="kicks",            # defaults to handler.__name__ (optional)
    poll_interval=180,       # seconds between polls (default: 180)
    block_buffer=5,          # re-scan buffer in blocks (default: 5)
    notify_errors=True,      # send errors to Telegram (default: True)
)

The event signature is derived from the ABI at registration time. Raises ValueError if:

  • Event not found in ABI
  • Duplicate listener name
  • Empty addresses

bot.every(interval, handler, name="", notify_errors=True) -> PeriodicTask

Register a periodic task.

bot.every(3600, check_expired)

Handler signature: async fn(bot)


bot.cron(expression, handler, name="", notify_errors=True) -> CronTask

Register a cron-scheduled task. Uses standard cron expressions.

bot.cron("0 * * * *", notify_ending_soon)      # every hour at :00
bot.cron("*/30 * * * *", check_something)       # every 30 minutes

Handler signature: async fn(bot)


bot.get_listener(name) -> EventListener

Get a registered listener by name. Raises ValueError if not found.


bot.replay(name, from_block, to_block)

Replay historical events through a listener's handler. Useful for testing with real chain data.

await bot.replay("kicks", from_block=21000000, to_block=21000500)

bot.run(tick=10)

Start the polling loop. tick (default: 10s) is the inner loop sleep. Each listener and task fires at its own interval.


EventListener

Returned by bot.listen().

  • listener.add_address(address) — add a contract address at runtime
  • listener.remove_address(address) — remove a contract address at runtime

Both handle checksumming and dedup.


Executor

Available via bot.executor when private_key is provided.

bot = TinyBot(rpc_url, name="my bot", private_key=os.environ["PRIVATE_KEY"])

tx_hash = bot.executor.execute(
    contract.functions.tend(strategy_addr),
    gas_limit=0,              # default: 0 — auto-estimates with 1.5x buffer
    max_fee_gwei=100,         # default: 100
    max_priority_fee_gwei=0,  # default: 0
    simulate=True,            # default: True — dry-run via eth_call before sending
    wait=120,                 # default: 120 — seconds to wait for mining (0 = fire and forget)
)
  • executor.address — signer address
  • executor.balance — signer ETH balance in wei
  • executor.execute(call, ...) — sign and broadcast a transaction, returns tx hash hex string
  • gas_limit=0 (default) — auto-estimates gas with 1.5x buffer; pass a value to override
  • max_fee_gwei / max_priority_fee_gwei — accept floats (e.g. 0.1)
  • simulate=True (default) — runs call.call() first; reverts raise before the tx is sent
  • wait=120 (default) — wait up to N seconds for the tx to be mined; 0 for fire and forget

State

In-memory state, available via bot.state.

  • state.last_blockdict[str, int] mapping names to last processed block
  • state.active_itemslist of tracked items (e.g. address pairs)
  • state.add_item(*addrs) — add an item (deduped)
  • state.remove_item(item) — remove an item
  • state.is_processed(event_id) — check if event was processed
  • state.mark_processed(event_id) — mark event as processed (handled automatically for listeners)

multicall(w3, calls) -> list

Batch contract reads via Multicall3.

symbol, decimals = multicall(bot.w3, [
    token.functions.symbol(),
    token.functions.decimals(),
])

notify_group_chat(text, parse_mode="HTML", chat_id=GROUP_CHAT_ID)

Send a Telegram message. HTML parse mode by default.


event_id(log) -> str

Unique ID from a log (txHash:logIndex). Used internally for dedup, also available for custom event processing in periodic tasks.

Handler Signatures

# Event handler
async def on_event(bot: TinyBot, log) -> None: ...

# Task handler
async def my_task(bot: TinyBot) -> None: ...

Access bot.w3, bot.state, bot.executor, and bot.get_listener() from any handler.

Error Handling

Enabled by default. Exceptions are caught and sent to DEV_GROUP_CHAT_ID as [name] error message. The bot continues running. Set notify_errors=False to disable.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

tinybot_eth-0.6.0.tar.gz (10.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

tinybot_eth-0.6.0-py3-none-any.whl (9.6 kB view details)

Uploaded Python 3

File details

Details for the file tinybot_eth-0.6.0.tar.gz.

File metadata

  • Download URL: tinybot_eth-0.6.0.tar.gz
  • Upload date:
  • Size: 10.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for tinybot_eth-0.6.0.tar.gz
Algorithm Hash digest
SHA256 8be8bb5f26b578f6482e553ffb2c04d465e3dadb511fe95527249114163df3d9
MD5 1ea1a3958d2cbe43118f5a31ecbba1f6
BLAKE2b-256 85625700a848a21b8219d515e7ffa07bf4865339fed067b1040486c6a2ab38ca

See more details on using hashes here.

File details

Details for the file tinybot_eth-0.6.0-py3-none-any.whl.

File metadata

  • Download URL: tinybot_eth-0.6.0-py3-none-any.whl
  • Upload date:
  • Size: 9.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.12

File hashes

Hashes for tinybot_eth-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 ad269e8824e3aaf34f12e9aa03d1977e835274650ab7c0edac63d2b36b0363e1
MD5 498346aa73f7ce7bfb28ec9e30a9f1ee
BLAKE2b-256 f86fd1866b7e8029221d5ffa29ade595eef820c058ab3e8c953d17dcb2d433fd

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page