Metadata-Version: 2.4
Name: brawny
Version: 0.1.0
Summary: Block-driven Ethereum job/transaction execution framework
Author-email: yearn <dev@yearn.fi>
License: MIT
Project-URL: Homepage, https://github.com/yearn/brawny
Project-URL: Documentation, https://github.com/yearn/brawny#readme
Project-URL: Repository, https://github.com/yearn/brawny.git
Project-URL: Issues, https://github.com/yearn/brawny/issues
Keywords: ethereum,web3,blockchain,jobs,transactions
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: web3<8.0,>=7.0
Requires-Dist: click<9.0,>=8.1
Requires-Dist: pyyaml<7.0,>=6.0
Requires-Dist: python-dotenv<2.0,>=1.0
Requires-Dist: structlog<25.0,>=24.1
Requires-Dist: httpx<1.0,>=0.26
Requires-Dist: psycopg[binary]<4.0,>=3.1
Requires-Dist: psycopg-pool<4.0,>=3.2
Requires-Dist: rich<14.0,>=13.0
Requires-Dist: prometheus-client<1.0,>=0.20
Requires-Dist: prompt_toolkit<4.0,>=3.0
Requires-Dist: pygments<3.0,>=2.17
Provides-Extra: dev
Requires-Dist: pytest<9.0,>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == "dev"
Requires-Dist: pytest-cov<5.0,>=4.1; extra == "dev"
Requires-Dist: mypy<2.0,>=1.8; extra == "dev"
Requires-Dist: ruff<1.0,>=0.2; extra == "dev"
Requires-Dist: black<25.0,>=24.1; extra == "dev"
Requires-Dist: pre-commit<4.0,>=3.6; extra == "dev"
Provides-Extra: test
Requires-Dist: pytest<9.0,>=8.0; extra == "test"
Requires-Dist: pytest-asyncio<1.0,>=0.23; extra == "test"
Requires-Dist: pytest-cov<5.0,>=4.1; extra == "test"

# brawny

Block-driven Ethereum job and transaction execution framework, inspired by [eth-brownie](https://github.com/eth-brownie/brownie).

**Brownie-style ergonomics**: brawny mirrors Brownie's developer experience with familiar patterns—`accounts`, `Contract()`, `chain`, `history`, and an interactive console. If you've used Brownie, you'll feel right at home.

## Installation (Local Development)

```bash
# Clone and install the framework
git clone https://github.com/yearn/brawny.git
cd brawny
pip install -e .
```

## Quick Start

```bash
# Create a new keeper project
mkdir my-keeper && cd my-keeper
brawny init

# Install your project (brawny is already installed from above)
pip install -e .

# Configure
cp .env.example .env
# Edit .env: set RPC_URL and BRAWNY_KEYSTORE_PASSWORD_WORKER

# Import a signer key (will prompt for password)
brawny accounts import --name worker --private-key 0xYOUR_PRIVATE_KEY

# Run
brawny start
```

See `docs/quickstart.md` for a longer walkthrough.

## Project Structure

After `brawny init`, your project looks like:

```
my-keeper/
├── my_keeper/              # Your Python package
│   └── __init__.py
├── jobs/
│   ├── __init__.py
│   └── _examples.py        # Reference implementations (not registered)
├── interfaces/             # Place ABI JSON files here
├── monitoring/             # Prometheus + Grafana stack
│   ├── docker-compose.yml
│   └── grafana/...
├── pyproject.toml
├── config.yaml
├── .env.example
├── .gitignore
└── AGENTS.md               # AI agent guide for writing jobs
```

The `AGENTS.md` file contains a comprehensive guide for AI agents to generate correct, idiomatic jobs.

## Minimal Job Example

Create `jobs/harvester.py`:

```python
from brawny import Job, job, Contract, trigger, intent, block


@job(signer="worker")
class HarvestJob(Job):
    name = "Harvest Example"
    check_interval_blocks = 50
    vault_address = "0xYourVault"

    def check(self):
        vault = Contract(self.vault_address)
        pending = vault.pendingRewards()
        if pending > 1_000_000_000_000_000_000:
            return trigger(
                reason="Harvest pending rewards",
                data={"pending": pending},
                idempotency_parts=[block.number // 50],
            )
        return None

    def build_intent(self, trig):
        vault = Contract(self.vault_address)
        return intent(
            signer_address=self._signer_name,
            to_address=self.vault_address,
            data=vault.harvest.encode_input(),
            min_confirmations=2,
        )

    def alert_triggered(self, ctx):
        return f"Harvest triggered: {ctx.trigger.data['pending'] / 1e18:.4f}"

    def alert_confirmed(self, ctx):
        return f"Harvest confirmed: {ctx.tx.hash}"
```

## Docs

- `docs/quickstart.md`
- `docs/cli.md`
- `docs/job-lifecycle.md`
- `docs/alerts.md`
