#!/usr/bin/env python3 
# This script sends a bundle of two transactions which transfer ETH into a random account.
# For full instructions: https://lilithsecurity.substack.com/p/-mev-wednesdays-intro-to-flashbots


import os
import secrets
from pathlib import Path
from dotenv import load_dotenv
from flashbots import flashbot
from eth_account.account import Account
from eth_account.signers.local import LocalAccount
from web3 import Web3, HTTPProvider
from web3.exceptions import TransactionNotFound
from web3.types import TxParams


def env(key: str) -> str:
    load_dotenv(Path('.') / '.env')
    return os.getenv(key)


def random_account() -> LocalAccount:
    key = "0x" + secrets.token_hex(32)
    return Account.from_key(key)


def main():

    SENDER_KEY = env("SENDER_KEY")
    SIGNER_KEY = env("SIGNER_KEY")
    PROVIDER_URL = env("PROVIDER_URL")
    CHAIN_ID =  int(env("CHAIN_ID"))

    # 1. account to send the transfer and sign transactions
    sender: LocalAccount = Account.from_key(SENDER_KEY)

    # 2. account to receive the transfer
    receiverAddress: str = random_account().address

    # 3. account to sign bundles and flashbots reputation
    signer: LocalAccount = Account.from_key(SIGNER_KEY)
    w3 = Web3(HTTPProvider(PROVIDER_URL))
    flashbot(w3, signer, "https://relay-goerli.flashbots.net")

    print(f"✅ Sender address: {sender.address}")
    print(f"✅ Receiver address: {receiverAddress}")
    print(f"✅ Sender account balance: {Web3.fromWei(w3.eth.get_balance(sender.address), 'ether')} ETH")
    print(f"✅ Receiver account balance: {Web3.fromWei(w3.eth.get_balance(receiverAddress), 'ether')} ETH")

    # 4. bundle two EIP-1559
    nonce = w3.eth.get_transaction_count(sender.address)
    tx1: TxParams = {
        "to": receiverAddress,
        "value": Web3.toWei(0.005, "ether"),
        "gas": 21000,
        "maxFeePerGas": Web3.toWei(300, "gwei"),
        "maxPriorityFeePerGas": Web3.toWei(50, "gwei"),
        "nonce": nonce,
        "chainId": CHAIN_ID,
        "type": 2,
    }
    tx1_signed = sender.sign_transaction(tx1)

    tx2: TxParams = {
        "to": receiverAddress,
        "value": Web3.toWei(0.005, "ether"),
        "gas": 21000,
        "maxFeePerGas": Web3.toWei(300, "gwei"),
        "maxPriorityFeePerGas": Web3.toWei(50, "gwei"),
        "nonce": nonce + 1,
        "chainId": CHAIN_ID,
        "type": 2,
    }

    bundle = [
        {"signed_transaction": tx1_signed.rawTransaction},
        {"signer": sender, "transaction": tx2},
    ]

    # 5. Send bundle until it gets mined
    while True:
        block = w3.eth.block_number
        print(f"✨ Simulating on block {block}...")

        try:
            w3.flashbots.simulate(bundle, block)
            print("✅ Simulation successful!")
    
        except Exception as e:
            print(f"🚨 Simulation error: {e}")
            return

        print(f"Sending bundle targeting next block: {block+1}...")
        send_result = w3.flashbots.send_bundle(bundle, target_block_number=block + 1)
        send_result.wait()

        try:
            receipts = send_result.receipts()
            print(f"\n✅ Bundle was mined in block {receipts[0].blockNumber}\a")
            break

        except Exception as e:
            print(f"Bundle not found in block {block+1}: {e}")

    print(f"✅ Sender account balance: {Web3.fromWei(w3.eth.get_balance(sender.address), 'ether')} ETH")
    print(f"✅ Receiver account balance: {Web3.fromWei(w3.eth.get_balance(receiverAddress), 'ether')} ETH")


if __name__ == "__main__":
    main()