Create a New Bot

This is a step-by-step guide on creating a new bot and releasing it to be used with cHaOSneT. The creation of this bot will be through the Silverback SDK, which can be found at https://github.com/ApeWorX/silverback. You can use the hosted base image which can be used to build a docker container of the bot.

In this example, we will create an NFT bot to mint and distribute NFTs to active addresses in each block. The complete code for this bot can be found at https://github.com/ApeAcademy/Spamalot

File Structure

We will create a file structure for our bot to keep it organized.

{bot-family}/               # Contains all bots under "family" (Protocol, etc.)
    ├── {bot-name}.json     # Contains task definition for simulation
    └── {bot-name}/         # Contains all files for testing and building bot
        ├── ape-config.yaml # Ape configuration file (development only)
        ├── contracts       # Smart contracts directory (if needed)
        │   └── *.{vy,sol}  # Any mock contracts needed for bot operation
        ├── scripts         # Scripts directory (contains production script and data)
        │   └── bot.py      # Main script for running the bot (in container)
        └── tests           # Test suite (assumes `--network :mainnet-fork:foundry`)
            ├── conftest.py # Pytest configuration file (should set up `bot` fixture)
            └── test_*.py   # Test files to test bot functionality

Steps

  1. Initialize with Silverback

  2. Create Data Structure

  3. App Initialization

  4. New Block Event / Event Log

Step 1. Initiate with Silverback

To create a cHaOsNeT bot, you must use the Silverback SDK in conjunction with cHaOsNeT to continuously monitor and respond to on-chain events occuring in the Simulation. Instructions for installing the Silverback SDK can be found at https://github.com/ApeWorX/silverback.

We are going to be creating our NFT bot in a file named bot.py and initialize Silverback using:

from silverback import SilverbackApp

# App Initialization
app = SilverbackApp()

Step 2. Create a Data Structure and Constants

We will then need to create data structures and set constants for our bot, like block intervals, tracked addresses, and NFT contracts.

from typing import Set

from ape import chain, project
from ape.logging import logger
from taskiq import Context, TaskiqDepends

# Constants
BLOCK_INTERVAL = 100  # Adjust this to the desired block interval for NFT minting

# Data Structures
tracked_addresses: Set[str] = set()

# NFT Contract
NFT_CONTRACT = project.erc721  # Replace this with the correct path to your NFT contract found in the `contracts/` directory

Step 4. App Initialization

The SilverbackApp class handles state and configuration, so we need to initialize the state and deploy the contract.

@app.on__startup()
def initialize(context: Context = TaskiqDepends()):
    """
    Startup function to deploy the NFT contract and initialize the state.
    """
    logger.info("Deploying NFT contract")
    nft_contract = NFT_CONTRACT.deploy(sender=app.signer)  # Ensure you have the correct deploy method in your contract
    context.state.nft_contract = nft_contract
    context.state.tracked_addresses = tracked_addresses

Step 4. New Block Events/Event Logs

Then, we define the logic that we want to handle each new event detected by the client. This can be either block or event handlers. For our use, we want to track active addresses based on every n blocks (BLOCK_INTERVAl) and track it in tracked_addresses.

@app.on_(chain.blocks)
async def track_active_addresses(block, context: Context = TaskiqDepends()):
    """
    Handler that tracks active addresses from transactions in each block.
    """
    if block.number % BLOCK_INTERVAL == 0:
        context.state.tracked_addresses.clear()  # Reset the set every `n` blocks

    # Track active addresses
    transactions = block.transactions
    for tx in transactions:
        context.state.tracked_addresses.add(tx.sender)

Then we want to mint NFTs to those active addresses found.

@app.on_(chain.blocks)
async def mint_and_distribute_nfts(block, context: Context = TaskiqDepends()):
    """
    Handler that mints and distributes NFTs to active addresses every `n` blocks.
    """
    if block.number % BLOCK_INTERVAL == 0 and context.state.tracked_addresses:
        logger.info("Minting and distributing NFTs to active addresses")
        for address in context.state.tracked_addresses:
            try:
                # Adjust based on your contract's API
                context.state.nft_contract.mint(address, sender=app.signer)
                logger.info(f"NFT minted and sent to {address}")
            except Exception as e:
                logger.error(f"Error minting NFT for {address}: {e}")