Full Code of solidquant/whack-a-mole for AI

main 290840b7de88 cached
51 files
630.1 KB
145.7k tokens
100 symbols
1 requests
Download .txt
Showing preview only (654K chars total). Download the full file or copy to clipboard to get everything.
Repository: solidquant/whack-a-mole
Branch: main
Commit: 290840b7de88
Files: 51
Total size: 630.1 KB

Directory structure:
gitextract_xq82mp3m/

├── .gitignore
├── .gitmodules
├── README.md
├── abi/
│   ├── ERC20.json
│   ├── UniswapV2Pool.json
│   ├── UniswapV2Router2.json
│   ├── UniswapV3Pool.json
│   ├── UniswapV3Quoter2.json
│   ├── UniswapV3SwapRouter2.json
│   └── WETH.json
├── addresses/
│   ├── __init__.py
│   ├── arbitrum.py
│   ├── ethereum.py
│   └── polygon.py
├── configs.py
├── contracts/
│   ├── foundry.toml
│   ├── src/
│   │   ├── SimulatorV1.sol
│   │   ├── WhackAMoleBotV1.sol
│   │   ├── lib/
│   │   │   └── SafeTransfer.sol
│   │   └── protocols/
│   │       ├── IERC20.sol
│   │       ├── IWETH.sol
│   │       ├── curve/
│   │       │   └── ICurvePool.sol
│   │       └── uniswap/
│   │           ├── IQuoterV2.sol
│   │           ├── IUniswapV2Pair.sol
│   │           ├── IUniswapV2Router.sol
│   │           ├── IUniswapV3SwapRouter.sol
│   │           └── UniswapV2Library.sol
│   └── test/
│       └── WhackAMoleBotV1.t.sol
├── data/
│   ├── __init__.py
│   ├── cex.py
│   ├── cex_streams.py
│   ├── dex.py
│   ├── dex_streams.py
│   └── utils.py
├── examples/
│   └── dex.py
├── execution/
│   ├── WhackAMoleBotV1.json
│   ├── __init__.py
│   └── dex_order.py
├── external/
│   ├── __init__.py
│   ├── influxdb.py
│   └── telegram_bot.py
├── main.py
├── requirements.txt
├── simulation/
│   ├── SimulatorV1.json
│   ├── __init__.py
│   ├── online_simulator.py
│   ├── uniswap_v2.py
│   └── uniswap_v3.py
├── strategies/
│   └── dex_arb_base.py
└── tests/
    ├── test_WhackAMoleBotV1.py
    └── test_simulation.py

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Dotenv file
.env

# Mac
.DS_Store

# IDE
.idea/

# Cache / Env
venv/
__pycache__/

# Large files
*.pyc
*.zip
*.csv

# Compiler files
contracts/cache/
contracts/out/

# Ignores development broadcast logs
!/broadcast
/broadcast/*/31337/
/broadcast/**/dry-run/

# Docs
docs/

# Test scripts
scripts/

================================================
FILE: .gitmodules
================================================
[submodule "contracts/lib/forge-std"]
	path = contracts/lib/forge-std
	url = https://github.com/foundry-rs/forge-std
[submodule "contracts/lib/openzeppelin-contracts"]
	path = contracts/lib/openzeppelin-contracts
	url = https://github.com/OpenZeppelin/openzeppelin-contracts


================================================
FILE: README.md
================================================
# Whack-A-Mole

<p align="center">
    <img src = "https://github.com/solidquant/whack-a-mole/assets/134243834/841a91df-728b-489b-b4af-4af948c03c35" width="450px">
</p>

The image is of Dugtrio from Pokemon.

*And the banner is... Pokemon. I'm a Pokemon fan, what more can I say?*

---

### What the heck?

Whack-A-Mole is a CEX-DEX arbitrage bot written in Python.

Arbitrage strategies are like the global Whack-A-Mole game played in parallel.

Multiple players participate to find the mole that pops up, and jump to capture that opportunity.

Who knows who'll win...

What we know for certain is that you'll need a fast pair of eyes on the market at all times,
and an extra fast execution engine to capture the moment without latency.

Will our beloved Python be able to accomplish this? We'll see 😎

### Example Strategy #1: DEX arbitrage

The **main** branch is likely to go through a lot of changes, so to run an example that runs without breaking,
you should switch to the **examples/strategy/dex_arb_base** branch before running **main.py**. Run:

```
git checkout examples/strategy/dex_arb_base
```

### Example Strategy #2: CEX-DEX arbitrage

#### You said this is a CEX-DEX arbitrage bot, where the f is it?

I know...🥲 I'm still actively researching the CEX-DEX arbitrage space.

Everyone interested in the process can go visit my other repository which is actually a derivative of Whack-A-Mole:

https://github.com/solidquant/cex-dex-arb-research

This research template is an attempt to find alphas within the crypto space.

You can focus on DEX only arbs, CEX only arbs, and also CEX-DEX arbs using this template.

### 🛠 Recent Updates (2023.08.08):

1. **asyncio error**: added nest_asyncio
2. **.env error**: added some randomly generated sample private keys, addresses for people that want a complete testing environment
3. **requirements.txt**: web3, flashbots, websockets often times have conflicting versions. You sometimes need to delete their fields in the requirements.txt file and install them manually
4. **Telegram bot**: updated the code so that the bot runs without having to set the Telegram token in .env

Now with these issues resolved, you can easily test this bot out. It is set to debug, and the private keys are set at random,
so you don't need to worry! Just run:

```python
import asyncio
import nest_asyncio

from strategies.dex_arb_base import main


if __name__ == '__main__':
    nest_asyncio.apply()
    asyncio.run(main())
```

This code is in **main.py**.

```bash
python main.py
```

---

Check out my blog post describing in detail what this project attempts to do, and how you can use it.

[Go to blog 👉](https://medium.com/@solidquant/how-i-built-my-first-mev-arbitrage-bot-introducing-whack-a-mole-66d91657152e)

---

⚡️ For readers that want to talk about MEV and any other quant related stuff with people, please join my Discord! There’s currently no one on the Discord server, so it’s not too active yet, but I hope to meet new people there! 🌎🪐

https://discord.com/invite/e6KpjTQP98

================================================
FILE: abi/ERC20.json
================================================
[
    {
        "constant": true,
        "inputs": [],
        "name": "name",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_spender",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "approve",
        "outputs": [
            {
                "name": "",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "totalSupply",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_from",
                "type": "address"
            },
            {
                "name": "_to",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "transferFrom",
        "outputs": [
            {
                "name": "",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "decimals",
        "outputs": [
            {
                "name": "",
                "type": "uint8"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            }
        ],
        "name": "balanceOf",
        "outputs": [
            {
                "name": "balance",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "symbol",
        "outputs": [
            {
                "name": "",
                "type": "string"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "constant": false,
        "inputs": [
            {
                "name": "_to",
                "type": "address"
            },
            {
                "name": "_value",
                "type": "uint256"
            }
        ],
        "name": "transfer",
        "outputs": [
            {
                "name": "",
                "type": "bool"
            }
        ],
        "payable": false,
        "stateMutability": "nonpayable",
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [
            {
                "name": "_owner",
                "type": "address"
            },
            {
                "name": "_spender",
                "type": "address"
            }
        ],
        "name": "allowance",
        "outputs": [
            {
                "name": "",
                "type": "uint256"
            }
        ],
        "payable": false,
        "stateMutability": "view",
        "type": "function"
    },
    {
        "payable": true,
        "stateMutability": "payable",
        "type": "fallback"
    },
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "owner",
                "type": "address"
            },
            {
                "indexed": true,
                "name": "spender",
                "type": "address"
            },
            {
                "indexed": false,
                "name": "value",
                "type": "uint256"
            }
        ],
        "name": "Approval",
        "type": "event"
    },
    {
        "anonymous": false,
        "inputs": [
            {
                "indexed": true,
                "name": "from",
                "type": "address"
            },
            {
                "indexed": true,
                "name": "to",
                "type": "address"
            },
            {
                "indexed": false,
                "name": "value",
                "type": "uint256"
            }
        ],
        "name": "Transfer",
        "type": "event"
    }
]

================================================
FILE: abi/UniswapV2Pool.json
================================================
[{"inputs":[],"payable":false,"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1In","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount0Out","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1Out","type":"uint256"},{"indexed":true,"internalType":"address","name":"to","type":"address"}],"name":"Swap","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint112","name":"reserve0","type":"uint112"},{"indexed":false,"internalType":"uint112","name":"reserve1","type":"uint112"}],"name":"Sync","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"constant":true,"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"MINIMUM_LIQUIDITY","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"getReserves","outputs":[{"internalType":"uint112","name":"_reserve0","type":"uint112"},{"internalType":"uint112","name":"_reserve1","type":"uint112"},{"internalType":"uint32","name":"_blockTimestampLast","type":"uint32"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"_token0","type":"address"},{"internalType":"address","name":"_token1","type":"address"}],"name":"initialize","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"kLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"mint","outputs":[{"internalType":"uint256","name":"liquidity","type":"uint256"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"price0CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"price1CumulativeLast","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"}],"name":"skim","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"uint256","name":"amount0Out","type":"uint256"},{"internalType":"uint256","name":"amount1Out","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[],"name":"sync","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"}]

================================================
FILE: abi/UniswapV2Router2.json
================================================
[{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address","name":"_WETH","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"amountADesired","type":"uint256"},{"internalType":"uint256","name":"amountBDesired","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountTokenDesired","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"addLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"},{"internalType":"uint256","name":"liquidity","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountIn","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"reserveIn","type":"uint256"},{"internalType":"uint256","name":"reserveOut","type":"uint256"}],"name":"getAmountOut","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsIn","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"}],"name":"getAmountsOut","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"reserveA","type":"uint256"},{"internalType":"uint256","name":"reserveB","type":"uint256"}],"name":"quote","outputs":[{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidity","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETH","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"removeLiquidityETHSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermit","outputs":[{"internalType":"uint256","name":"amountToken","type":"uint256"},{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountTokenMin","type":"uint256"},{"internalType":"uint256","name":"amountETHMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityETHWithPermitSupportingFeeOnTransferTokens","outputs":[{"internalType":"uint256","name":"amountETH","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"tokenA","type":"address"},{"internalType":"address","name":"tokenB","type":"address"},{"internalType":"uint256","name":"liquidity","type":"uint256"},{"internalType":"uint256","name":"amountAMin","type":"uint256"},{"internalType":"uint256","name":"amountBMin","type":"uint256"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"removeLiquidityWithPermit","outputs":[{"internalType":"uint256","name":"amountA","type":"uint256"},{"internalType":"uint256","name":"amountB","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapETHForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactETHForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForETHSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapExactTokensForTokensSupportingFeeOnTransferTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactETH","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256[]","name":"amounts","type":"uint256[]"}],"stateMutability":"nonpayable","type":"function"},{"stateMutability":"payable","type":"receive"}]

================================================
FILE: abi/UniswapV3Pool.json
================================================
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Burn","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":false,"internalType":"address","name":"recipient","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"amount1","type":"uint128"}],"name":"Collect","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint128","name":"amount0","type":"uint128"},{"indexed":false,"internalType":"uint128","name":"amount1","type":"uint128"}],"name":"CollectProtocol","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"paid0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"paid1","type":"uint256"}],"name":"Flash","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint16","name":"observationCardinalityNextOld","type":"uint16"},{"indexed":false,"internalType":"uint16","name":"observationCardinalityNextNew","type":"uint16"}],"name":"IncreaseObservationCardinalityNext","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"indexed":false,"internalType":"int24","name":"tick","type":"int24"}],"name":"Initialize","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"int24","name":"tickLower","type":"int24"},{"indexed":true,"internalType":"int24","name":"tickUpper","type":"int24"},{"indexed":false,"internalType":"uint128","name":"amount","type":"uint128"},{"indexed":false,"internalType":"uint256","name":"amount0","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount1","type":"uint256"}],"name":"Mint","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint8","name":"feeProtocol0Old","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol1Old","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol0New","type":"uint8"},{"indexed":false,"internalType":"uint8","name":"feeProtocol1New","type":"uint8"}],"name":"SetFeeProtocol","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"recipient","type":"address"},{"indexed":false,"internalType":"int256","name":"amount0","type":"int256"},{"indexed":false,"internalType":"int256","name":"amount1","type":"int256"},{"indexed":false,"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"indexed":false,"internalType":"uint128","name":"liquidity","type":"uint128"},{"indexed":false,"internalType":"int24","name":"tick","type":"int24"}],"name":"Swap","type":"event"},{"inputs":[{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount","type":"uint128"}],"name":"burn","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount0Requested","type":"uint128"},{"internalType":"uint128","name":"amount1Requested","type":"uint128"}],"name":"collect","outputs":[{"internalType":"uint128","name":"amount0","type":"uint128"},{"internalType":"uint128","name":"amount1","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint128","name":"amount0Requested","type":"uint128"},{"internalType":"uint128","name":"amount1Requested","type":"uint128"}],"name":"collectProtocol","outputs":[{"internalType":"uint128","name":"amount0","type":"uint128"},{"internalType":"uint128","name":"amount1","type":"uint128"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"fee","outputs":[{"internalType":"uint24","name":"","type":"uint24"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeGrowthGlobal0X128","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"feeGrowthGlobal1X128","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"flash","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint16","name":"observationCardinalityNext","type":"uint16"}],"name":"increaseObservationCardinalityNext","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"liquidity","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"maxLiquidityPerTick","outputs":[{"internalType":"uint128","name":"","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint128","name":"amount","type":"uint128"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"mint","outputs":[{"internalType":"uint256","name":"amount0","type":"uint256"},{"internalType":"uint256","name":"amount1","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"observations","outputs":[{"internalType":"uint32","name":"blockTimestamp","type":"uint32"},{"internalType":"int56","name":"tickCumulative","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityCumulativeX128","type":"uint160"},{"internalType":"bool","name":"initialized","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32[]","name":"secondsAgos","type":"uint32[]"}],"name":"observe","outputs":[{"internalType":"int56[]","name":"tickCumulatives","type":"int56[]"},{"internalType":"uint160[]","name":"secondsPerLiquidityCumulativeX128s","type":"uint160[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"name":"positions","outputs":[{"internalType":"uint128","name":"liquidity","type":"uint128"},{"internalType":"uint256","name":"feeGrowthInside0LastX128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthInside1LastX128","type":"uint256"},{"internalType":"uint128","name":"tokensOwed0","type":"uint128"},{"internalType":"uint128","name":"tokensOwed1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"protocolFees","outputs":[{"internalType":"uint128","name":"token0","type":"uint128"},{"internalType":"uint128","name":"token1","type":"uint128"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint8","name":"feeProtocol0","type":"uint8"},{"internalType":"uint8","name":"feeProtocol1","type":"uint8"}],"name":"setFeeProtocol","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"slot0","outputs":[{"internalType":"uint160","name":"sqrtPriceX96","type":"uint160"},{"internalType":"int24","name":"tick","type":"int24"},{"internalType":"uint16","name":"observationIndex","type":"uint16"},{"internalType":"uint16","name":"observationCardinality","type":"uint16"},{"internalType":"uint16","name":"observationCardinalityNext","type":"uint16"},{"internalType":"uint8","name":"feeProtocol","type":"uint8"},{"internalType":"bool","name":"unlocked","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"}],"name":"snapshotCumulativesInside","outputs":[{"internalType":"int56","name":"tickCumulativeInside","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityInsideX128","type":"uint160"},{"internalType":"uint32","name":"secondsInside","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"bool","name":"zeroForOne","type":"bool"},{"internalType":"int256","name":"amountSpecified","type":"int256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"},{"internalType":"bytes","name":"data","type":"bytes"}],"name":"swap","outputs":[{"internalType":"int256","name":"amount0","type":"int256"},{"internalType":"int256","name":"amount1","type":"int256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int16","name":"","type":"int16"}],"name":"tickBitmap","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"tickSpacing","outputs":[{"internalType":"int24","name":"","type":"int24"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"int24","name":"","type":"int24"}],"name":"ticks","outputs":[{"internalType":"uint128","name":"liquidityGross","type":"uint128"},{"internalType":"int128","name":"liquidityNet","type":"int128"},{"internalType":"uint256","name":"feeGrowthOutside0X128","type":"uint256"},{"internalType":"uint256","name":"feeGrowthOutside1X128","type":"uint256"},{"internalType":"int56","name":"tickCumulativeOutside","type":"int56"},{"internalType":"uint160","name":"secondsPerLiquidityOutsideX128","type":"uint160"},{"internalType":"uint32","name":"secondsOutside","type":"uint32"},{"internalType":"bool","name":"initialized","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token0","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"token1","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"}]

================================================
FILE: abi/UniswapV3Quoter2.json
================================================
[{"inputs":[{"internalType":"address","name":"_factory","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint256","name":"amountIn","type":"uint256"}],"name":"quoteExactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint160[]","name":"sqrtPriceX96AfterList","type":"uint160[]"},{"internalType":"uint32[]","name":"initializedTicksCrossedList","type":"uint32[]"},{"internalType":"uint256","name":"gasEstimate","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IQuoterV2.QuoteExactInputSingleParams","name":"params","type":"tuple"}],"name":"quoteExactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceX96After","type":"uint160"},{"internalType":"uint32","name":"initializedTicksCrossed","type":"uint32"},{"internalType":"uint256","name":"gasEstimate","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint256","name":"amountOut","type":"uint256"}],"name":"quoteExactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint160[]","name":"sqrtPriceX96AfterList","type":"uint160[]"},{"internalType":"uint32[]","name":"initializedTicksCrossedList","type":"uint32[]"},{"internalType":"uint256","name":"gasEstimate","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IQuoterV2.QuoteExactOutputSingleParams","name":"params","type":"tuple"}],"name":"quoteExactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceX96After","type":"uint160"},{"internalType":"uint32","name":"initializedTicksCrossed","type":"uint32"},{"internalType":"uint256","name":"gasEstimate","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"path","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"view","type":"function"}]

================================================
FILE: abi/UniswapV3SwapRouter2.json
================================================
[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]

================================================
FILE: abi/WETH.json
================================================
[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"decimals","outputs":[{"name":"","type":"uint8"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"}],"name":"balanceOf","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"symbol","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"dst","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Deposit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Withdrawal","type":"event"}]

================================================
FILE: addresses/__init__.py
================================================
from addresses.arbitrum import TOKENS as ARBITRUM_TOKENS
from addresses.arbitrum import POOLS as ARBITRUM_POOLS

from addresses.ethereum import TOKENS as ETHEREUM_TOKENS
from addresses.ethereum import POOLS as ETHEREUM_POOLS
from addresses.ethereum import SIMULATION_HANDLERS as ETHEREUM_SIMULATION_HANDLERS
from addresses.ethereum import EXECUTION_HANDLERS as ETHEREUM_EXECUTION_HANDLERS

from addresses.polygon import TOKENS as POLYGON_TOKENS
from addresses.polygon import POOLS as POLYGON_POOLS


================================================
FILE: addresses/arbitrum.py
================================================
EXCHANGE = 'arbitrum'

TOKENS = {
    'ETH': ['0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', 18],
    'USDT': ['0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9', 6],
    'USDC': ['0xFF970A61A04b1cA14834A43f5dE4533eBDDB5CC8', 6],
    'BTC': ['0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f', 8],
    'ARB': ['0x912CE59144191C1204E64559FE8253a0e49E6548', 18],
}

columns = ['chain', 'exchange', 'version', 'name', 'address', 'fee', 'token0', 'token1']

POOLS = [
    ['uniswap', 3, 'ETH/USDC', '0xC31E54c7a869B9FcBEcc14363CF510d1c41fa443', 500, 'ETH', 'USDC'],
    ['uniswap', 3, 'BTC/ETH', '0x2f5e87C9312fa29aed5c179E456625D79015299c', 500, 'BTC', 'ETH'],
    ['uniswap', 3, 'ETH/USDT', '0x641C00A822e8b671738d32a431a4Fb6074E5c79d', 500, 'ETH', 'USDT'],
    ['uniswap', 3, 'USDT/USDC', '0x8c9D230D45d6CfeE39a6680Fb7CB7E8DE7Ea8E71', 100, 'USDT', 'USDC'],
]

POOLS = [dict(zip(columns, [EXCHANGE] + pool)) for pool in POOLS]


================================================
FILE: addresses/ethereum.py
================================================
EXCHANGE = 'ethereum'

TOKENS = {
    'ETH': ['0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 18],
    'USDT': ['0xdAC17F958D2ee523a2206206994597C13D831ec7', 6],
    'USDC': ['0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48', 6],
    'BTC': ['0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599', 8],
    'DAI': ['0x6B175474E89094C44Da98b954EedeAC495271d0F', 18],
    'PEPE': ['0x6982508145454Ce325dDbE47a25d4ec3d2311933', 18],
}

columns = ['chain', 'exchange', 'version', 'name', 'address', 'fee', 'token0', 'token1']

POOLS = [
    ['uniswap', 3, 'ETH/USDT', '0x11b815efB8f581194ae79006d24E0d814B7697F6', 500, 'ETH', 'USDT'],
    ['uniswap', 3, 'USDC/USDT', '0x3416cF6C708Da44DB2624D63ea0AAef7113527C6', 100, 'USDC', 'USDT'],
    ['uniswap', 3, 'BTC/ETH', '0x4585FE77225b41b697C938B018E2Ac67Ac5a20c0', 500, 'BTC', 'ETH'],
    ['uniswap', 3, 'DAI/USDC', '0x5777d92f208679DB4b9778590Fa3CAB3aC9e2168', 100, 'DAI', 'USDC'],
    ['uniswap', 3, 'PEPE/ETH', '0x11950d141EcB863F01007AdD7D1A342041227b58', 3000, 'PEPE', 'ETH'],

    ['uniswap', 2, 'ETH/USDT', '0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852', 3000, 'ETH', 'USDT'],
    ['uniswap', 2, 'USDC/ETH', '0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc', 3000, 'USDC', 'ETH'],
    ['uniswap', 2, 'DAI/USDC', '0xAE461cA67B15dc8dc81CE7615e0320dA1A9aB8D5', 3000, 'DAI', 'USDC'],
    ['uniswap', 2, 'PEPE/ETH', '0xA43fe16908251ee70EF74718545e4FE6C5cCEc9f', 3000, 'PEPE', 'ETH'],

    ['sushiswap', 3, 'ETH/USDT', '0x72c2178E082feDB13246877B5aA42ebcE1b72218', 500, 'ETH', 'USDT'],
    ['sushiswap', 3, 'USDC/ETH', '0x35644Fb61aFBc458bf92B15AdD6ABc1996Be5014', 500, 'USDC', 'ETH'],
    ['sushiswap', 3, 'BTC/ETH', '0x801CCFae9d2C77893B545E8D0E4637C055CD26cB', 500, 'BTC', 'ETH'],
    ['sushiswap', 3, 'USDC/USDT', '0xfA6e8E97ecECDC36302eCA534f63439b1E79487B', 100, 'USDC', 'USDT'],
    ['sushiswap', 3, 'DAI/USDC', '0x31ac258B911Af9a0d2669ebDFC4e39D92e96b772', 100, 'DAI', 'USDC'],

    ['sushiswap', 2, 'BTC/ETH', '0xCEfF51756c56CeFFCA006cD410B03FFC46dd3a58', 3000, 'BTC', 'ETH'],
    ['sushiswap', 2, 'ETH/USDT', '0x06da0fd433C1A5d7a4faa01111c044910A184553', 3000, 'ETH', 'USDT'],
    ['sushiswap', 2, 'USDC/ETH', '0x397FF1542f962076d0BFE58eA045FfA2d347ACa0', 3000, 'USDC', 'ETH'],
    ['sushiswap', 2, 'DAI/ETH', '0xC3D03e4F041Fd4cD388c549Ee2A29a9E5075882f', 3000, 'DAI', 'ETH'],
]

POOLS = [dict(zip(columns, [EXCHANGE] + pool)) for pool in POOLS]

SIMULATION_HANDLERS = {
    'uniswap_v2': '0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f',    # factory
    'sushiswap_v2': '0xC0AEe478e3658e2610c5F7A4A2E1777cE9e4f2Ac',  # factory
    'uniswap_v3': '0x61fFE014bA17989E743c5F6cB21bF9697530B21e',    # quoterv2
    'sushiswap_v3': '0x64e8802FE490fa7cc61d3463958199161Bb608A7',  # quoterv2
}

EXECUTION_HANDLERS = {
    'uniswap_v2': '0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D',    # router2
    'sushiswap_v2': '0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F',  # router2
    'uniswap_v3': '0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45',    # swap router2
    'sushiswap_v3': '',  # TODO: Sushiswap V3 doesn't have SwapRouter2, needs to use pool contract
}

================================================
FILE: addresses/polygon.py
================================================
EXCHANGE = 'polygon'

TOKENS = {
    'ETH': ['0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619', 18],
    'USDT': ['0xc2132D05D31c914a87C6611C10748AEb04B58e8F', 6],
    'USDC': ['0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174', 6],
    'BTC': ['0x1BFD67037B42Cf73acF2047067bd4F2C47D9BfD6', 8],
    'MATIC': ['0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270', 18],
}

columns = ['chain', 'exchange', 'version', 'name', 'address', 'fee', 'token0', 'token1']

POOLS = [
    ['uniswap', 3, 'USDC/ETH', '0x45dDa9cb7c25131DF268515131f647d726f50608', 500, 'USDC', 'ETH'],
    ['uniswap', 3, 'BTC/ETH', '0x50eaEDB835021E4A108B7290636d62E9765cc6d7', 500, 'BTC', 'ETH'],
    ['uniswap', 3, 'USDC/USDT', '0xDaC8A8E6DBf8c690ec6815e0fF03491B2770255D', 100, 'USDC', 'USDT'],
    ['uniswap', 3, 'MATIC/USDC', '0xA374094527e1673A86dE625aa59517c5dE346d32', 500, 'MATIC', 'USDC'],
]

POOLS = [dict(zip(columns, [EXCHANGE] + pool)) for pool in POOLS]


================================================
FILE: configs.py
================================================
import os
from dotenv import load_dotenv

from addresses import (
    ETHEREUM_TOKENS,
    POLYGON_TOKENS,
    ARBITRUM_TOKENS,

    ETHEREUM_POOLS,
    POLYGON_POOLS,
    ARBITRUM_POOLS,

    ETHEREUM_SIMULATION_HANDLERS,
    ETHEREUM_EXECUTION_HANDLERS,
)

load_dotenv(override=True)

RPC_ENDPOINTS = {
    'ethereum': os.getenv('ETHEREUM_HTTP_RPC_URL'),
    'polygon': os.getenv('POLYGON_HTTP_RPC_URL'),
    'arbitrum': os.getenv('ARBITRUM_HTTP_RPC_URL'),
}

WS_ENDPOINTS = {
    'ethereum': os.getenv('ETHEREUM_WS_RPC_URL'),
    'polygon': os.getenv('POLYGON_WS_RPC_URL'),
    'arbitrum': os.getenv('ARBITRUM_WS_RPC_URL'),
}

TOKENS = {
    'ethereum': ETHEREUM_TOKENS,
    'polygon': POLYGON_TOKENS,
    'arbitrum': ARBITRUM_TOKENS,
}

POOLS = ETHEREUM_POOLS + POLYGON_POOLS + ARBITRUM_POOLS

SIMULATION_HANDLERS = {
    'ethereum': ETHEREUM_SIMULATION_HANDLERS,
}

EXECUTION_HANDLERS = {
    'ethereum': ETHEREUM_EXECUTION_HANDLERS,
}


================================================
FILE: contracts/foundry.toml
================================================
[profile.default]
src = "src"
out = "out"
libs = ["lib"]

# See more config options https://github.com/foundry-rs/foundry/tree/master/config

================================================
FILE: contracts/src/SimulatorV1.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "openzeppelin-contracts/contracts/utils/math/SafeMath.sol";

import "./protocols/uniswap/UniswapV2Library.sol";
import "./protocols/uniswap/IQuoterV2.sol";
import "./protocols/curve/ICurvePool.sol";

// Deployment gas: 610,194
// With optimizations: 773,502
contract SimulatorV1 {
    using SafeMath for uint256;

    struct SwapParams {
        uint8 protocol; // 0 (UniswapV2), 1 (UniswapV3), 2 (Curve Finance)
        address handler; // UniswapV2: Factory, UniswapV3: Quoter, Curve: Pool
        address tokenIn;
        address tokenOut;
        uint24 fee; // only used in Uniswap V3
        uint256 amount; // amount in (1 USDC = 1,000,000 / 1 MATIC = 1 * 10 ** 18)
    }

    constructor() {}

    function simulateSwapIn(
        SwapParams[] calldata paramsArray
    ) public returns (uint256) {
        uint256 amountOut;
        uint256 paramsArrayLength = paramsArray.length;

        for (uint256 i; i < paramsArrayLength; ) {
            SwapParams memory params = paramsArray[i];

            if (amountOut == 0) {
                amountOut = params.amount;
            } else {
                params.amount = amountOut;
            }

            if (params.protocol == 0) {
                amountOut = simulateUniswapV2SwapIn(params);
            } else if (params.protocol == 1) {
                amountOut = simulateUniswapV3SwapIn(params);
            }

            unchecked {
                ++i;
            }
        }

        return amountOut;
    }

    function simulateUniswapV2SwapIn(
        SwapParams memory params
    ) public view returns (uint256 amountOut) {
        (uint reserveIn, uint reserveOut) = UniswapV2Library.getReserves(
            params.handler,
            params.tokenIn,
            params.tokenOut
        );
        amountOut = UniswapV2Library.getAmountOut(
            params.amount,
            reserveIn,
            reserveOut
        );
    }

    function simulateUniswapV3SwapIn(
        SwapParams memory params
    ) public returns (uint256 amountOut) {
        IQuoterV2 quoter = IQuoterV2(params.handler);
        IQuoterV2.QuoteExactInputSingleParams memory quoterParams;
        quoterParams.tokenIn = params.tokenIn;
        quoterParams.tokenOut = params.tokenOut;
        quoterParams.amountIn = params.amount;
        quoterParams.fee = params.fee;
        quoterParams.sqrtPriceLimitX96 = 0;
        (amountOut, , , ) = quoter.quoteExactInputSingle(quoterParams);
    }

    function simulateCurveSwapIn(
        SwapParams memory params
    ) public returns (uint256 amountOut) {
        ICurvePool pool = ICurvePool(params.handler);

        int128 i;
        int128 j;

        int128 coinIdx;

        while (i == j) {
            address coin = pool.coins(coinIdx);

            if (coin == params.tokenIn) {
                i = coinIdx;
            } else if (coin == params.tokenOut) {
                j = coinIdx;
            }

            if (i != j) {
                break;
            }

            unchecked {
                ++coinIdx;
            }
        }

        amountOut = pool.get_dy(i, j, params.amount);
    }
}


================================================
FILE: contracts/src/WhackAMoleBotV1.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "openzeppelin-contracts/contracts/utils/math/SafeMath.sol";
import "openzeppelin-contracts/contracts/token/erc20/IERC20.sol";
import "openzeppelin-contracts/contracts/token/erc20/utils/SafeERC20.sol";

import "./protocols/uniswap/IUniswapV2Router.sol";
import "./protocols/uniswap/IUniswapV3SwapRouter.sol";

// Deployment gas: 1,004,388
contract WhackAMoleBotV1 {
    using SafeERC20 for IERC20;

    address internal immutable owner;

    struct SwapParams {
        uint8 protocol;
        address handler;
        address tokenIn;
        address tokenOut;
        uint24 fee;
        uint256 amount;
    }

    error Unauthorized();
    error TradeFailed();

    constructor() {
        owner = msg.sender;
    }

    modifier onlyOwner() {
        _checkOwner();
        _;
    }

    function _checkOwner() internal view virtual {
        if (msg.sender != owner) {
            revert Unauthorized();
        }
    }

    function recoverTokens(address[] calldata tokens) public payable onlyOwner {
        uint length = tokens.length;

        for (uint i; i < length; ) {
            address token = tokens[i];
            IERC20(token).safeTransfer(
                msg.sender,
                IERC20(token).balanceOf(address(this))
            );

            unchecked {
                ++i;
            }
        }
    }

    // 2 tokens, 2 protocols: 142,582 gas
    function approveHandlers(
        address[] calldata tokens,
        address[] calldata protocols
    ) public payable {
        // Used to allow Routers from Uniswap V2, Uniswap V3, etc.
        // the access to tokens held by this contract
        uint maxInt = type(uint256).max;

        uint tokensLength = tokens.length;
        uint protocolsLength = protocols.length;

        for (uint i; i < tokensLength; ) {
            IERC20 token = IERC20(tokens[i]);
            for (uint j; j < protocolsLength; ) {
                address protocol = protocols[j];
                token.safeApprove(protocol, maxInt);

                unchecked {
                    ++j;
                }
            }

            unchecked {
                ++i;
            }
        }
    }

    function whack(
        SwapParams[] calldata paramsArray,
        uint256 minAmountOut
    ) public payable onlyOwner returns (uint256) {
        uint256 amountOut;
        uint256 paramsArrayLength = paramsArray.length;

        for (uint256 i; i < paramsArrayLength; ) {
            SwapParams memory params = paramsArray[i];

            if (amountOut == 0) {
                amountOut = params.amount;
            } else {
                params.amount = amountOut;
            }

            if (params.protocol == 0) {
                amountOut = uniswapV2Swap(params);
            } else if (params.protocol == 1) {
                amountOut = uniswapV3Swap(params);
            }

            unchecked {
                ++i;
            }
        }

        if (amountOut < minAmountOut) {
            revert TradeFailed();
        }

        return amountOut;
    }

    function uniswapV2Swap(
        SwapParams memory params
    ) internal returns (uint256 amountOut) {
        address[] memory path;
        path = new address[](2);
        path[0] = params.tokenIn;
        path[1] = params.tokenOut;

        uint[] memory amounts = IUniswapV2Router(params.handler)
            .swapExactTokensForTokens(
                params.amount,
                0,
                path,
                address(this),
                block.timestamp
            );

        return amounts[1];
    }

    function uniswapV3Swap(
        SwapParams memory params
    ) internal returns (uint256 amountOut) {
        // Sushiswap V3 doesn't have SwapRouter, needs to reimplement this to use Pools
        ISwapRouter.ExactInputSingleParams memory singleParams = ISwapRouter
            .ExactInputSingleParams({
                tokenIn: params.tokenIn,
                tokenOut: params.tokenOut,
                fee: params.fee,
                recipient: address(this),
                deadline: block.timestamp,
                amountIn: params.amount,
                amountOutMinimum: 0,
                sqrtPriceLimitX96: 0
            });

        amountOut = ISwapRouter(params.handler).exactInputSingle(singleParams);
    }
}


================================================
FILE: contracts/src/lib/SafeTransfer.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "../protocols/IERC20.sol";

library SafeTransfer {
    function safeTransferFrom(
        IERC20 token,
        address from,
        address to,
        uint256 value
    ) internal {
        (bool s, ) = address(token).call(
            abi.encodeWithSelector(
                IERC20.transferFrom.selector,
                from,
                to,
                value
            )
        );
        require(s, "safeTransferFrom failed");
    }

    function safeTransfer(IERC20 token, address to, uint256 value) internal {
        (bool s, ) = address(token).call(
            abi.encodeWithSelector(IERC20.transfer.selector, to, value)
        );
        require(s, "safeTransfer failed");
    }

    function safeApprove(IERC20 token, address to, uint256 value) internal {
        (bool s, ) = address(token).call(
            abi.encodeWithSelector(IERC20.approve.selector, to, value)
        );
        require(s, "safeApprove failed");
    }

    function safeTransferETH(address to, uint256 value) internal {
        (bool s, ) = to.call{value: value}(new bytes(0));
        require(s, "safeTransferETH failed");
    }
}


================================================
FILE: contracts/src/protocols/IERC20.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

interface IERC20 {
    function totalSupply() external view returns (uint);

    function balanceOf(address account) external view returns (uint);

    function transfer(address recipient, uint amount) external returns (bool);

    function allowance(
        address owner,
        address spender
    ) external view returns (uint);

    function approve(address spender, uint amount) external returns (bool);

    function transferFrom(
        address sender,
        address recipient,
        uint amount
    ) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint value);
    event Approval(address indexed owner, address indexed spender, uint value);
}


================================================
FILE: contracts/src/protocols/IWETH.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "./IERC20.sol";

interface IWETH is IERC20 {
    function deposit() external payable;

    function withdraw(uint amount) external;
}


================================================
FILE: contracts/src/protocols/curve/ICurvePool.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

interface ICurvePool {
    function get_virtual_price() external returns (uint256 out);

    function add_liquidity(
        uint256[2] calldata amounts,
        uint256 deadline
    ) external;

    function get_dy(
        int128 i,
        int128 j,
        uint256 dx
    ) external returns (uint256 out);

    function get_dy_underlying(
        int128 i,
        int128 j,
        uint256 dx
    ) external returns (uint256 out);

    function exchange(int128 i, int128 j, uint256 dx, uint256 min_dy) external;

    function exchange(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy,
        uint256 deadline
    ) external;

    function exchange_underlying(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy
    ) external;

    function exchange_underlying(
        int128 i,
        int128 j,
        uint256 dx,
        uint256 min_dy,
        uint256 deadline
    ) external;

    function remove_liquidity(
        uint256 _amount,
        uint256 deadline,
        uint256[2] calldata min_amounts
    ) external;

    function remove_liquidity_imbalance(
        uint256[2] calldata amounts,
        uint256 deadline
    ) external;

    function commit_new_parameters(
        int128 amplification,
        int128 new_fee,
        int128 new_admin_fee
    ) external;

    function apply_new_parameters() external;

    function revert_new_parameters() external;

    function commit_transfer_ownership(address _owner) external;

    function apply_transfer_ownership() external;

    function revert_transfer_ownership() external;

    function withdraw_admin_fees() external;

    function coins(int128 arg0) external returns (address out);

    function underlying_coins(int128 arg0) external returns (address out);

    function balances(int128 arg0) external returns (uint256 out);

    function A() external returns (int128 out);

    function fee() external returns (int128 out);

    function admin_fee() external returns (int128 out);

    function owner() external returns (address out);

    function admin_actions_deadline() external returns (uint256 out);

    function transfer_ownership_deadline() external returns (uint256 out);

    function future_A() external returns (int128 out);

    function future_fee() external returns (int128 out);

    function future_admin_fee() external returns (int128 out);

    function future_owner() external returns (address out);
}


================================================
FILE: contracts/src/protocols/uniswap/IQuoterV2.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;
pragma abicoder v2;

/// @title QuoterV2 Interface
/// @notice Supports quoting the calculated amounts from exact input or exact output swaps.
/// @notice For each pool also tells you the number of initialized ticks crossed and the sqrt price of the pool after the swap.
/// @dev These functions are not marked view because they rely on calling non-view functions and reverting
/// to compute the result. They are also not gas efficient and should not be called on-chain.
interface IQuoterV2 {
    /// @notice Returns the amount out received for a given exact input swap without executing the swap
    /// @param path The path of the swap, i.e. each token pair and the pool fee
    /// @param amountIn The amount of the first token to swap
    /// @return amountOut The amount of the last token that would be received
    /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
    /// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
    /// @return gasEstimate The estimate of the gas that the swap consumes
    function quoteExactInput(bytes memory path, uint256 amountIn)
        external
        returns (
            uint256 amountOut,
            uint160[] memory sqrtPriceX96AfterList,
            uint32[] memory initializedTicksCrossedList,
            uint256 gasEstimate
        );

    struct QuoteExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint256 amountIn;
        uint24 fee;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Returns the amount out received for a given exact input but for a swap of a single pool
    /// @param params The params for the quote, encoded as `QuoteExactInputSingleParams`
    /// tokenIn The token being swapped in
    /// tokenOut The token being swapped out
    /// fee The fee of the token pool to consider for the pair
    /// amountIn The desired input amount
    /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
    /// @return amountOut The amount of `tokenOut` that would be received
    /// @return sqrtPriceX96After The sqrt price of the pool after the swap
    /// @return initializedTicksCrossed The number of initialized ticks that the swap crossed
    /// @return gasEstimate The estimate of the gas that the swap consumes
    function quoteExactInputSingle(QuoteExactInputSingleParams memory params)
        external
        returns (
            uint256 amountOut,
            uint160 sqrtPriceX96After,
            uint32 initializedTicksCrossed,
            uint256 gasEstimate
        );

    /// @notice Returns the amount in required for a given exact output swap without executing the swap
    /// @param path The path of the swap, i.e. each token pair and the pool fee. Path must be provided in reverse order
    /// @param amountOut The amount of the last token to receive
    /// @return amountIn The amount of first token required to be paid
    /// @return sqrtPriceX96AfterList List of the sqrt price after the swap for each pool in the path
    /// @return initializedTicksCrossedList List of the initialized ticks that the swap crossed for each pool in the path
    /// @return gasEstimate The estimate of the gas that the swap consumes
    function quoteExactOutput(bytes memory path, uint256 amountOut)
        external
        returns (
            uint256 amountIn,
            uint160[] memory sqrtPriceX96AfterList,
            uint32[] memory initializedTicksCrossedList,
            uint256 gasEstimate
        );

    struct QuoteExactOutputSingleParams {
        address tokenIn;
        address tokenOut;
        uint256 amount;
        uint24 fee;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Returns the amount in required to receive the given exact output amount but for a swap of a single pool
    /// @param params The params for the quote, encoded as `QuoteExactOutputSingleParams`
    /// tokenIn The token being swapped in
    /// tokenOut The token being swapped out
    /// fee The fee of the token pool to consider for the pair
    /// amountOut The desired output amount
    /// sqrtPriceLimitX96 The price limit of the pool that cannot be exceeded by the swap
    /// @return amountIn The amount required as the input for the swap in order to receive `amountOut`
    /// @return sqrtPriceX96After The sqrt price of the pool after the swap
    /// @return initializedTicksCrossed The number of initialized ticks that the swap crossed
    /// @return gasEstimate The estimate of the gas that the swap consumes
    function quoteExactOutputSingle(QuoteExactOutputSingleParams memory params)
        external
        returns (
            uint256 amountIn,
            uint160 sqrtPriceX96After,
            uint32 initializedTicksCrossed,
            uint256 gasEstimate
        );
}


================================================
FILE: contracts/src/protocols/uniswap/IUniswapV2Pair.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

interface IUniswapV2Pair {
    event Approval(
        address indexed owner,
        address indexed spender,
        uint256 value
    );
    event Transfer(address indexed from, address indexed to, uint256 value);

    function name() external pure returns (string memory);

    function symbol() external pure returns (string memory);

    function decimals() external pure returns (uint8);

    function totalSupply() external view returns (uint256);

    function balanceOf(address owner) external view returns (uint256);

    function allowance(address owner, address spender)
        external
        view
        returns (uint256);

    function approve(address spender, uint256 value) external returns (bool);

    function transfer(address to, uint256 value) external returns (bool);

    function transferFrom(
        address from,
        address to,
        uint256 value
    ) external returns (bool);

    function DOMAIN_SEPARATOR() external view returns (bytes32);

    function PERMIT_TYPEHASH() external pure returns (bytes32);

    function nonces(address owner) external view returns (uint256);

    function permit(
        address owner,
        address spender,
        uint256 value,
        uint256 deadline,
        uint8 v,
        bytes32 r,
        bytes32 s
    ) external;

    event Mint(address indexed sender, uint256 amount0, uint256 amount1);
    event Burn(
        address indexed sender,
        uint256 amount0,
        uint256 amount1,
        address indexed to
    );
    event Swap(
        address indexed sender,
        uint256 amount0In,
        uint256 amount1In,
        uint256 amount0Out,
        uint256 amount1Out,
        address indexed to
    );
    event Sync(uint112 reserve0, uint112 reserve1);

    function MINIMUM_LIQUIDITY() external pure returns (uint256);

    function factory() external view returns (address);

    function token0() external view returns (address);

    function token1() external view returns (address);

    function getReserves()
        external
        view
        returns (
            uint112 reserve0,
            uint112 reserve1,
            uint32 blockTimestampLast
        );

    function price0CumulativeLast() external view returns (uint256);

    function price1CumulativeLast() external view returns (uint256);

    function kLast() external view returns (uint256);

    function mint(address to) external returns (uint256 liquidity);

    function burn(address to)
        external
        returns (uint256 amount0, uint256 amount1);

    function swap(
        uint256 amount0Out,
        uint256 amount1Out,
        address to,
        bytes calldata data
    ) external;

    function skim(address to) external;

    function sync() external;

    function initialize(address, address) external;
}


================================================
FILE: contracts/src/protocols/uniswap/IUniswapV2Router.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

interface IUniswapV2Router {
    function swapExactTokensForTokens(
        uint amountIn,
        uint amountOutMin,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);

    function swapTokensForExactTokens(
        uint amountOut,
        uint amountInMax,
        address[] calldata path,
        address to,
        uint deadline
    ) external returns (uint[] memory amounts);
}


================================================
FILE: contracts/src/protocols/uniswap/IUniswapV3SwapRouter.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

interface ISwapRouter {
    struct ExactInputSingleParams {
        address tokenIn;
        address tokenOut;
        uint24 fee;
        address recipient;
        uint deadline;
        uint amountIn;
        uint amountOutMinimum;
        uint160 sqrtPriceLimitX96;
    }

    /// @notice Swaps amountIn of one token for as much as possible of another token
    /// @param params The parameters necessary for the swap, encoded as ExactInputSingleParams in calldata
    /// @return amountOut The amount of the received token
    function exactInputSingle(
        ExactInputSingleParams calldata params
    ) external payable returns (uint amountOut);

    struct ExactInputParams {
        bytes path;
        address recipient;
        uint deadline;
        uint amountIn;
        uint amountOutMinimum;
    }

    /// @notice Swaps amountIn of one token for as much as possible of another along the specified path
    /// @param params The parameters necessary for the multi-hop swap, encoded as ExactInputParams in calldata
    /// @return amountOut The amount of the received token
    function exactInput(
        ExactInputParams calldata params
    ) external payable returns (uint amountOut);
}


================================================
FILE: contracts/src/protocols/uniswap/UniswapV2Library.sol
================================================
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.9;

import "./IUniswapV2Pair.sol";

import "openzeppelin-contracts/contracts/utils/math/SafeMath.sol";

library UniswapV2Library {
    using SafeMath for uint256;

    // returns sorted token addresses, used to handle return values from pairs sorted in this order
    function sortTokens(address tokenA, address tokenB)
        internal
        pure
        returns (address token0, address token1)
    {
        require(tokenA != tokenB, "UniswapV2Library: IDENTICAL_ADDRESSES");
        (token0, token1) = tokenA < tokenB
            ? (tokenA, tokenB)
            : (tokenB, tokenA);
        require(token0 != address(0), "UniswapV2Library: ZERO_ADDRESS");
    }

    // calculates the CREATE2 address for a pair without making any external calls
    function pairFor(
        address factory,
        address tokenA,
        address tokenB
    ) internal pure returns (address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);


            bytes32 initCodeHash
         = 0x96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f;

        bytes32 salt = keccak256(abi.encodePacked(token0, token1));

        pair = address(
            uint160(
                uint256(
                    keccak256(
                        abi.encodePacked(
                            bytes1(0xff),
                            factory,
                            salt,
                            initCodeHash
                        )
                    )
                )
            )
        );

        return pair;
    }

    // fetches and sorts the reserves for a pair
    function getReserves(
        address factory,
        address tokenA,
        address tokenB
    ) internal view returns (uint256 reserveA, uint256 reserveB) {
        (address token0, ) = sortTokens(tokenA, tokenB);
        (uint256 reserve0, uint256 reserve1, ) = IUniswapV2Pair(
            pairFor(factory, tokenA, tokenB)
        )
            .getReserves();
        (reserveA, reserveB) = tokenA == token0
            ? (reserve0, reserve1)
            : (reserve1, reserve0);
    }

    // given an input amount of an asset and pair reserves, returns the maximum output amount of the other asset
    function getAmountOut(
        uint256 amountIn,
        uint256 reserveIn,
        uint256 reserveOut
    ) internal pure returns (uint256 amountOut) {
        require(amountIn > 0, "UniswapV2Library: INSUFFICIENT_INPUT_AMOUNT");
        require(
            reserveIn > 0 && reserveOut > 0,
            "UniswapV2Library: INSUFFICIENT_LIQUIDITY"
        );
        uint256 amountInWithFee = amountIn.mul(997);
        uint256 numerator = amountInWithFee.mul(reserveOut);
        uint256 denominator = reserveIn.mul(1000).add(amountInWithFee);
        amountOut = numerator / denominator;
    }

    // given an output amount of an asset and pair reserves, returns a required input amount of the other asset
    function getAmountIn(
        uint256 amountOut,
        uint256 reserveIn,
        uint256 reserveOut
    ) internal pure returns (uint256 amountIn) {
        require(amountOut > 0, "UniswapV2Library: INSUFFICIENT_OUTPUT_AMOUNT");
        require(
            reserveIn > 0 && reserveOut > 0,
            "UniswapV2Library: INSUFFICIENT_LIQUIDITY"
        );
        uint256 numerator = reserveIn.mul(amountOut).mul(1000);
        uint256 denominator = reserveOut.sub(amountOut).mul(997);
        amountIn = (numerator / denominator).add(1);
    }

    // performs chained getAmountOut calculations on any number of pairs
    function getAmountsOut(
        address factory,
        uint256 amountIn,
        address[] memory path
    ) internal view returns (uint256[] memory amounts) {
        require(path.length >= 2, "UniswapV2Library: INVALID_PATH");
        amounts = new uint256[](path.length);
        amounts[0] = amountIn;
        for (uint256 i; i < path.length - 1; i++) {
            (uint256 reserveIn, uint256 reserveOut) = getReserves(
                factory,
                path[i],
                path[i + 1]
            );
            amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
        }
    }

    // performs chained getAmountIn calculations on any number of pairs
    function getAmountsIn(
        address factory,
        uint256 amountOut,
        address[] memory path
    ) internal view returns (uint256[] memory amounts) {
        require(path.length >= 2, "UniswapV2Library: INVALID_PATH");
        amounts = new uint256[](path.length);
        amounts[amounts.length - 1] = amountOut;
        for (uint256 i = path.length - 1; i > 0; i--) {
            (uint256 reserveIn, uint256 reserveOut) = getReserves(
                factory,
                path[i - 1],
                path[i]
            );
            amounts[i - 1] = getAmountIn(amounts[i], reserveIn, reserveOut);
        }
    }
}


================================================
FILE: contracts/test/WhackAMoleBotV1.t.sol
================================================
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.9;

import "forge-std/Test.sol";
import "../src/WhackAMoleBotV1.sol";
import "../src/SimulatorV1.sol";

contract FoundryDemoTest is Test {
    WhackAMoleBotV1 bot;
    SimulatorV1 simulator;

    uint256 simulatedAmountOut1;
    uint256 simulatedAmountOut2;
    uint256 simulatedAmountOut3;

    function setUp() public {}

    function testBotDeploy() public {
        bot = new WhackAMoleBotV1();
    }

    function testSimulatorDeploy() public {
        simulator = new SimulatorV1();
    }
}


================================================
FILE: data/__init__.py
================================================
from data.dex import *
from data.dex_streams import *

================================================
FILE: data/cex.py
================================================
import ccxt
from typing import List


class CEX:
    """
    Use CCXT to build CEX execution

    The Python project is a prototype CEX-DEX arb. bot,
    use as much of what others have built already
    """

    def __init__(self, trading_symbols: List[str]):
        self.trading_symbols = trading_symbols


if __name__ == '__main__':
    """
    Test with Binance, OKX, Bybit
    These are the top 3 derivatives exchanges
    """
    from configs import TRADING_SYMBOLS

    cex = CEX(TRADING_SYMBOLS)

    # print(ccxt.exchanges)
    #
    # binance = ccxt.binance({'options': {'defaultType': 'future'}})
    # markets = binance.load_markets()
    # print(markets)

================================================
FILE: data/cex_streams.py
================================================
import json
import time
import eth_abi
import asyncio
import datetime
import eth_utils
import websockets
import numpy as np
import aioprocessing
from functools import partial
from typing import Any, Dict, Optional

from data.cex import CEX
from data.utils import reconnecting_websocket_loop


class CexStream:
    """
    WIP

    The first step to building CEX-DEX arbitrage bot is making sure DEX execution works well
    Data from CEX and DEX are updated at different time frames, as CEX data is real-time,
    whereas DEX data is updated every new block.

    Analyze how this will affect execution of CEX-DEX arbitrage
    """

    def __init__(self,
                 cex: CEX,
                 publisher: Optional[aioprocessing.AioQueue] = None,
                 debug: bool = False):

        self.cex = cex
        self.publisher = publisher
        self.debug = debug

    def publish(self, data: Any):
        if self.publisher:
            self.publisher.put(data)

    def start_stream(self):
        pass

    async def stream_binance_usdm_orderbook(self):
        async with websockets.connect('wss://fstream.binance.com/ws/') as ws:
            params = [f'{s.replace("/", "").lower()}@depth5@100ms' for s in self.cex.trading_symbols]
            subscription = {
                'method': 'SUBSCRIBE',
                'params': params,
                'id': 1,
            }
            await ws.send(json.dumps(subscription))
            _ = await ws.recv()

            while True:
                msg = await asyncio.wait_for(ws.recv(), timeout=15)
                data = json.loads(msg)
                print(data)

    async def stream_okx_usdm_orderbook(self):
        async with websockets.connect('wss://ws.okx.com:8443/ws/v5/public') as ws:
            args = [{'channel': 'books5', 'instId': f'{s.replace("/", "-")}-SWAP'} for s in self.cex.trading_symbols]
            subscription = {
                'op': 'subscribe',
                'args': args,
            }
            await ws.send(json.dumps(subscription))
            _ = await ws.recv()

            while True:
                msg = await asyncio.wait_for(ws.recv(), timeout=15)
                data = json.loads(msg)
                print(data)

    async def stream_bybit_usdm_orderbook(self):
        """
        Bybit doesn't provide you with the option to stream orderbook snapshot data.
        We'll have to implement the orderbook snapshot on our own.
        """
        max_depth = 50

        bids = np.zeros((max_depth, 2))
        asks = np.zeros((max_depth, 2))

        async with websockets.connect('wss://stream.bybit.com/v5/public/linear') as ws:
            args = [f'orderbook.{max_depth}.{s.replace("/", "").upper()}' for s in self.cex.trading_symbols]
            subscription = {
                'op': 'subscribe',
                'args': args,
            }
            await ws.send(json.dumps(subscription))
            _ = await ws.recv()

            while True:
                msg = await asyncio.wait_for(ws.recv(), timeout=15)
                data = json.loads(msg)
                print(data)


if __name__ == '__main__':
    trading_symbols = ['BTC/USDT']

    cex = CEX(trading_symbols)

    queue = aioprocessing.AioQueue()

    cex_streams = CexStream(cex, queue, False)

    asyncio.run(cex_streams.stream_bybit_usdm_orderbook())


================================================
FILE: data/dex.py
================================================
import numpy as np
from web3 import Web3
from typing import Any, Dict, List
from multicall import Call, Multicall

from simulation import UniswapV2Simulator, UniswapV3Simulator

# Dimension of DEX.storage_array
CHAIN = 0
EXCHANGE = 1
TOKEN_IN = 2
TOKEN_OUT = 3
VERSION = 4
STORAGE = 5

V2 = 0
V3 = 1

DECIMALS0 = 0
DECIMALS1 = 1
RESERVE0 = 2
RESERVE1 = 3
SQRT_PRICE = 4
FEE = 5
TOKEN0_IN = 6
POOL_INDEX = 7


class DexBase:

    def __init__(self,
                 rpc_endpoints: Dict[str, str],
                 tokens: Dict[str, Dict[str, List[str or int]]],
                 pools: List[Dict[str, Any]],
                 trading_symbols: List[str],
                 max_swap_number: int = 3):
        """
        :param rpc_endpoints:
        ex) {'ethereum': '<RPC URL>'}

        :param tokens:
        ex) {'ethereum': {'ETH': ['<token address>', 18]}}

        :param pools:
        ex) [{'chain': 'ethereum',
              'exchange': 'uniswap',
              'version': 3,
              'name': 'ETH/USDT',
              'address': '<pool address>',
              'fee': 500,  # 0.05%
              'token0': 'ETH',
              'token1': 'USDT'}]

        :param trading_symbols:
        ex) ['BTC/USDT']

        :param max_swap_number: the maximum number of swaps in a trade
        ex) 1, 2, 3, ...
        """
        self.rpc_endpoints = rpc_endpoints
        self.tokens = tokens
        self.pools = pools
        self.trading_symbols = trading_symbols
        self.max_swap_number = max_swap_number

        self.sim_v2 = UniswapV2Simulator()
        self.sim_v3 = UniswapV3Simulator()

        self.web3 = {k: Web3(Web3.HTTPProvider(v)) for k, v in rpc_endpoints.items()}

        # extract keys from tokens, pools
        self.chains_list = sorted(list(tokens.keys()))
        self.exchanges_list = sorted(set([p['exchange'] for p in pools]))

        tokens_list = []
        for exchange, tokens_dict in tokens.items():
            tokens_list.extend(list(tokens_dict.keys()))
        self.tokens_list = sorted(list(set(tokens_list)))

        # map chains, exchanges, tokens to int id value
        # this is used to map chains/exchanges/tokens to numpy array index values
        self.chain_to_id = {k: i for i, k in enumerate(self.chains_list)}
        self.exchange_to_id = {k: i for i, k in enumerate(self.exchanges_list)}
        self.token_to_id = {k: i for i, k in enumerate(self.tokens_list)}

        """
        storage_array
        : 6-dimensional array that stores storage values from pool contracts
        """
        self.storage_array = np.zeros((
            len(self.chains_list),      # chains
            len(self.exchanges_list),   # exchanges
            len(tokens_list),           # token in
            len(tokens_list),           # token out
            2,                          # uniswap variant version: 2, 3
            8                           # decimals0, decimals1, reserve0, reserve1, sqrtPriceX96,
                                        # fee, token0_is_input, pool_index
        ))

        """
        storage_index
        : Keeps the 5-dimensional index of storage_array by chains
        : Used to generate swap paths
        : Filled in from _load_pool_data()
        
        ex) {'ethereum': [(0, 0, 1, 2, 0), ...]
        """
        self.storage_index = {c: [] for c in self.chains_list}

        """
        swap_paths
        : contains information about swap paths, pool indexes, tags, tokens involved, price, fee
        : Filled in from _generate_swap_paths()

        ex) {'ETH/USDT': {'path': np.ndarray,
                          'pool_indexes': List[List[int]],
                          'tag': List[str],
                          'tokens': np.ndarray,
                          'price': np.ndarray
                          'fee': np.ndarray}, ...}
        """
        self.swap_paths = {s: None for s in self.trading_symbols}

    def load(self):
        """
        Make sure to call this method in DEX
        """
        self._load_pool_data()
        self._generate_swap_paths()

    def _load_pool_data(self):
        """
        Loads all storage values from multiple pool contracts using Multicall
        this enables users to bulk query data on the blockchain
        """
        calls_by_chain = {c: [] for c in self.chains_list}

        for pool_idx, pool in enumerate(self.pools):
            if pool['version'] == 2:
                """
                Reference: https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol
                Return values: reserve0, reserve1, blockTimestampLast
                """
                signature = 'getReserves()((uint112,uint112,uint32))'
            else:
                """
                Reference: https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol
                Return values: sqrtPriceX96, tick, observationIndex, observationCardinality,
                               observationCardinalityNext, feeProtocol, unlocked
                """
                signature = 'slot0()((uint160,int24,uint16,uint16,uint16,uint8,bool))'

            call = Call(
                pool['address'],
                signature,
                [(str(pool_idx), lambda x: x)]
            )
            calls_by_chain[pool['chain']].append(call)

        # Send multicall queries
        multicall_results = {}
        for exchange, calls in calls_by_chain.items():
            multicall = Multicall(calls, _w3=self.web3[exchange])
            multicall_results = {
                **multicall_results,
                **multicall()
            }

        # Fill in storage_index, storage_array
        for pool_idx, storage_data in multicall_results.items():
            pool: Dict[str, Any] = self.pools[int(pool_idx)]

            chain_idx = self.chain_to_id[pool['chain']]
            exchange_idx = self.exchange_to_id[pool['exchange']]
            token0_idx = self.token_to_id[pool['token0']]
            token1_idx = self.token_to_id[pool['token1']]
            version_idx = V2 if pool['version'] == 2 else V3

            # We create two indexes to indicate:
            # - token0 -> token1,
            # - token1 -> token0
            idx_1 = (chain_idx, exchange_idx, token0_idx, token1_idx, version_idx)
            idx_2 = (chain_idx, exchange_idx, token1_idx, token0_idx, version_idx)

            # Keep all the indexes that are associated with the specific chain in DEX.storage_index
            # this is to _generate_swap_paths using the pre-saved indexes
            self.storage_index[pool['chain']].append(idx_1)
            self.storage_index[pool['chain']].append(idx_2)

            decimals0 = self.tokens[pool['chain']][pool['token0']][1]
            decimals1 = self.tokens[pool['chain']][pool['token1']][1]
            fee = pool['fee'] / 10000.0 / 100.0

            data = [0] * 6

            if version_idx == V2:
                reserve0 = storage_data[0]
                reserve1 = storage_data[1]
                data = [decimals0, decimals1, reserve0, reserve1, 0, fee]

            elif version_idx == V3:
                sqrt_price = storage_data[0]
                data = [decimals0, decimals1, 0, 0, sqrt_price, fee]

            self.storage_array[idx_1] = data + [1, int(pool_idx)]  # token_in is token0
            self.storage_array[idx_2] = data + [0, int(pool_idx)]  # token_in is not token0

    def _generate_swap_paths(self):
        """
        Generates all the swap paths up to max_swap_number swaps
        This internal function has to be called after DEX.storage_index has been filled
        """

        # Dictionary that looks like: {'ETH/USDT': [3, 1], 'BTC/USDT': [3, 0], ...}
        token_in_out = {
            symbol: [self.token_to_id[token] for token in reversed(symbol.split('/'))]
            for symbol in self.trading_symbols
        }

        chain_swap_paths = {}

        for chain, index in self.storage_index.items():
            index_arr = np.array(index)

            symbol_swap_paths = {}

            """
            Loop through each symbol from token_in_out (=symbols in trading_symbols)
            and generate viable swap paths that can occur within a blockchain.
            This means that there will be multiple viable swap paths for a trading symbol per chain.
            """
            for symbol, in_out in token_in_out.items():
                pool_samples = self.__sample_pools(index_arr, in_out)
                paths = self.__generate_paths(pool_samples)

                paths_arr = np.array(paths)
                if paths_arr.shape[0] != 0:
                    symbol_swap_paths[symbol] = paths_arr
                else:
                    symbol_swap_paths[symbol] = paths_arr.reshape((0, 3, 5)).astype(np.int64)

            chain_swap_paths[chain] = symbol_swap_paths

        def _get_pool_indexes(_symbol_paths: np.ndarray) -> List[int]:
            indexes = []
            for i in np.arange(_symbol_paths.shape[0]):
                idx = _symbol_paths[i]
                if np.sum(idx) != 0:
                    pool_idx = self.storage_array[tuple(idx)][POOL_INDEX]
                    indexes.append(int(pool_idx))
            return indexes

        # concatenate the paths generated for each chain
        for symbol in self.trading_symbols:
            symbol_paths_list = [chain_swap_paths[chain][symbol] for chain in self.chains_list]
            symbol_paths_array = np.concatenate(symbol_paths_list)

            pool_indexes = [
                _get_pool_indexes(symbol_paths_array[i])
                for i in np.arange(symbol_paths_array.shape[0])
            ]

            # get unique tokens from pool index that isn't all 0's
            _tokens_involved = symbol_paths_array[:, :, [TOKEN_IN, TOKEN_OUT]].reshape(-1, 2)
            tokens_involved = np.unique(_tokens_involved[~np.all(_tokens_involved == 0, axis=1)])

            price_arr = np.zeros(symbol_paths_array.shape[0])
            fee_arr = np.zeros(symbol_paths_array.shape[0])

            """
            Create a path tag for easy reference/debugging
            Each tag will look like: ethereum-0, ethereum-1, polygon-0, ...
            This step is completely unnecessary to the main logic
            """
            chain_path_counter = {chain: 0 for chain in self.chains_list}
            tags = []
            for i in np.arange(symbol_paths_array.shape[0]):
                chain_idx = symbol_paths_array[i][0][0]
                chain = self.chains_list[chain_idx]
                tag = f'{chain}-{chain_path_counter[chain]}'
                chain_path_counter[chain] += 1
                tags.append(tag)

            self.swap_paths[symbol] = {
                'path': symbol_paths_array,     # np.ndarray: (n, max_swap_number, 5)
                'pool_indexes': pool_indexes,   # List[List[int]]: len() == n
                'tag': tags,                    # List[str]: len() == n
                'tokens': tokens_involved,      # np.ndarray: (1, unique token numbers)
                'price': price_arr,             # np.ndarray: (1, n) --> n should match the number of paths
                'fee': fee_arr,                 # np.ndarray: (1, n) --> n should match the number of paths
            }

    def __sample_pools(self, index_arr: np.ndarray, in_out: List[int]) -> Dict[int, List[List[List[int]]]]:
        # Step #1
        # Sampling pools that can be used in n-hop swaps with token_in, token_out constraints
        # Sampling before finding swap paths can reduce the time it takes to build viable swap paths
        swap_nums = range(1, self.max_swap_number + 1)
        pool_samples = {n: [] for n in swap_nums}

        for n in swap_nums:
            token_in, token_out = in_out

            no_path = False

            # setup an empty list of pool samples that could be used in each nth step of the swap path
            filtered_pools = [[]] * self.max_swap_number

            for i in range(n):
                if i == 0:
                    # token_in is int here
                    condition_1 = index_arr[:, TOKEN_IN] == token_in
                else:
                    # token_in in List[int] here
                    condition_1 = np.isin(index_arr[:, TOKEN_IN], token_in)

                if i == n - 1:
                    condition_2 = index_arr[:, TOKEN_OUT] == token_out
                else:
                    condition_2 = index_arr[:, TOKEN_OUT] != token_out

                condition = condition_1 & condition_2
                filtered = index_arr[condition]
                if filtered.shape[0] > 0:
                    filtered_pools[i] = filtered.tolist()
                else:
                    no_path = True
                    break

                # set the next token_in to be current token_outs
                token_in = list(filtered[:, TOKEN_OUT])

            if not no_path:
                pool_samples[n] = filtered_pools

        return pool_samples

    def __generate_paths(self, pool_samples: Dict[int, List[List[List[int]]]]) -> List[List[List[int]]]:
        """
        :param pool_samples: returned value of DEX._sample_pools
        """

        def __sample_paths_list(_n_total_hops: int,
                                _nth_hop: int,
                                _prev_pool: None or List[int],
                                _sampled: List[List[int]],
                                _pool_samples: Dict[int, List[List[List[int]]]],
                                _paths: List[List[List[int]]]):
            """
            Uses recursive looping to fill in paths_list

            :param _n_total_hops: the number of hops you are trying to sample. ex) 1, 2, 3, ...
            :param _nth_hop: out of the _n_total_hops which _nth_hop are you on
            :param _prev_pool: the previous pool index. ex) (0, 0, 1, 2, 0)
            :param _sampled: the sampled pools we should append to _paths
            :param _pool_samples: pool_samples we made from the above sampling process
            :param _paths: the list that collects all viable combination of pools
            """

            for _p in _pool_samples[_n_total_hops][_nth_hop]:
                _sampled[_nth_hop] = _p
                if _prev_pool is None or _prev_pool[TOKEN_OUT] == _p[TOKEN_IN]:
                    if _nth_hop == _n_total_hops - 1:
                        if _prev_pool:
                            """
                            Exclude swaps that buy from the previous step, and sells on this one
                            ex) ETH -> USDT -> ETH
                            (ETH/USDT, USDT/ETH pools on the same exchange, version are considered equal)

                            This isn't necessarily true in Uniswap V3 variants, because different fee levels can exist.
                            However, we exclude that scenario for simplicity.
                            """
                            same_exchange = _prev_pool[EXCHANGE] == _p[EXCHANGE]
                            same_version = _prev_pool[VERSION] == _p[VERSION]
                            same_pool = _prev_pool[TOKEN_IN] == _p[TOKEN_OUT] and _prev_pool[TOKEN_OUT] == _p[
                                TOKEN_IN]
                            if not (same_exchange and same_version and same_pool):
                                _paths.append(_sampled.copy())
                        else:
                            _paths.append(_sampled.copy())
                    else:
                        __sample_paths_list(_n_total_hops,
                                            _nth_hop + 1,
                                            _p,
                                            _sampled,
                                            _pool_samples,
                                            _paths)

        # Step #2
        # Generate swap paths by applying conditional checks to see if token_in, token_out is in sync
        paths = []
        empty_pool = [0, 0, 0, 0, 0]

        for i in range(1, self.max_swap_number + 1):
            if len(pool_samples[i]) > 0:
                sampled = [empty_pool] * self.max_swap_number
                prev_pool = None
                __sample_paths_list(_n_total_hops=i,
                                    _nth_hop=0,
                                    _prev_pool=prev_pool,
                                    _sampled=sampled,
                                    _pool_samples=pool_samples,
                                    _paths=paths)

        return paths


class NoSymbolError(Exception):

    def __init__(self, msg: str):
        self.msg = msg

    def __str__(self):
        return self.msg


class DEX(DexBase):

    def __init__(self,
                 rpc_endpoints: Dict[str, str],
                 tokens: Dict[str, Dict[str, List[str or int]]],
                 pools: List[Dict[str, Any]],
                 trading_symbols: List[str],
                 max_swap_number: int = 3):

        super().__init__(rpc_endpoints,
                         tokens,
                         pools,
                         trading_symbols,
                         max_swap_number)

        self.load()

        for chain in self.chains_list:
            for symbol in self.trading_symbols:
                self.update_price_for_symbol(chain, symbol)

    def get_index(self,
                  chain: str,
                  exchange: str,
                  token0: str,
                  token1: str,
                  version: int) -> tuple:

        c = self.chain_to_id[chain]
        e = self.exchange_to_id[exchange]
        t0 = self.token_to_id[token0]
        t1 = self.token_to_id[token1]
        v = V2 if version == 2 else V3
        return c, e, t0, t1, v

    def get_price(self,
                  c: int,
                  e: int,
                  t0: int,
                  t1: int,
                  v: int) -> tuple:

        idx = (c, e, t0, t1, v)
        dec0, dec1, res0, res1, sqrt, fee, tok0, _ = self.storage_array[idx]

        if v == V2:
            price = self.sim_v2.reserves_to_price(res0, res1, dec0, dec1, bool(tok0))
        else:
            price = self.sim_v3.sqrtx96_to_price(sqrt, dec0, dec1, bool(tok0))

        return price, fee

    def get_symbols_to_update(self, token0: str, token1: str) -> List[str]:
        """
        Returns the symbols that need to be updated after
        token0, token1 storage information have been updated

        This is used in DexStream to figure out which symbols to update the price for
        after receiving Sync, Swap events from Uniswap V2, V3 variant exchanges
        """
        token0_id = self.token_to_id[token0]
        token1_id = self.token_to_id[token1]

        symbols_to_update = []

        for symbol in self.trading_symbols:
            tokens_involved = self.swap_paths[symbol]['tokens']
            if token0_id in tokens_involved or token1_id in tokens_involved:
                symbols_to_update.append(symbol)

        return symbols_to_update

    def update_price_for_symbol(self, chain: str, symbol: str):
        if symbol not in self.trading_symbols:
            raise NoSymbolError(f'{symbol} not in {self.trading_symbols}')

        chain_idx = self.chain_to_id[chain]
        paths_arr = self.swap_paths[symbol]['path']

        for i in np.arange(paths_arr.shape[0]):
            path = paths_arr[i]

            # There is always a first pool, so check the first pool's chain id
            if path[0][0] == chain_idx:
                price = 1
                fee = 1
                for p_step in np.arange(path.shape[0]):
                    idx = path[p_step]
                    if np.sum(idx) == 0:
                        break
                    _p, _f = self.get_price(*idx)
                    """
                    Take the inverse of price.
                    This is needed because if you are trying to BUY ETH with USDT,
                    then token_in will be USDT, and token_out will be ETH.
                    Thus, the quote amount of ETH you get for providing 1 USDT is currently: 0.0005387 ETH.
                    However, we want the price to be in the format of ETH/USDT = 1856.32 USDT.
                    (*This is the equivalent format of CEX price quotes. Binance ETH/USDT = 1856.xx USDT)
                    To get this value, we take the inverse of price.
                    1 / 0.0005387 = 1856.32
                    """
                    price = price * (1 / _p)
                    fee = fee * (1 - _f)

                self.swap_paths[symbol]['price'][i] = price
                self.swap_paths[symbol]['fee'][i] = 1 - fee

    def update_reserves(self,
                        chain: str,
                        exchange: str,
                        token0: str,
                        token1: str,
                        reserve0: float,
                        reserve1: float):

        idx_1 = self.get_index(chain, exchange, token0, token1, 2)
        idx_2 = (idx_1[0], idx_1[1], idx_1[3], idx_1[2], idx_1[4])

        self.storage_array[idx_1][RESERVE0] = reserve0
        self.storage_array[idx_1][RESERVE1] = reserve1

        self.storage_array[idx_2][RESERVE0] = reserve0
        self.storage_array[idx_2][RESERVE1] = reserve1

    def update_sqrt_price(self,
                          chain: str,
                          exchange: str,
                          token0: str,
                          token1: str,
                          sqrt_price: float):

        idx_1 = self.get_index(chain, exchange, token0, token1, 3)
        idx_2 = (idx_1[0], idx_1[1], idx_1[3], idx_1[2], idx_1[4])

        self.storage_array[idx_1][SQRT_PRICE] = sqrt_price
        self.storage_array[idx_2][SQRT_PRICE] = sqrt_price

    def debug_message(self,
                      chain: str,
                      exchange: str,
                      token0: str,
                      token1: str,
                      version: int) -> str:

        idx = self.get_index(chain, exchange, token0, token1, version)
        price, _ = self.get_price(*idx)
        return f'[{chain}] {exchange} V{version}: {token0}/{token1} @{price} & {token1}/{token0} @{1 / price}'


if __name__ == '__main__':
    from configs import RPC_ENDPOINTS, TOKENS, POOLS

    dex = DEX(RPC_ENDPOINTS,
              TOKENS,
              POOLS,
              ['ETH/USDT'])


================================================
FILE: data/dex_streams.py
================================================
import os
import json
import time
import eth_abi
import asyncio
import aiohttp
import datetime
import eth_utils
import websockets
import numpy as np
import aioprocessing
from functools import partial
from dotenv import load_dotenv
from typing import Any, Callable, Dict, Optional

from data.dex import DEX
from data.utils import reconnecting_websocket_loop, calculate_next_block_base_fee

load_dotenv(override=True)

BLOCKNATIVE_TOKEN = os.getenv('BLOCKNATIVE_TOKEN')


def default_message_format(symbol: str,
                           message: Dict[str, Any],
                           block_number: int = 0) -> Dict[str, Any]:
    """

    :param symbol: BTC/USDT, ETH/USDT, ...
    :param message: value of DEX.swap_paths[symbol]
    ex) {'path': np.ndarray,
         'pool_indexes': List[List[int]],
         'tag': List[str],
         'tokens': np.ndarray,
         'price': np.ndarray,
         'fee': np.ndarray}
    :param block_number: block number of event
    :return:
    """
    return {
        'source': 'dex',
        'type': 'event',
        'block': block_number,
        'path': message['path'].tolist(),
        'pool_indexes': message['pool_indexes'],
        'symbol': symbol,
        'tag': message['tag'],
        'price': message['price'].tolist(),
        'fee': message['fee'].tolist(),
    }


class DexStream:

    def __init__(self,
                 dex: DEX,
                 ws_endpoints: Dict[str, str],
                 publisher: Optional[aioprocessing.AioQueue] = None,
                 message_formatter: Callable = default_message_format,
                 debug: bool = False):
        """
        :param dex: DEX instance

        :param ws_endpoints:
        ex) {'ethereum': '<WS URL>'}

        :param publisher: an instance of aioprocessing.AioQueue, used to send processed
                          market data to strategy

        :param message_formatter: is used to format message sent through the publisher
                                  this data will be accessed from the main process
        """
        self.dex = dex
        self.ws_endpoints = ws_endpoints
        self.publisher = publisher
        self.message_formatter = message_formatter
        self.debug = debug

    def publish(self, data: Any):
        if self.publisher:
            self.publisher.put(data)

    def start_streams(self):
        streams = []

        for chain in self.dex.chains_list:
            block_stream = reconnecting_websocket_loop(
                partial(self.stream_new_blocks, chain),
                tag=f'{chain.upper()}_Blocks'
            )
            streams.append(block_stream)

        for chain in self.dex.chains_list:
            v2_stream = reconnecting_websocket_loop(
                partial(self.stream_uniswap_v2_events, chain),
                tag=f'{chain.upper()}_V2'
            )
            v3_stream = reconnecting_websocket_loop(
                partial(self.stream_uniswap_v3_events, chain),
                tag=f'{chain.upper()}_V3'
            )
            streams.extend([asyncio.ensure_future(f) for f in [v2_stream, v3_stream]])

        loop = asyncio.get_event_loop()
        loop.run_until_complete(asyncio.wait(streams))

    async def stream_uniswap_v2_events(self, chain: str):
        filtered_pools = [
            pool for pool in self.dex.pools
            if pool['chain'] == chain and pool['version'] == 2
        ]
        pools = {pool['address'].lower(): pool for pool in filtered_pools}

        sync_event_selector = self.dex.web3[chain].keccak(
            text='Sync(uint112,uint112)'
        ).hex()

        async with websockets.connect(self.ws_endpoints[chain]) as ws:
            subscription = {
                'json': '2.0',
                'id': 1,
                'method': 'eth_subscribe',
                'params': [
                    'logs',
                    {'topics': [sync_event_selector]}
                ]
            }

            await ws.send(json.dumps(subscription))
            _ = await ws.recv()

            while True:
                msg = await asyncio.wait_for(ws.recv(), timeout=60 * 10)
                event = json.loads(msg)['params']['result']
                address = event['address'].lower()

                if address in pools:
                    s = time.time()
                    block_number = int(event['blockNumber'], base=16)
                    pool = pools[address]
                    data = eth_abi.decode(
                        ['uint112', 'uint112'],
                        eth_utils.decode_hex(event['data'])
                    )

                    chain = pool['chain']
                    exchange = pool['exchange']
                    token0 = pool['token0']
                    token1 = pool['token1']

                    self.dex.update_reserves(chain, exchange, token0, token1, data[0], data[1])

                    symbols = self.dex.get_symbols_to_update(token0, token1)
                    for symbol in symbols:
                        self.dex.update_price_for_symbol(chain, symbol)
                        self.publish(self.message_formatter(symbol, self.dex.swap_paths[symbol], block_number))
                    e = time.time()

                    if self.debug:
                        dbg_msg = self.dex.debug_message(chain, exchange, token0, token1, 2)
                        print(f'{datetime.datetime.now()} {dbg_msg} -> Update took: {e - s} seconds')

    async def stream_uniswap_v3_events(self, chain: str):
        filtered_pools = [
            pool for pool in self.dex.pools
            if pool['chain'] == chain and pool['version'] == 3
        ]
        pools = {pool['address'].lower(): pool for pool in filtered_pools}

        swap_event_selector = self.dex.web3[chain].keccak(
            text='Swap(address,address,int256,int256,uint160,uint128,int24)'
        ).hex()

        async with websockets.connect(self.ws_endpoints[chain]) as ws:
            subscription = {
                'json': '2.0',
                'id': 1,
                'method': 'eth_subscribe',
                'params': [
                    'logs',
                    {'topics': [swap_event_selector]}
                ]
            }

            await ws.send(json.dumps(subscription))
            _ = await ws.recv()

            while True:
                msg = await asyncio.wait_for(ws.recv(), timeout=60 * 10)
                event = json.loads(msg)['params']['result']
                address = event['address'].lower()

                if address in pools:
                    # we don't need the sender, recipient data in topics
                    s = time.time()
                    block_number = int(event['blockNumber'], base=16)
                    pool = pools[address]
                    data = eth_abi.decode(
                        ['int256', 'int256', 'uint160', 'uint128', 'int24'],
                        eth_utils.decode_hex(event['data'])
                    )

                    chain = pool['chain']
                    exchange = pool['exchange']
                    token0 = pool['token0']
                    token1 = pool['token1']

                    self.dex.update_sqrt_price(chain, exchange, token0, token1, data[2])

                    symbols = self.dex.get_symbols_to_update(token0, token1)
                    for symbol in symbols:
                        self.dex.update_price_for_symbol(chain, symbol)
                        self.publish(self.message_formatter(symbol, self.dex.swap_paths[symbol], block_number))
                    e = time.time()

                    if self.debug:
                        dbg_msg = self.dex.debug_message(chain, exchange, token0, token1, 3)
                        print(f'{datetime.datetime.now()} {dbg_msg} -> Update took: {e - s} seconds')

    async def stream_new_blocks(self, chain: str):
        """
        Retrieves new blocks and calculates base fees accordingly
        Base fees are calculated adhering to the EIP-1559 implementation and
        max_price, max_priority_fee_per_gas, max_fee_per_gas are retrieved using Blocknative's gas estimator endpoint

        - Ethereum: https://api.blocknative.com/gasprices/blockprices?chainId=1
        - Polygon: https://api.blocknative.com/gasprices/blockprices?chainId=137
        """

        """
        historical_gas tracks 100 historical base_fee, max_priority_fee_per_gas, max_fee_per_gas
        However, note that the historical data isn't loaded up from the start, but instead is
        filled in at real-time
        Thus, at start-up this data is an empty numpy array with all 0's
        """
        historical_gas = np.zeros((3, 100))

        # index of historical_gas
        BASE_FEE = 0
        MAX_PRIORITY_FEE_PER_GAS = 1
        MAX_FEE_PER_GAS = 2

        async with websockets.connect(self.ws_endpoints[chain]) as ws:
            subscription = {
                'json': '2.0',
                'id': 1,
                'method': 'eth_subscribe',
                'params': ['newHeads']
            }

            await ws.send(json.dumps(subscription))
            _ = await ws.recv()

            gwei = 10 ** 9

            while True:
                msg = await asyncio.wait_for(ws.recv(), timeout=60 * 10)
                block = json.loads(msg)['params']['result']
                block_number = int(block['number'], base=16)
                base_fee = calculate_next_block_base_fee(block)

                """
                For Ethereum and Polygon, use gas price estimation tools provided by Blocknative
                https://www.blocknative.com/gas-estimator
                
                Run only if a token is given
                """
                if chain in ['ethereum', 'polygon'] and BLOCKNATIVE_TOKEN:
                    chain_id = 1 if 'ethereum' else 137
                    headers = {'Authorization': BLOCKNATIVE_TOKEN}
                    async with aiohttp.ClientSession(headers=headers) as session:
                        async with session.get(f'https://api.blocknative.com/gasprices/blockprices?chainId={chain_id}') as r:
                            res = await r.json()
                            estimated_price = res['blockPrices'][0]['estimatedPrices'][0]

                            max_priority_fee_per_gas = estimated_price['maxPriorityFeePerGas']
                            max_fee_per_gas = estimated_price['maxFeePerGas']

                            new_gas_data = [
                                base_fee,
                                int(max_priority_fee_per_gas * gwei),
                                int(max_fee_per_gas * gwei)
                            ]
                else:
                    new_gas_data = [base_fee, 0, 0]

                """
                update historical_gas data as you would an Deque data structure
                
                Currently this data does nothing, however, it can be used to optimize gas costs later
                """
                historical_gas[:, 0:-1] = historical_gas[:, 1:]
                historical_gas[:, -1] = new_gas_data

                data = {
                    'source': 'dex',
                    'type': 'block',
                    'chain': chain,
                    'block': block_number,
                    'base_fee': int(historical_gas[BASE_FEE, -1]),
                    'max_priority_fee_per_gas': int(historical_gas[MAX_PRIORITY_FEE_PER_GAS, -1]),
                    'max_fee_per_gas': int(historical_gas[MAX_FEE_PER_GAS, -1]),
                }
                self.publish(data)


if __name__ == '__main__':
    from configs import (
        RPC_ENDPOINTS,
        WS_ENDPOINTS,
        TOKENS,
        POOLS,
    )

    chain = 'ethereum'

    rpc_endpoints = {chain: RPC_ENDPOINTS[chain]}
    ws_endpoints = {chain: WS_ENDPOINTS[chain]}
    tokens = {chain: TOKENS[chain]}
    pools = [pool for pool in POOLS if pool['chain'] == chain]

    dex = DEX(rpc_endpoints,
              tokens,
              pools,
              ['ETH/USDT'])

    queue = aioprocessing.AioQueue()

    dex_stream = DexStream(dex, ws_endpoints, queue, default_message_format, False)
    # dex_stream.start_streams()
    asyncio.run(dex_stream.stream_new_blocks('ethereum'))


================================================
FILE: data/utils.py
================================================
import random
import asyncio
import websockets
from typing import Any, Callable, Dict


async def reconnecting_websocket_loop(stream_fn: Callable, tag: str):
    while True:
        try:
            await stream_fn()

        except (websockets.ConnectionClosedError, websockets.ConnectionClosedOK) as e:
            print(f'{tag} websocket connection closed: {e}')
            print('Reconnecting...')
            await asyncio.sleep(2)

        except Exception as e:
            print(f'An error has occurred with {tag} websocket: {e}')
            break


def calculate_next_block_base_fee(block: Dict[str, Any]):
    base_fee = int(block['baseFeePerGas'], base=16)
    gas_used = int(block['gasUsed'], base=16)
    gas_limit = int(block['gasLimit'], base=16)

    target_gas_used = gas_limit / 2
    target_gas_used = 1 if target_gas_used == 0 else target_gas_used

    if gas_used > target_gas_used:
        new_base_fee = base_fee + ((base_fee * (gas_used - target_gas_used)) / target_gas_used) / 8
    else:
        new_base_fee = base_fee - ((base_fee * (target_gas_used - gas_used)) / target_gas_used) / 8

    return int(new_base_fee + random.randint(0, 9))


================================================
FILE: examples/dex.py
================================================
import numpy as np

from data.dex import DEX
from configs import RPC_ENDPOINTS, TOKENS, POOLS


if __name__ == '__main__':
    dex = DEX(RPC_ENDPOINTS,
              TOKENS,
              POOLS,
              ['ETH/USDT', 'BTC/USDT'])

    # Retrieving the price of a specific pool
    idx = dex.get_index('ethereum', 'uniswap', 'ETH', 'USDT', 3)
    print(idx)

    price, fee = dex.get_price(*idx)
    print(price, fee)

    # Retrieving price of multiple swap paths
    btc_price = dex.swap_paths['BTC/USDT']['price']
    btc_fee = dex.swap_paths['BTC/USDT']['fee']

    print(btc_price)
    print(btc_fee)

    # Get the lowest price path: best for BUY orders
    buy_price = np.min(btc_price)

    # Get the highest price path: best for SELL orders
    sell_price = np.max(btc_price)

    # Calculate the spread between the two paths
    buy_sell_spread = (sell_price / buy_price - 1) * 100
    print(f'Buy-Sell price spread: {buy_sell_spread}%')


================================================
FILE: execution/WhackAMoleBotV1.json
================================================
{
  "abi": [
    {
      "inputs": [],
      "stateMutability": "nonpayable",
      "type": "constructor"
    },
    {
      "inputs": [],
      "name": "TradeFailed",
      "type": "error"
    },
    {
      "inputs": [],
      "name": "Unauthorized",
      "type": "error"
    },
    {
      "inputs": [
        {
          "internalType": "address[]",
          "name": "tokens",
          "type": "address[]"
        },
        {
          "internalType": "address[]",
          "name": "protocols",
          "type": "address[]"
        }
      ],
      "name": "approveHandlers",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "internalType": "address[]",
          "name": "tokens",
          "type": "address[]"
        }
      ],
      "name": "recoverTokens",
      "outputs": [],
      "stateMutability": "payable",
      "type": "function"
    },
    {
      "inputs": [
        {
          "components": [
            {
              "internalType": "uint8",
              "name": "protocol",
              "type": "uint8"
            },
            {
              "internalType": "address",
              "name": "handler",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "tokenIn",
              "type": "address"
            },
            {
              "internalType": "address",
              "name": "tokenOut",
              "type": "address"
            },
            {
              "internalType": "uint24",
              "name": "fee",
              "type": "uint24"
            },
            {
              "internalType": "uint256",
              "name": "amount",
              "type": "uint256"
            }
          ],
          "internalType": "struct WhackAMoleBotV1.SwapParams[]",
          "name": "paramsArray",
          "type": "tuple[]"
        },
        {
          "internalType": "uint256",
          "name": "minAmountOut",
          "type": "uint256"
        }
      ],
      "name": "whack",
      "outputs": [
        {
          "internalType": "uint256",
          "name": "",
          "type": "uint256"
        }
      ],
      "stateMutability": "payable",
      "type": "function"
    }
  ],
  "bytecode": {
    "object": "0x60a060405234801561001057600080fd5b5033608052608051610f0d61003060003960006102fa0152610f0d6000f3fe6080604052600436106100345760003560e01c80639c832f9314610039578063bc2078fb1461004e578063c0f757c014610061575b600080fd5b61004c610047366004610a9e565b610086565b005b61004c61005c366004610ae0565b61016d565b61007461006f366004610b4c565b61020f565b60405190815260200160405180910390f35b61008e6102ef565b8060005b818110156101675760008484838181106100ae576100ae610bc7565b90506020020160208101906100c39190610bf9565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015290915061015e9033906001600160a01b038416906370a0823190602401602060405180830381865afa158015610129573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061014d9190610c14565b6001600160a01b0384169190610353565b50600101610092565b50505050565b600019838260005b8281101561020557600088888381811061019157610191610bc7565b90506020020160208101906101a69190610bf9565b905060005b838110156101fb5760008888838181106101c7576101c7610bc7565b90506020020160208101906101dc9190610bf9565b90506101f26001600160a01b0384168289610401565b506001016101ab565b5050600101610175565b5050505050505050565b60006102196102ef565b600083815b818110156102ab57600087878381811061023a5761023a610bc7565b905060c002018036038101906102509190610c9d565b905083600003610266578060a00151935061026e565b60a081018490525b805160ff1660000361028a5761028381610554565b93506102a2565b805160ff166001036102a25761029f81610696565b93505b5060010161021e565b50838210156102e6576040517f2d8ef0cf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50949350505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610351576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6040516001600160a01b0383166024820152604481018290526103fc9084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526107c3565b505050565b80158061049457506040517fdd62ed3e0000000000000000000000000000000000000000000000000000000081523060048201526001600160a01b03838116602483015284169063dd62ed3e90604401602060405180830381865afa15801561046e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104929190610c14565b155b61050b5760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527f20746f206e6f6e2d7a65726f20616c6c6f77616e63650000000000000000000060648201526084015b60405180910390fd5b6040516001600160a01b0383166024820152604481018290526103fc9084907f095ea7b30000000000000000000000000000000000000000000000000000000090606401610398565b604080516002808252606080830184526000939092919060208301908036833701905050905082604001518160008151811061059257610592610bc7565b60200260200101906001600160a01b031690816001600160a01b0316815250508260600151816001815181106105ca576105ca610bc7565b60200260200101906001600160a01b031690816001600160a01b031681525050600083602001516001600160a01b03166338ed17398560a0015160008530426040518663ffffffff1660e01b8152600401610629959493929190610d2b565b6000604051808303816000875af1158015610648573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526106709190810190610d9c565b90508060018151811061068557610685610bc7565b602002602001015192505050919050565b6040805161010081018252828201516001600160a01b0390811682526060808501518216602080850191825260808088015162ffffff908116878901908152309588019586524292880192835260a0808b0151908901908152600060c08a0181815260e08b01828152968d01519b517f414bf3890000000000000000000000000000000000000000000000000000000081528b518b16600482015297518a1660248901529251909316604487015295518716606486015291516084850152935160a48401525160c483015251831660e482015290939091169063414bf38990610104016020604051808303816000875af1158015610798573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107bc9190610c14565b9392505050565b6000610818826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166108ab9092919063ffffffff16565b90508051600014806108395750808060200190518101906108399190610e42565b6103fc5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610502565b60606108ba84846000856108c2565b949350505050565b60608247101561093a5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610502565b600080866001600160a01b031685876040516109569190610e88565b60006040518083038185875af1925050503d8060008114610993576040519150601f19603f3d011682016040523d82523d6000602084013e610998565b606091505b50915091506109a9878383876109b4565b979650505050505050565b60608315610a23578251600003610a1c576001600160a01b0385163b610a1c5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610502565b50816108ba565b6108ba8383815115610a385781518083602001fd5b8060405162461bcd60e51b81526004016105029190610ea4565b60008083601f840112610a6457600080fd5b50813567ffffffffffffffff811115610a7c57600080fd5b6020830191508360208260051b8501011115610a9757600080fd5b9250929050565b60008060208385031215610ab157600080fd5b823567ffffffffffffffff811115610ac857600080fd5b610ad485828601610a52565b90969095509350505050565b60008060008060408587031215610af657600080fd5b843567ffffffffffffffff80821115610b0e57600080fd5b610b1a88838901610a52565b90965094506020870135915080821115610b3357600080fd5b50610b4087828801610a52565b95989497509550505050565b600080600060408486031215610b6157600080fd5b833567ffffffffffffffff80821115610b7957600080fd5b818601915086601f830112610b8d57600080fd5b813581811115610b9c57600080fd5b87602060c083028501011115610bb157600080fd5b6020928301989097509590910135949350505050565b634e487b7160e01b600052603260045260246000fd5b80356001600160a01b0381168114610bf457600080fd5b919050565b600060208284031215610c0b57600080fd5b6107bc82610bdd565b600060208284031215610c2657600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b60405160c0810167ffffffffffffffff81118282101715610c6657610c66610c2d565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715610c9557610c95610c2d565b604052919050565b600060c08284031215610caf57600080fd5b610cb7610c43565b823560ff81168114610cc857600080fd5b8152610cd660208401610bdd565b6020820152610ce760408401610bdd565b6040820152610cf860608401610bdd565b6060820152608083013562ffffff81168114610d1357600080fd5b608082015260a0928301359281019290925250919050565b600060a082018783526020878185015260a0604085015281875180845260c086019150828901935060005b81811015610d7b5784516001600160a01b031683529383019391830191600101610d56565b50506001600160a01b03969096166060850152505050608001529392505050565b60006020808385031215610daf57600080fd5b825167ffffffffffffffff80821115610dc757600080fd5b818501915085601f830112610ddb57600080fd5b815181811115610ded57610ded610c2d565b8060051b9150610dfe848301610c6c565b8181529183018401918481019088841115610e1857600080fd5b938501935b83851015610e3657845182529385019390850190610e1d565b98975050505050505050565b600060208284031215610e5457600080fd5b815180151581146107bc57600080fd5b60005b83811015610e7f578181015183820152602001610e67565b50506000910152565b60008251610e9a818460208701610e64565b9190910192915050565b6020815260008251806020840152610ec3816040850160208701610e64565b601f01601f1916919091016040019291505056fea2646970667358221220e75668af077dbec1fec4d2e8edeec37cd59855fc788594733b9b89d21e48939064736f6c63430008140033",
    "sourceMap": "433:3979:25:-:0;;;763:49;;;;;;;;;-1:-1:-1;795:10:25;787:18;;433:3979;;;;;;;;;;;;",
    "linkReferences": {}
  },
  "deployedBytecode": {
    "object": "0x6080604052600436106100345760003560e01c80639c832f9314610039578063bc2078fb1461004e578063c0f757c014610061575b600080fd5b61004c610047366004610a9e565b610086565b005b61004c61005c366004610ae0565b61016d565b61007461006f366004610b4c565b61020f565b60405190815260200160405180910390f35b61008e6102ef565b8060005b818110156101675760008484838181106100ae576100ae610bc7565b90506020020160208101906100c39190610bf9565b6040517f70a0823100000000000000000000000000000000000000000000000000000000815230600482015290915061015e9033906001600160a01b038416906370a0823190602401602060405180830381865afa158015610129573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061014d9190610c14565b6001600160a01b0384169190610353565b50600101610092565b50505050565b600019838260005b8281101561020557600088888381811061019157610191610bc7565b90506020020160208101906101a69190610bf9565b905060005b838110156101fb5760008888838181106101c7576101c7610bc7565b90506020020160208101906101dc9190610bf9565b90506101f26001600160a01b0384168289610401565b506001016101ab565b5050600101610175565b5050505050505050565b60006102196102ef565b600083815b818110156102ab57600087878381811061023a5761023a610bc7565b905060c002018036038101906102509190610c9d565b905083600003610266578060a00151935061026e565b60a081018490525b805160ff1660000361028a5761028381610554565b93506102a2565b805160ff166001036102a25761029f81610696565b93505b5060010161021e565b50838210156102e6576040517f2d8ef0cf00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b50949350505050565b336001600160a01b037f00000000000000000000000000000000000000000000000000000000000000001614610351576040517f82b4290000000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b565b6040516001600160a01b0383166024820152604481018290526103fc9084907fa9059cbb00000000000000000000000000000000000000000000000000000000906064015b60408051601f198184030181529190526020810180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff167fffffffff00000000000000000000000000000000000000000000000000000000909316929092179091526107c3565b505050565b80158061049457506040517fdd62ed3e0000000000000000000000000000000000000000000000000000000081523060048201526001600160a01b03838116602483015284169063dd62ed3e90604401602060405180830381865afa15801561046e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104929190610c14565b155b61050b5760405162461bcd60e51b815260206004820152603660248201527f5361666545524332303a20617070726f76652066726f6d206e6f6e2d7a65726f60448201527f20746f206e6f6e2d7a65726f20616c6c6f77616e63650000000000000000000060648201526084015b60405180910390fd5b6040516001600160a01b0383166024820152604481018290526103fc9084907f095ea7b30000000000000000000000000000000000000000000000000000000090606401610398565b604080516002808252606080830184526000939092919060208301908036833701905050905082604001518160008151811061059257610592610bc7565b60200260200101906001600160a01b031690816001600160a01b0316815250508260600151816001815181106105ca576105ca610bc7565b60200260200101906001600160a01b031690816001600160a01b031681525050600083602001516001600160a01b03166338ed17398560a0015160008530426040518663ffffffff1660e01b8152600401610629959493929190610d2b565b6000604051808303816000875af1158015610648573d6000803e3d6000fd5b505050506040513d6000823e601f3d908101601f191682016040526106709190810190610d9c565b90508060018151811061068557610685610bc7565b602002602001015192505050919050565b6040805161010081018252828201516001600160a01b0390811682526060808501518216602080850191825260808088015162ffffff908116878901908152309588019586524292880192835260a0808b0151908901908152600060c08a0181815260e08b01828152968d01519b517f414bf3890000000000000000000000000000000000000000000000000000000081528b518b16600482015297518a1660248901529251909316604487015295518716606486015291516084850152935160a48401525160c483015251831660e482015290939091169063414bf38990610104016020604051808303816000875af1158015610798573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107bc9190610c14565b9392505050565b6000610818826040518060400160405280602081526020017f5361666545524332303a206c6f772d6c6576656c2063616c6c206661696c6564815250856001600160a01b03166108ab9092919063ffffffff16565b90508051600014806108395750808060200190518101906108399190610e42565b6103fc5760405162461bcd60e51b815260206004820152602a60248201527f5361666545524332303a204552433230206f7065726174696f6e20646964206e60448201527f6f742073756363656564000000000000000000000000000000000000000000006064820152608401610502565b60606108ba84846000856108c2565b949350505050565b60608247101561093a5760405162461bcd60e51b815260206004820152602660248201527f416464726573733a20696e73756666696369656e742062616c616e636520666f60448201527f722063616c6c00000000000000000000000000000000000000000000000000006064820152608401610502565b600080866001600160a01b031685876040516109569190610e88565b60006040518083038185875af1925050503d8060008114610993576040519150601f19603f3d011682016040523d82523d6000602084013e610998565b606091505b50915091506109a9878383876109b4565b979650505050505050565b60608315610a23578251600003610a1c576001600160a01b0385163b610a1c5760405162461bcd60e51b815260206004820152601d60248201527f416464726573733a2063616c6c20746f206e6f6e2d636f6e74726163740000006044820152606401610502565b50816108ba565b6108ba8383815115610a385781518083602001fd5b8060405162461bcd60e51b81526004016105029190610ea4565b60008083601f840112610a6457600080fd5b50813567ffffffffffffffff811115610a7c57600080fd5b6020830191508360208260051b8501011115610a9757600080fd5b9250929050565b60008060208385031215610ab157600080fd5b823567ffffffffffffffff811115610ac857600080fd5b610ad485828601610a52565b90969095509350505050565b60008060008060408587031215610af657600080fd5b843567ffffffffffffffff80821115610b0e57600080fd5b610b1a88838901610a52565b90965094506020870135915080821115610b3357600080fd5b50610b4087828801610a52565b95989497509550505050565b600080600060408486031215610b6157600080fd5b833567ffffffffffffffff80821115610b7957600080fd5b818601915086601f830112610b8d57600080fd5b813581811115610b9c57600080fd5b87602060c083028501011115610bb157600080fd5b6020928301989097509590910135949350505050565b634e487b7160e01b600052603260045260246000fd5b80356001600160a01b0381168114610bf457600080fd5b919050565b600060208284031215610c0b57600080fd5b6107bc82610bdd565b600060208284031215610c2657600080fd5b5051919050565b634e487b7160e01b600052604160045260246000fd5b60405160c0810167ffffffffffffffff81118282101715610c6657610c66610c2d565b60405290565b604051601f8201601f1916810167ffffffffffffffff81118282101715610c9557610c95610c2d565b604052919050565b600060c08284031215610caf57600080fd5b610cb7610c43565b823560ff81168114610cc857600080fd5b8152610cd660208401610bdd565b6020820152610ce760408401610bdd565b6040820152610cf860608401610bdd565b6060820152608083013562ffffff81168114610d1357600080fd5b608082015260a0928301359281019290925250919050565b600060a082018783526020878185015260a0604085015281875180845260c086019150828901935060005b81811015610d7b5784516001600160a01b031683529383019391830191600101610d56565b50506001600160a01b03969096166060850152505050608001529392505050565b60006020808385031215610daf57600080fd5b825167ffffffffffffffff80821115610dc757600080fd5b818501915085601f830112610ddb57600080fd5b815181811115610ded57610ded610c2d565b8060051b9150610dfe848301610c6c565b8181529183018401918481019088841115610e1857600080fd5b938501935b83851015610e3657845182529385019390850190610e1d565b98975050505050505050565b600060208284031215610e5457600080fd5b815180151581146107bc57600080fd5b60005b83811015610e7f578181015183820152602001610e67565b50506000910152565b60008251610e9a818460208701610e64565b9190910192915050565b6020815260008251806020840152610ec3816040850160208701610e64565b601f01601f1916919091016040019291505056fea2646970667358221220e75668af077dbec1fec4d2e8edeec37cd59855fc788594733b9b89d21e48939064736f6c63430008140033",
    "sourceMap": "433:3979:25:-:0;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;1024:404;;;;;;:::i;:::-;;:::i;:::-;;1476:779;;;;;;:::i;:::-;;:::i;2323:876::-;;;;;;:::i;:::-;;:::i;:::-;;;2480:25:33;;;2468:2;2453:18;2323:876:25;;;;;;;1024:404;849:13;:11;:13::i;:::-;1123:6;1109:11:::1;1147:275;1164:6;1160:1;:10;1147:275;;;1188:13;1204:6;;1211:1;1204:9;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;1299:38;::::0;;;;1331:4:::1;1299:38;::::0;::::1;3243:74:33::0;1188:25:25;;-1:-1:-1;1227:124:25::1;::::0;1271:10:::1;::::0;-1:-1:-1;;;;;1299:23:25;::::1;::::0;::::1;::::0;3216:18:33;;1299:38:25::1;;;;;;;;;;;;;;;;;::::0;::::1;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;-1:-1:-1::0;;;;;1227:26:25;::::1;::::0;:124;:26:::1;:124::i;:::-;-1:-1:-1::0;1394:3:25::1;;1147:275;;;;1099:329;1024:404:::0;;:::o;1476:779::-;-1:-1:-1;;1788:6:25;1834:9;1726:11;1861:388;1878:12;1874:1;:16;1861:388;;;1908:12;1930:6;;1937:1;1930:9;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;1908:32;;1959:6;1954:225;1971:15;1967:1;:19;1954:225;;;2008:16;2027:9;;2037:1;2027:12;;;;;;;:::i;:::-;;;;;;;;;;;;;;:::i;:::-;2008:31;-1:-1:-1;2057:35:25;-1:-1:-1;;;;;2057:17:25;;2008:31;2085:6;2057:17;:35::i;:::-;-1:-1:-1;2143:3:25;;1954:225;;;-1:-1:-1;;2221:3:25;;1861:388;;;;1595:660;;;1476:779;;;;:::o;2323:876::-;2451:7;849:13;:11;:13::i;:::-;2470:17:::1;2525:11:::0;2470:17;2554:527:::1;2574:17;2570:1;:21;2554:527;;;2609:24;2636:11;;2648:1;2636:14;;;;;;;:::i;:::-;;;;;;2609:41;;;;;;;;;;:::i;:::-;;;2669:9;2682:1;2669:14:::0;2665:142:::1;;2715:6;:13;;;2703:25;;2665:142;;;2767:13;::::0;::::1;:25:::0;;;2665:142:::1;2825:15:::0;;:20:::1;;:15;:20:::0;2821:190:::1;;2877:21;2891:6;2877:13;:21::i;:::-;2865:33;;2821:190;;;2923:15:::0;;:20:::1;;2942:1;2923:20:::0;2919:92:::1;;2975:21;2989:6;2975:13;:21::i;:::-;2963:33;;2919:92;-1:-1:-1::0;3053:3:25::1;;2554:527;;;;3107:12;3095:9;:24;3091:75;;;3142:13;;;;;;;;;;;;;;3091:75;-1:-1:-1::0;3183:9:25;2323:876;-1:-1:-1;;;;2323:876:25:o;886:132::-;945:10;-1:-1:-1;;;;;959:5:25;945:19;;941:71;;987:14;;;;;;;;;;;;;;941:71;886:132::o;941:175:21:-;1050:58;;-1:-1:-1;;;;;5272:55:33;;1050:58:21;;;5254:74:33;5344:18;;;5337:34;;;1023:86:21;;1043:5;;1073:23;;5227:18:33;;1050:58:21;;;;-1:-1:-1;;1050:58:21;;;;;;;;;;;;;;;;;;;;;;;;;;;1023:19;:86::i;:::-;941:175;;;:::o;1818:573::-;2143:10;;;2142:62;;-1:-1:-1;2159:39:21;;;;;2183:4;2159:39;;;5617:34:33;-1:-1:-1;;;;;5687:15:33;;;5667:18;;;5660:43;2159:15:21;;;;;5529:18:33;;2159:39:21;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;:44;2142:62;2121:163;;;;-1:-1:-1;;;2121:163:21;;5916:2:33;2121:163:21;;;5898:21:33;5955:2;5935:18;;;5928:30;5994:34;5974:18;;;5967:62;6065:24;6045:18;;;6038:52;6107:19;;2121:163:21;;;;;;;;;2321:62;;-1:-1:-1;;;;;5272:55:33;;2321:62:21;;;5254:74:33;5344:18;;;5337:34;;;2294:90:21;;2314:5;;2344:22;;5227:18:33;;2321:62:21;5080:297:33;3225:523:25;3373:16;;;3387:1;3373:16;;;3335:21;3373:16;;;;;3306:17;;3335:21;;3373:16;3387:1;3373:16;;;;;;;;;;-1:-1:-1;3373:16:25;3366:23;;3409:6;:14;;;3399:4;3404:1;3399:7;;;;;;;;:::i;:::-;;;;;;:24;-1:-1:-1;;;;;3399:24:25;;;-1:-1:-1;;;;;3399:24:25;;;;;3443:6;:15;;;3433:4;3438:1;3433:7;;;;;;;;:::i;:::-;;;;;;:25;-1:-1:-1;;;;;3433:25:25;;;-1:-1:-1;;;;;3433:25:25;;;;;3469:21;3510:6;:14;;;-1:-1:-1;;;;;3493:70:25;;3581:6;:13;;;3612:1;3631:4;3661;3684:15;3493:220;;;;;;;;;;;;;;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;-1:-1:-1;;3493:220:25;;;;;;;;;;;;:::i;:::-;3469:244;;3731:7;3739:1;3731:10;;;;;;;;:::i;:::-;;;;;;;3724:17;;;;3225:523;;;:::o;3774:636::-;3941:381;;;;;;;;4016:14;;;;-1:-1:-1;;;;;3941:381:25;;;;;4058:15;;;;;3941:381;;;;;;;;;4096:10;;;;;3941:381;;;;;;;;;;4143:4;3941:381;;;;;;4176:15;3941:381;;;;;;;4219:13;;;;3941:381;;;;;;3855:17;3941:381;;;;;;;;;;;;4357:14;;;;4345:58;;;;;8421:13:33;;8417:22;;4345:58:25;;;8399:41:33;8482:24;;8478:33;;8456:20;;;8449:63;8554:24;;8550:39;;;8528:20;;;8521:69;8632:24;;8628:33;;8606:20;;;8599:63;8700:24;;8678:20;;;8671:54;8763:24;;8741:20;;;8734:54;8826:24;8804:20;;;8797:54;8893:24;8889:33;;8867:20;;;8860:63;3855:17:25;;4345:44;;;;;;8310:19:33;;4345:58:25;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;:::i;:::-;4333:70;3774:636;-1:-1:-1;;;3774:636:25:o;5173:642:21:-;5592:23;5618:69;5646:4;5618:69;;;;;;;;;;;;;;;;;5626:5;-1:-1:-1;;;;;5618:27:21;;;:69;;;;;:::i;:::-;5592:95;;5705:10;:17;5726:1;5705:22;:56;;;;5742:10;5731:30;;;;;;;;;;;;:::i;:::-;5697:111;;;;-1:-1:-1;;;5697:111:21;;9418:2:33;5697:111:21;;;9400:21:33;9457:2;9437:18;;;9430:30;9496:34;9476:18;;;9469:62;9567:12;9547:18;;;9540:40;9597:19;;5697:111:21;9216:406:33;4108:223:22;4241:12;4272:52;4294:6;4302:4;4308:1;4311:12;4272:21;:52::i;:::-;4265:59;4108:223;-1:-1:-1;;;;4108:223:22:o;5165:446::-;5330:12;5387:5;5362:21;:30;;5354:81;;;;-1:-1:-1;;;5354:81:22;;9829:2:33;5354:81:22;;;9811:21:33;9868:2;9848:18;;;9841:30;9907:34;9887:18;;;9880:62;9978:8;9958:18;;;9951:36;10004:19;;5354:81:22;9627:402:33;5354:81:22;5446:12;5460:23;5487:6;-1:-1:-1;;;;;5487:11:22;5506:5;5513:4;5487:31;;;;;;:::i;:::-;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;5445:73;;;;5535:69;5562:6;5570:7;5579:10;5591:12;5535:26;:69::i;:::-;5528:76;5165:446;-1:-1:-1;;;;;;;5165:446:22:o;7671:628::-;7851:12;7879:7;7875:418;;;7906:10;:17;7927:1;7906:22;7902:286;;-1:-1:-1;;;;;1702:19:22;;;8113:60;;;;-1:-1:-1;;;8113:60:22;;10783:2:33;8113:60:22;;;10765:21:33;10822:2;10802:18;;;10795:30;10861:31;10841:18;;;10834:59;10910:18;;8113:60:22;10581:353:33;8113:60:22;-1:-1:-1;8208:10:22;8201:17;;7875:418;8249:33;8257:10;8269:12;8980:17;;:21;8976:379;;9208:10;9202:17;9264:15;9251:10;9247:2;9243:19;9236:44;8976:379;9331:12;9324:20;;-1:-1:-1;;;9324:20:22;;;;;;;;:::i;14:367:33:-;77:8;87:6;141:3;134:4;126:6;122:17;118:27;108:55;;159:1;156;149:12;108:55;-1:-1:-1;182:20:33;;225:18;214:30;;211:50;;;257:1;254;247:12;211:50;294:4;286:6;282:17;270:29;;354:3;347:4;337:6;334:1;330:14;322:6;318:27;314:38;311:47;308:67;;;371:1;368;361:12;308:67;14:367;;;;;:::o;386:437::-;472:6;480;533:2;521:9;512:7;508:23;504:32;501:52;;;549:1;546;539:12;501:52;589:9;576:23;622:18;614:6;611:30;608:50;;;654:1;651;644:12;608:50;693:70;755:7;746:6;735:9;731:22;693:70;:::i;:::-;782:8;;667:96;;-1:-1:-1;386:437:33;-1:-1:-1;;;;386:437:33:o;828:773::-;950:6;958;966;974;1027:2;1015:9;1006:7;1002:23;998:32;995:52;;;1043:1;1040;1033:12;995:52;1083:9;1070:23;1112:18;1153:2;1145:6;1142:14;1139:34;;;1169:1;1166;1159:12;1139:34;1208:70;1270:7;1261:6;1250:9;1246:22;1208:70;:::i;:::-;1297:8;;-1:-1:-1;1182:96:33;-1:-1:-1;1385:2:33;1370:18;;1357:32;;-1:-1:-1;1401:16:33;;;1398:36;;;1430:1;1427;1420:12;1398:36;;1469:72;1533:7;1522:8;1511:9;1507:24;1469:72;:::i;:::-;828:773;;;;-1:-1:-1;1560:8:33;-1:-1:-1;;;;828:773:33:o;1606:723::-;1732:6;1740;1748;1801:2;1789:9;1780:7;1776:23;1772:32;1769:52;;;1817:1;1814;1807:12;1769:52;1857:9;1844:23;1886:18;1927:2;1919:6;1916:14;1913:34;;;1943:1;1940;1933:12;1913:34;1981:6;1970:9;1966:22;1956:32;;2026:7;2019:4;2015:2;2011:13;2007:27;1997:55;;2048:1;2045;2038:12;1997:55;2088:2;2075:16;2114:2;2106:6;2103:14;2100:34;;;2130:1;2127;2120:12;2100:34;2188:7;2181:4;2173;2165:6;2161:17;2157:2;2153:26;2149:37;2146:50;2143:70;;;2209:1;2206;2199:12;2143:70;2240:4;2232:13;;;;2264:6;;-1:-1:-1;2302:20:33;;;;2289:34;;1606:723;-1:-1:-1;;;;1606:723:33:o;2516:184::-;-1:-1:-1;;;2565:1:33;2558:88;2665:4;2662:1;2655:15;2689:4;2686:1;2679:15;2705:196;2773:20;;-1:-1:-1;;;;;2822:54:33;;2812:65;;2802:93;;2891:1;2888;2881:12;2802:93;2705:196;;;:::o;2906:186::-;2965:6;3018:2;3006:9;2997:7;2993:23;2989:32;2986:52;;;3034:1;3031;3024:12;2986:52;3057:29;3076:9;3057:29;:::i;3328:184::-;3398:6;3451:2;3439:9;3430:7;3426:23;3422:32;3419:52;;;3467:1;3464;3457:12;3419:52;-1:-1:-1;3490:16:33;;3328:184;-1:-1:-1;3328:184:33:o;3517:::-;-1:-1:-1;;;3566:1:33;3559:88;3666:4;3663:1;3656:15;3690:4;3687:1;3680:15;3706:252;3778:2;3772:9;3820:3;3808:16;;3854:18;3839:34;;3875:22;;;3836:62;3833:88;;;3901:18;;:::i;:::-;3937:2;3930:22;3706:252;:::o;3963:275::-;4034:2;4028:9;4099:2;4080:13;;-1:-1:-1;;4076:27:33;4064:40;;4134:18;4119:34;;4155:22;;;4116:62;4113:88;;;4181:18;;:::i;:::-;4217:2;4210:22;3963:275;;-1:-1:-1;3963:275:33:o;4243:832::-;4331:6;4384:3;4372:9;4363:7;4359:23;4355:33;4352:53;;;4401:1;4398;4391:12;4352:53;4427:22;;:::i;:::-;4486:9;4473:23;4540:4;4531:7;4527:18;4518:7;4515:31;4505:59;;4560:1;4557;4550:12;4505:59;4573:22;;4627:38;4661:2;4646:18;;4627:38;:::i;:::-;4622:2;4615:5;4611:14;4604:62;4698:38;4732:2;4721:9;4717:18;4698:38;:::i;:::-;4693:2;4686:5;4682:14;4675:62;4769:38;4803:2;4792:9;4788:18;4769:38;:::i;:::-;4764:2;4757:5;4753:14;4746:62;4860:3;4849:9;4845:19;4832:33;4909:8;4900:7;4896:22;4887:7;4884:35;4874:63;;4933:1;4930;4923:12;4874:63;4964:3;4953:15;;4946:32;5039:3;5024:19;;;5011:33;4994:15;;;4987:58;;;;-1:-1:-1;4957:5:33;4243:832;-1:-1:-1;4243:832:33:o;6137:1026::-;6399:4;6447:3;6436:9;6432:19;6478:6;6467:9;6460:25;6504:2;6542:6;6537:2;6526:9;6522:18;6515:34;6585:3;6580:2;6569:9;6565:18;6558:31;6609:6;6644;6638:13;6675:6;6667;6660:22;6713:3;6702:9;6698:19;6691:26;;6752:2;6744:6;6740:15;6726:29;;6773:1;6783:218;6797:6;6794:1;6791:13;6783:218;;;6862:13;;-1:-1:-1;;;;;6858:62:33;6846:75;;6976:15;;;;6941:12;;;;6819:1;6812:9;6783:218;;;-1:-1:-1;;;;;;;7057:55:33;;;;7052:2;7037:18;;7030:83;-1:-1:-1;;;7144:3:33;7129:19;7122:35;7018:3;6137:1026;-1:-1:-1;;;6137:1026:33:o;7168:936::-;7263:6;7294:2;7337;7325:9;7316:7;7312:23;7308:32;7305:52;;;7353:1;7350;7343:12;7305:52;7386:9;7380:16;7415:18;7456:2;7448:6;7445:14;7442:34;;;7472:1;7469;7462:12;7442:34;7510:6;7499:9;7495:22;7485:32;;7555:7;7548:4;7544:2;7540:13;7536:27;7526:55;;7577:1;7574;7567:12;7526:55;7606:2;7600:9;7628:2;7624;7621:10;7618:36;;;7634:18;;:::i;:::-;7680:2;7677:1;7673:10;7663:20;;7703:28;7727:2;7723;7719:11;7703:28;:::i;:::-;7765:15;;;7835:11;;;7831:20;;;7796:12;;;;7863:19;;;7860:39;;;7895:1;7892;7885:12;7860:39;7919:11;;;;7939:135;7955:6;7950:3;7947:15;7939:135;;;8021:10;;8009:23;;7972:12;;;;8052;;;;7939:135;;;8093:5;7168:936;-1:-1:-1;;;;;;;;7168:936:33:o;8934:277::-;9001:6;9054:2;9042:9;9033:7;9029:23;9025:32;9022:52;;;9070:1;9067;9060:12;9022:52;9102:9;9096:16;9155:5;9148:13;9141:21;9134:5;9131:32;9121:60;;9177:1;9174;9167:12;10034:250;10119:1;10129:113;10143:6;10140:1;10137:13;10129:113;;;10219:11;;;10213:18;10200:11;;;10193:39;10165:2;10158:10;10129:113;;;-1:-1:-1;;10276:1:33;10258:16;;10251:27;10034:250::o;10289:287::-;10418:3;10456:6;10450:13;10472:66;10531:6;10526:3;10519:4;10511:6;10507:17;10472:66;:::i;:::-;10554:16;;;;;10289:287;-1:-1:-1;;10289:287:33:o;10939:396::-;11088:2;11077:9;11070:21;11051:4;11120:6;11114:13;11163:6;11158:2;11147:9;11143:18;11136:34;11179:79;11251:6;11246:2;11235:9;11231:18;11226:2;11218:6;11214:15;11179:79;:::i;:::-;11319:2;11298:15;-1:-1:-1;;11294:29:33;11279:45;;;;11326:2;11275:54;;10939:396;-1:-1:-1;;10939:396:33:o",
    "linkReferences": {},
    "immutableReferences": {
      "30835": [
        {
          "start": 762,
          "length": 32
        }
      ]
    }
  },
  "methodIdentifiers": {
    "approveHandlers(address[],address[])": "bc2078fb",
    "recoverTokens(address[])": "9c832f93",
    "whack((uint8,address,address,address,uint24,uint256)[],uint256)": "c0f757c0"
  },
  "rawMetadata": "{\"compiler\":{\"version\":\"0.8.20+commit.a1b79de6\"},\"language\":\"Solidity\",\"output\":{\"abi\":[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"inputs\":[],\"name\":\"TradeFailed\",\"type\":\"error\"},{\"inputs\":[],\"name\":\"Unauthorized\",\"type\":\"error\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"},{\"internalType\":\"address[]\",\"name\":\"protocols\",\"type\":\"address[]\"}],\"name\":\"approveHandlers\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"internalType\":\"address[]\",\"name\":\"tokens\",\"type\":\"address[]\"}],\"name\":\"recoverTokens\",\"outputs\":[],\"stateMutability\":\"payable\",\"type\":\"function\"},{\"inputs\":[{\"components\":[{\"internalType\":\"uint8\",\"name\":\"protocol\",\"type\":\"uint8\"},{\"internalType\":\"address\",\"name\":\"handler\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenIn\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"tokenOut\",\"type\":\"address\"},{\"internalType\":\"uint24\",\"name\":\"fee\",\"type\":\"uint24\"},{\"internalType\":\"uint256\",\"name\":\"amount\",\"type\":\"uint256\"}],\"internalType\":\"struct WhackAMoleBotV1.SwapParams[]\",\"name\":\"paramsArray\",\"type\":\"tuple[]\"},{\"internalType\":\"uint256\",\"name\":\"minAmountOut\",\"type\":\"uint256\"}],\"name\":\"whack\",\"outputs\":[{\"internalType\":\"uint256\",\"name\":\"\",\"type\":\"uint256\"}],\"stateMutability\":\"payable\",\"type\":\"function\"}],\"devdoc\":{\"kind\":\"dev\",\"methods\":{},\"version\":1},\"userdoc\":{\"kind\":\"user\",\"methods\":{},\"version\":1}},\"settings\":{\"compilationTarget\":{\"src/WhackAMoleBotV1.sol\":\"WhackAMoleBotV1\"},\"evmVersion\":\"paris\",\"libraries\":{},\"metadata\":{\"bytecodeHash\":\"ipfs\"},\"optimizer\":{\"enabled\":true,\"runs\":1000},\"remappings\":[\":ds-test/=lib/forge-std/lib/ds-test/src/\",\":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/\",\":forge-std/=lib/forge-std/src/\",\":openzeppelin-contracts/=lib/openzeppelin-contracts/\",\":openzeppelin/=lib/openzeppelin-contracts/contracts/\"]},\"sources\":{\"lib/openzeppelin-contracts/contracts/token/erc20/IERC20.sol\":{\"keccak256\":\"0x287b55befed2961a7eabd7d7b1b2839cbca8a5b80ef8dcbb25ed3d4c2002c305\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://bd39944e8fc06be6dbe2dd1d8449b5336e23c6a7ba3e8e9ae5ae0f37f35283f5\",\"dweb:/ipfs/QmPV3FGYjVwvKSgAXKUN3r9T9GwniZz83CxBpM7vyj2G53\"]},\"lib/openzeppelin-contracts/contracts/token/erc20/extensions/IERC20Permit.sol\":{\"keccak256\":\"0xec63854014a5b4f2b3290ab9103a21bdf902a508d0f41a8573fea49e98bf571a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://bc5b5dc12fbc4002f282eaa7a5f06d8310ed62c1c77c5770f6283e058454c39a\",\"dweb:/ipfs/Qme9rE2wS3yBuyJq9GgbmzbsBQsW2M2sVFqYYLw7bosGrv\"]},\"lib/openzeppelin-contracts/contracts/token/erc20/utils/SafeERC20.sol\":{\"keccak256\":\"0x909d608c2db6eb165ca178c81289a07ed2e118e444d0025b2a85c97d0b44a4fa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://656cda26512ddd7373c2d5551c8fae759fc30f05b10f0fc2e738e9274199dbd4\",\"dweb:/ipfs/QmTSArSzQRFbQmHgq7U1PZXnsDFhvDZhKVu9CzMG4yo6Lx\"]},\"lib/openzeppelin-contracts/contracts/utils/Address.sol\":{\"keccak256\":\"0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931\",\"dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm\"]},\"lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol\":{\"keccak256\":\"0x58b21219689909c4f8339af00813760337f7e2e7f169a97fe49e2896dcfb3b9a\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://ef8e012e946dec20e59f2d4446f4b44bb098f3fa8bac103b1b5112fff777447b\",\"dweb:/ipfs/QmVTooKWcLkJ9W68yNX4MgdrbAKiAXwuRN9A7f4NkdcdtQ\"]},\"src/WhackAMoleBotV1.sol\":{\"keccak256\":\"0x0eba973ac4a6a9f292beb8c0315d72a9491e28889a4dd4279e1f204d265d80c7\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://3f04c123d12ab9260b71cbf597e32730654c5b8faa026f062035245c1d861533\",\"dweb:/ipfs/QmRV6R2nc7ZUMQM6Cvudb7nSPJx85zXMyUAeDmzFLkMNcf\"]},\"src/protocols/uniswap/IUniswapV2Router.sol\":{\"keccak256\":\"0x2a86f62d96fd59a10d98e361056e3780cb1b94a758157b8f5d79ab865bb30a5c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://d055709a7bdc57be7298bafd9a87c60071c06f68192aac1eeb3353615d03ee2b\",\"dweb:/ipfs/Qmdtojg2PJhdzQVtevYJDPG1ggTLmwJgHqcZ555eDC6xbq\"]},\"src/protocols/uniswap/IUniswapV3SwapRouter.sol\":{\"keccak256\":\"0x7ca2f2c38b3d4e2d9fc8fe55ab8ce482b448bb9618910a6ad4e18d8873b7389c\",\"license\":\"MIT\",\"urls\":[\"bzz-raw://0f5d5e3aeb92e4e163a7dbb39dd66cbd9c9ca9714c48741530a14d57fbab5ed5\",\"dweb:/ipfs/QmaHyVhkBtoTm4VQHu4b3tXSpRDFmP6BDoqCseUbaKfVBT\"]}},\"version\":1}",
  "metadata": {
    "compiler": {
      "version": "0.8.20+commit.a1b79de6"
    },
    "language": "Solidity",
    "output": {
      "abi": [
        {
          "inputs": [],
          "stateMutability": "nonpayable",
          "type": "constructor"
        },
        {
          "inputs": [],
          "type": "error",
          "name": "TradeFailed"
        },
        {
          "inputs": [],
          "type": "error",
          "name": "Unauthorized"
        },
        {
          "inputs": [
            {
              "internalType": "address[]",
              "name": "tokens",
              "type": "address[]"
            },
            {
              "internalType": "address[]",
              "name": "protocols",
              "type": "address[]"
            }
          ],
          "stateMutability": "payable",
          "type": "function",
          "name": "approveHandlers"
        },
        {
          "inputs": [
            {
              "internalType": "address[]",
              "name": "tokens",
              "type": "address[]"
            }
          ],
          "stateMutability": "payable",
          "type": "function",
          "name": "recoverTokens"
        },
        {
          "inputs": [
            {
              "internalType": "struct WhackAMoleBotV1.SwapParams[]",
              "name": "paramsArray",
              "type": "tuple[]",
              "components": [
                {
                  "internalType": "uint8",
                  "name": "protocol",
                  "type": "uint8"
                },
                {
                  "internalType": "address",
                  "name": "handler",
                  "type": "address"
                },
                {
                  "internalType": "address",
                  "name": "tokenIn",
                  "type": "address"
                },
                {
                  "internalType": "address",
                  "name": "tokenOut",
                  "type": "address"
                },
                {
                  "internalType": "uint24",
                  "name": "fee",
                  "type": "uint24"
                },
                {
                  "internalType": "uint256",
                  "name": "amount",
                  "type": "uint256"
                }
              ]
            },
            {
              "internalType": "uint256",
              "name": "minAmountOut",
              "type": "uint256"
            }
          ],
          "stateMutability": "payable",
          "type": "function",
          "name": "whack",
          "outputs": [
            {
              "internalType": "uint256",
              "name": "",
              "type": "uint256"
            }
          ]
        }
      ],
      "devdoc": {
        "kind": "dev",
        "methods": {},
        "version": 1
      },
      "userdoc": {
        "kind": "user",
        "methods": {},
        "version": 1
      }
    },
    "settings": {
      "remappings": [
        ":ds-test/=lib/forge-std/lib/ds-test/src/",
        ":erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/",
        ":forge-std/=lib/forge-std/src/",
        ":openzeppelin-contracts/=lib/openzeppelin-contracts/",
        ":openzeppelin/=lib/openzeppelin-contracts/contracts/"
      ],
      "optimizer": {
        "enabled": true,
        "runs": 1000
      },
      "metadata": {
        "bytecodeHash": "ipfs"
      },
      "compilationTarget": {
        "src/WhackAMoleBotV1.sol": "WhackAMoleBotV1"
      },
      "libraries": {}
    },
    "sources": {
      "lib/openzeppelin-contracts/contracts/token/erc20/IERC20.sol": {
        "keccak256": "0x287b55befed2961a7eabd7d7b1b2839cbca8a5b80ef8dcbb25ed3d4c2002c305",
        "urls": [
          "bzz-raw://bd39944e8fc06be6dbe2dd1d8449b5336e23c6a7ba3e8e9ae5ae0f37f35283f5",
          "dweb:/ipfs/QmPV3FGYjVwvKSgAXKUN3r9T9GwniZz83CxBpM7vyj2G53"
        ],
        "license": "MIT"
      },
      "lib/openzeppelin-contracts/contracts/token/erc20/extensions/IERC20Permit.sol": {
        "keccak256": "0xec63854014a5b4f2b3290ab9103a21bdf902a508d0f41a8573fea49e98bf571a",
        "urls": [
          "bzz-raw://bc5b5dc12fbc4002f282eaa7a5f06d8310ed62c1c77c5770f6283e058454c39a",
          "dweb:/ipfs/Qme9rE2wS3yBuyJq9GgbmzbsBQsW2M2sVFqYYLw7bosGrv"
        ],
        "license": "MIT"
      },
      "lib/openzeppelin-contracts/contracts/token/erc20/utils/SafeERC20.sol": {
        "keccak256": "0x909d608c2db6eb165ca178c81289a07ed2e118e444d0025b2a85c97d0b44a4fa",
        "urls": [
          "bzz-raw://656cda26512ddd7373c2d5551c8fae759fc30f05b10f0fc2e738e9274199dbd4",
          "dweb:/ipfs/QmTSArSzQRFbQmHgq7U1PZXnsDFhvDZhKVu9CzMG4yo6Lx"
        ],
        "license": "MIT"
      },
      "lib/openzeppelin-contracts/contracts/utils/Address.sol": {
        "keccak256": "0x006dd67219697fe68d7fbfdea512e7c4cb64a43565ed86171d67e844982da6fa",
        "urls": [
          "bzz-raw://2455248c8ddd9cc6a7af76a13973cddf222072427e7b0e2a7d1aff345145e931",
          "dweb:/ipfs/QmfYjnjRbWqYpuxurqveE6HtzsY1Xx323J428AKQgtBJZm"
        ],
        "license": "MIT"
      },
      "lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol": {
        "keccak256": "0x58b21219689909c4f8339af00813760337f7e2e7f169a97fe49e2896dcfb3b9a",
        "urls": [
          "bzz-raw://ef8e012e946dec20e59f2d4446f4b44bb098f3fa8bac103b1b5112fff777447b",
          "dweb:/ipfs/QmVTooKWcLkJ9W68yNX4MgdrbAKiAXwuRN9A7f4NkdcdtQ"
        ],
        "license": "MIT"
      },
      "src/WhackAMoleBotV1.sol": {
        "keccak256": "0x0eba973ac4a6a9f292beb8c0315d72a9491e28889a4dd4279e1f204d265d80c7",
        "urls": [
          "bzz-raw://3f04c123d12ab9260b71cbf597e32730654c5b8faa026f062035245c1d861533",
          "dweb:/ipfs/QmRV6R2nc7ZUMQM6Cvudb7nSPJx85zXMyUAeDmzFLkMNcf"
        ],
        "license": "MIT"
      },
      "src/protocols/uniswap/IUniswapV2Router.sol": {
        "keccak256": "0x2a86f62d96fd59a10d98e361056e3780cb1b94a758157b8f5d79ab865bb30a5c",
        "urls": [
          "bzz-raw://d055709a7bdc57be7298bafd9a87c60071c06f68192aac1eeb3353615d03ee2b",
          "dweb:/ipfs/Qmdtojg2PJhdzQVtevYJDPG1ggTLmwJgHqcZ555eDC6xbq"
        ],
        "license": "MIT"
      },
      "src/protocols/uniswap/IUniswapV3SwapRouter.sol": {
        "keccak256": "0x7ca2f2c38b3d4e2d9fc8fe55ab8ce482b448bb9618910a6ad4e18d8873b7389c",
        "urls": [
          "bzz-raw://0f5d5e3aeb92e4e163a7dbb39dd66cbd9c9ca9714c48741530a14d57fbab5ed5",
          "dweb:/ipfs/QmaHyVhkBtoTm4VQHu4b3tXSpRDFmP6BDoqCseUbaKfVBT"
        ],
        "license": "MIT"
      }
    },
    "version": 1
  },
  "ast": {
    "absolutePath": "src/WhackAMoleBotV1.sol",
    "id": 31205,
    "exportedSymbols": {
      "Address": [
        30227
      ],
      "IERC20": [
        29521
      ],
      "IERC20Permit": [
        32363
      ],
      "ISwapRouter": [
        31832
      ],
      "IUniswapV2Router": [
        31783
      ],
      "SafeERC20": [
        29897
      ],
      "SafeMath": [
        30539
      ],
      "WhackAMoleBotV1": [
        31204
      ]
    },
    "nodeType": "SourceUnit",
    "src": "32:4381:25",
    "nodes": [
      {
        "id": 30824,
        "nodeType": "PragmaDirective",
        "src": "32:23:25",
        "nodes": [],
        "literals": [
          "solidity",
          "^",
          "0.8",
          ".9"
        ]
      },
      {
        "id": 30825,
        "nodeType": "ImportDirective",
        "src": "57:66:25",
        "nodes": [],
        "absolutePath": "lib/openzeppelin-contracts/contracts/utils/math/SafeMath.sol",
        "file": "openzeppelin-contracts/contracts/utils/math/SafeMath.sol",
        "nameLocation": "-1:-1:-1",
        "scope": 31205,
        "sourceUnit": 30540,
        "symbolAliases": [],
        "unitAlias": ""
      },
      {
        "id": 30826,
        "nodeType": "ImportDirective",
        "src": "124:65:25",
        "nodes": [],
        "absolutePath": "lib/openzeppelin-contracts/contracts/token/erc20/IERC20.sol",
        "file": "openzeppelin-contracts/contracts/token/erc20/IERC20.sol",
        "nameLocation": "-1:-1:-1",
        "scope": 31205,
        "sourceUnit": 29522,
        "symbolAliases": [],
        "unitAlias": ""
      },
      {
        "id": 30827,
        "nodeType": "ImportDirective",
        "src": "190:74:25",
        "nodes": [],
        "absolutePath": "lib/openzeppelin-contracts/contracts/token/erc20/utils/SafeERC20.sol",
        "file": "openzeppelin-contracts/contracts/token/erc20/utils/SafeERC20.sol",
        "nameLocation": "-1:-1:-1",
        "scope": 31205,
        "sourceUnit": 29898,
        "symbolAliases": [],
        "unitAlias": ""
      },
      {
        "id": 30828,
        "nodeType": "ImportDirective",
        "src": "266:50:25",
        "nodes": [],
        "absolutePath": "src/protocols/uniswap/IUniswapV2Router.sol",
        "file": "./protocols/uniswap/IUniswapV2Router.sol",
        "nameLocation": "-1:-1:-1",
        "scope": 31205,
        "sourceUnit": 31784,
        "symbolAliases": [],
        "unitAlias": ""
      },
      {
        "id": 30829,
        "nodeType": "ImportDirective",
        "src": "317:54:25",
        "nodes": [],
        "absolutePath": "src/protocols/uniswap/IUniswapV3SwapRouter.sol",
        "file": "./protocols/uniswap/IUniswapV3SwapRouter.sol",
        "nameLocation": "-1:-1:-1",
        "scope": 31205,
        "sourceUnit": 31833,
        "symbolAliases": [],
        "unitAlias": ""
      },
      {
        "id": 31204,
        "nodeType": "ContractDefinition",
        "src": "433:3979:25",
        "nodes": [
          {
            "id": 30833,
            "nodeType": "UsingForDirective",
            "src": "464:27:25",
            "nodes": [],
            "global": false,
            "libraryName": {
              "id": 30830,
              "name": "SafeERC20",
              "nameLocations": [
                "470:9:25"
              ],
              "nodeType": "IdentifierPath",
              "referencedDeclaration": 29897,
              "src": "470:9:25"
            },
            "typeName": {
              "id": 30832,
              "nodeType": "UserDefinedTypeName",
              "pathNode": {
                "id": 30831,
                "name": "IERC20",
                "nameLocations": [
                  "484:6:25"
                ],
                "nodeType": "IdentifierPath",
                "referencedDeclaration": 29521,
                "src": "484:6:25"
              },
              "referencedDeclaration": 29521,
              "src": "484:6:25",
              "typeDescriptions": {
                "typeIdentifier": "t_contract$_IERC20_$29521",
                "typeString": "contract IERC20"
              }
            }
          },
          {
            "id": 30835,
            "nodeType": "VariableDeclaration",
            "src": "497:32:25",
            "nodes": [],
            "constant": false,
            "mutability": "immutable",
            "name": "owner",
            "nameLocation": "524:5:25",
            "scope": 31204,
            "stateVariable": true,
            "storageLocation": "default",
            "typeDescriptions": {
              "typeIdentifier": "t_address",
              "typeString": "address"
            },
            "typeName": {
              "id": 30834,
              "name": "address",
              "nodeType": "ElementaryTypeName",
              "src": "497:7:25",
              "stateMutability": "nonpayable",
              "typeDescriptions": {
                "typeIdentifier": "t_address",
                "typeString": "address"
              }
            },
            "visibility": "internal"
          },
          {
            "id": 30848,
            "nodeType": "StructDefinition",
            "src": "536:169:25",
            "nodes": [],
            "canonicalName": "WhackAMoleBotV1.SwapParams",
            "members": [
              {
                "constant": false,
                "id": 30837,
                "mutability": "mutable",
                "name": "protocol",
                "nameLocation": "570:8:25",
                "nodeType": "VariableDeclaration",
                "scope": 30848,
                "src": "564:14:25",
                "stateVariable": false,
                "storageLocation": "default",
                "typeDescriptions": {
                  "typeIdentifier": "t_uint8",
                  "typeString": "uint8"
                },
                "typeName": {
                  "id": 30836,
                  "name": "uint8",
                  "nodeType": "ElementaryTypeName",
                  "src": "564:5:25",
                  "typeDescriptions": {
                    "typeIdentifier": "t_uint8",
                    "typeString": "uint8"
                  }
                },
                "visibility": "internal"
              },
              {
                "constant": false,
                "id": 30839,
                "mutability": "mutable",
                "name": "handler",
                "nameLocation": "596:7:25",
                "nodeType": "VariableDeclaration",
                "scope": 30848,
                "src": "588:15:25",
                "stateVariable": false,
                "storageLocation": "default",
                "typeDescriptions": {
                  "typeIdentifier": "t_address",
                  "typeString": "address"
                },
                "typeName": {
                  "id": 30838,
                  "name": "address",
                  "nodeType": "ElementaryTypeName",
                  "src": "588:7:25",
                  "stateMutability": "nonpayable",
                  "typeDescriptions": {
                    "typeIdentifier": "t_address",
                    "typeString": "address"
                  }
                },
                "visibility": "internal"
              },
              {
                "constant": false,
                "id": 30841,
                "mutability": "mutable",
                "name": "tokenIn",
                "nameLocation": "621:7:25",
                "nodeType": "VariableDeclaration",
                "scope": 30848,
                "src": "613:15:25",
                "stateVariable": false,
                "storageLocation": "default",
                "typeDescriptions": {
                  "typeIdentifier": "t_address",
                  "typeString": "address"
                },
                "typeName": {
                  "id": 30840,
                  "name": "address",
                  "nodeType": "ElementaryTypeName",
                  "src": "613:7:25",
                  "stateMutability": "nonpayable",
                  "typeDescriptions": {
                    "typeIdentifier": "t_address",
                    "typeString": "address"
                  }
                },
                "visibility": "internal"
              },
              {
                "constant": false,
                "id": 30843,
                "mutability": "mutable",
                "name": "tokenOut",
                "nameLocation": "646:8:25",
                "nodeType": "VariableDeclaration",
                "scope": 30848,
                "src": "638:16:25",
                "stateVariable": false,
                "storageLocation": "default",
                "typeDescriptions": {
                  "typeIdentifier": "t_address",
                  "typeString": "address"
                },
                "typeName": {
                  "id": 30842,
                  "name": "address",
                  "nodeType": "ElementaryTypeName",
                  "src": "638:7:25",
                  "stateMutability": "nonpayable",
                  "typeDescriptions": {
                    "typeIdentifier": "t_address",
                    "typeString": "address"
                  }
                },
                "visibility": "internal"
              },
              {
                "constant": false,
                "id": 30845,
                "mutability": "mutable",
                "name": "fee",
                "nameLocation": "671:3:25",
                "nodeType": "VariableDeclaration",
                "scope": 30848,
                "src": "664:10:25",
                "stateVariable": false,
                "storageLocation": "default",
                "typeDescriptions": {
                  "typeIdentifier": "t_uint24",
                  "typeString": "uint24"
                },
                "typeName": {
                  "id": 30844,
                  "name": "uint24",
                  "nodeType": "ElementaryTypeName",
                  "src": "664:6:25",
                  "typeDescriptions": {
                    "typeIdentifier": "t_uint24",
                    "typeString": "uint24"
                  }
                },
                "visibility": "internal"
              },
              {
                "constant": false,
                "id": 30847,
                "mutability": "mutable",
                "name": "amount",
                "nameLocation": "692:6:25",
                "nodeType": "VariableDeclaration",
                "scope": 30848,
                "src": "684:14:25",
                "stateVariable": false,
                "storageLocation": "default",
                "typeDescriptions": {
                  "typeIdentifier": "t_uint256",
                  "typeString": "uint256"
                },
                "typeName": {
                  "id": 30846,
                  "name": "uint256",
                  "nodeType": "ElementaryTypeName",
                  "src": "684:7:25",
                  "typeDescriptions": {
                    "typeIdentifier": "t_uint256",
                    "typeString": "uint256"
                  }
                },
                "visibility": "internal"
              }
            ],
            "name": "SwapParams",
            "nameLocation": "543:10:25",
            "scope": 31204,
            "visibility": "public"
          },
          {
            "id": 30850,
            "nodeType": "ErrorDefinition",
            "src": "711:21:25",
            "nodes": [],
            "errorSelector": "82b42900",
            "name": "Unauthorized",
            "nameLocation": "717:12:25",
            "parameters": {
              "id": 30849,
              "nodeType": "ParameterList",
              "parameters": [],
              "src": "729:2:25"
            }
          },
          {
            "id": 30852,
            "nodeType": "ErrorDefinition",
            "src": "737:20:25",
            "nodes": [],
            "errorSelector": "2d8ef0cf",
            "name": "TradeFailed",
            "nameLocation": "743:11:25",
            "parameters": {
              "id": 30851,
              "nodeType": "ParameterList",
              "parameters": [],
              "src": "754:2:25"
            }
          },
          {
            "id": 30861,
            "nodeType": "FunctionDefinition",
            "src": "763:49:25",
            "nodes": [],
            "body": {
              "id": 30860,
              "nodeType": "Block",
              "src": "777:35:25",
              "nodes": [],
              "statements": [
                {
                  "expression": {
                    "id": 30858,
                    "isConstant": false,
                    "isLValue": false,
                    "isPure": false,
                    "lValueRequested": false,
                    "leftHandSide": {
                      "id": 30855,
                      "name": "owner",
                      "nodeType": "Identifier",
                      "overloadedDeclarations": [],
                      "referencedDeclaration": 30835,
                      "src": "787:5:25",
                      "typeDescriptions": {
                        "typeIdentifier": "t_address",
                        "typeString": "address"
                      }
                    },
                    "nodeType": "Assignment",
                    "operator": "=",
                    "rightHandSide": {
                      "expression": {
                        "id": 30856,
                        "name": "msg",
                        "nodeType": "Identifier",
                        "overloadedDeclarations": [],
                        "referencedDeclaration": -15,
                        "src": "795:3:25",
                        "typeDescriptions": {
                          "typeIdentifier": "t_magic_message",
                          "typeString": "msg"
                        }
                      },
                      "id": 30857,
                      "isConstant": false,
                      "isLValue": false,
                      "isPure": false,
                      "lValueRequested": false,
                      "memberLocation": "799:6:25",
                      "memberName": "sender",
                      "nodeType": "MemberAccess",
                      "src": "795:10:25",
                      "typeDescriptions": {
                        "typeIdentifier": "t_address",
                        "typeString": "address"
                      }
                    },
                    "src": "787:18:25",
                    "typeDescriptions": {
                      "typeIdentifier": "t_address",
                      "typeString": "address"
                    }
                  },
                  "id": 30859,
                  "nodeType": "ExpressionStatement",
                  "src": "787:18:25"
                }
              ]
            },
            "implemented": true,
            "kind": "constructor",
            "modifiers": [],
            "name": "",
            "nameLocation": "-1:-1:-1",
            "parameters": {
              "id": 30853,
              "nodeType": "ParameterList",
              "parameters": [],
              "src": "774:2:25"
            },
            "returnParameters": {
              "id": 30854,
              "nodeType": "ParameterList",
              "parameters": [],
              "src": "777:0:25"
            },
            "scope": 31204,
            "stateMutability": "nonpayable",
            "virtual": false,
            "visibility": "public"
          },
          {
            "id": 30868,
            "nodeType": "ModifierDefinition",
            "src": "818:62:25",
            "nodes": [],
            "body": {
              "id": 30867,
              "nodeType": "Block",
              "src": "839:41:25",
              "nodes": [],
              "statements": [
                {
                  "expression": {
                    "arguments": [],
                    "expression": {
                      "argumentTypes": [],
                      "id": 30863,
                      "name": "_checkOwner",
                      "nodeType": "Identifier",
                      "overloadedDeclarations": [],
                      "referencedDeclaration": 30881,
                      "src": "849:11:25",
                      "typeDescriptions": {
                        "typeIdentifier": "t_function_internal_view$__$returns$__$",
                        "typeString": "function () view"
                      }
                    },
                    "id": 30864,
                    "isConstant": false,
                    "isLValue": false,
                    "isPure": false,
                    "kind": "functionCall",
                    "lValueRequested": false,
                    "nameLocations": [],
                    "names": [],
                    "nodeType": "FunctionCall",
                    "src": "849:13:25",
                    "tryCall": false,
                    "typeDescriptions": {
                      "typeIdentifier": "t_tuple$__$",
                      "typeString": "tuple()"
                    }
                  },
                  "id": 30865,
                  "nodeType": "ExpressionStatement",
                  "src": "849:13:25"
                },
                {
                  "id": 30866,
                  "nodeType": "PlaceholderStatement",
                  "src": "872:1:25"
                }
              ]
            },
            "name": "onlyOwner",
            "nameLocation": "827:9:25",
            "parameters": {
              "id": 30862,
              "nodeType": "ParameterList",
              "parameters": [],
              "src": "836:2:25"
            },
            "virtual": false,
            "visibility": "internal"
          },
          {
            "id": 30881,
            "nodeType": "FunctionDefinition",
            "src": "886:132:25",
            "nodes": [],
            "body": {
              "id": 30880,
              "nodeType": "Block",
              "src": "931:87:25",
              "nodes": [],
              "statements": [
                {
                  "condition": {
                    "commonType": {
                      "typeIdentifier": "t_address",
                      "typeString": "address"
                    },
                    "id": 30874,
                    "isConstant": false,
                    "isLValue": false,
                    "isPure": false,
                    "lValueRequested": false,
                    "leftExpression": {
                      "expression": {
                        "id": 30871,
                        "name": "msg",
                        "nodeType": "Identifier",
                        "overloadedDeclarations": [],
                        "referencedDeclaration": -15,
                        "src": "945:3:25",
                        "typeDescriptions": {
                          "typeIdentifier": "t_magic_message",
                          "typeString": "msg"
                        }
                      },
                      "id": 30872,
                      "isConstant": false,
                      "isLValue": false,
                      "isPure": false,
                      "lValueRequested": false,
                      "memberLocation": "949:6:25",
                      "memberName": "sender",
                      "nodeType": "MemberAccess",
                      "src": "945:10:25",
                      "typeDescriptions": {
                        "typeIdentifier": "t_address",
                        "typeString": "address"
                      }
                    },
                    "nodeType": "BinaryOperation",
                    "operator": "!=",
                    "rightExpression": {
                      "id": 30873,
                      "name": "owner",
                      "nodeType": "Identifier",
                      "overloadedDeclarations": [],
                      "referencedDeclaration": 30835,
                      "src": "959:5:25",
                      "typeDescriptions": {
                        "typeIdentifier": "t_address",
                        "typeString": "address"
                      }
                    },
                    "src": "945:19:25",
                    "typeDescriptions": {
                      "typeIdentifier": "t_bool",
                      "typeString": "bool"
               
Download .txt
gitextract_xq82mp3m/

├── .gitignore
├── .gitmodules
├── README.md
├── abi/
│   ├── ERC20.json
│   ├── UniswapV2Pool.json
│   ├── UniswapV2Router2.json
│   ├── UniswapV3Pool.json
│   ├── UniswapV3Quoter2.json
│   ├── UniswapV3SwapRouter2.json
│   └── WETH.json
├── addresses/
│   ├── __init__.py
│   ├── arbitrum.py
│   ├── ethereum.py
│   └── polygon.py
├── configs.py
├── contracts/
│   ├── foundry.toml
│   ├── src/
│   │   ├── SimulatorV1.sol
│   │   ├── WhackAMoleBotV1.sol
│   │   ├── lib/
│   │   │   └── SafeTransfer.sol
│   │   └── protocols/
│   │       ├── IERC20.sol
│   │       ├── IWETH.sol
│   │       ├── curve/
│   │       │   └── ICurvePool.sol
│   │       └── uniswap/
│   │           ├── IQuoterV2.sol
│   │           ├── IUniswapV2Pair.sol
│   │           ├── IUniswapV2Router.sol
│   │           ├── IUniswapV3SwapRouter.sol
│   │           └── UniswapV2Library.sol
│   └── test/
│       └── WhackAMoleBotV1.t.sol
├── data/
│   ├── __init__.py
│   ├── cex.py
│   ├── cex_streams.py
│   ├── dex.py
│   ├── dex_streams.py
│   └── utils.py
├── examples/
│   └── dex.py
├── execution/
│   ├── WhackAMoleBotV1.json
│   ├── __init__.py
│   └── dex_order.py
├── external/
│   ├── __init__.py
│   ├── influxdb.py
│   └── telegram_bot.py
├── main.py
├── requirements.txt
├── simulation/
│   ├── SimulatorV1.json
│   ├── __init__.py
│   ├── online_simulator.py
│   ├── uniswap_v2.py
│   └── uniswap_v3.py
├── strategies/
│   └── dex_arb_base.py
└── tests/
    ├── test_WhackAMoleBotV1.py
    └── test_simulation.py
Download .txt
SYMBOL INDEX (100 symbols across 14 files)

FILE: data/cex.py
  class CEX (line 5) | class CEX:
    method __init__ (line 13) | def __init__(self, trading_symbols: List[str]):

FILE: data/cex_streams.py
  class CexStream (line 17) | class CexStream:
    method __init__ (line 28) | def __init__(self,
    method publish (line 37) | def publish(self, data: Any):
    method start_stream (line 41) | def start_stream(self):
    method stream_binance_usdm_orderbook (line 44) | async def stream_binance_usdm_orderbook(self):
    method stream_okx_usdm_orderbook (line 60) | async def stream_okx_usdm_orderbook(self):
    method stream_bybit_usdm_orderbook (line 75) | async def stream_bybit_usdm_orderbook(self):

FILE: data/dex.py
  class DexBase (line 29) | class DexBase:
    method __init__ (line 31) | def __init__(self,
    method load (line 124) | def load(self):
    method _load_pool_data (line 131) | def _load_pool_data(self):
    method _generate_swap_paths (line 208) | def _generate_swap_paths(self):
    method __sample_pools (line 293) | def __sample_pools(self, index_arr: np.ndarray, in_out: List[int]) -> ...
    method __generate_paths (line 337) | def __generate_paths(self, pool_samples: Dict[int, List[List[List[int]...
  class NoSymbolError (line 407) | class NoSymbolError(Exception):
    method __init__ (line 409) | def __init__(self, msg: str):
    method __str__ (line 412) | def __str__(self):
  class DEX (line 416) | class DEX(DexBase):
    method __init__ (line 418) | def __init__(self,
    method get_index (line 437) | def get_index(self,
    method get_price (line 451) | def get_price(self,
    method get_symbols_to_update (line 468) | def get_symbols_to_update(self, token0: str, token1: str) -> List[str]:
    method update_price_for_symbol (line 488) | def update_price_for_symbol(self, chain: str, symbol: str):
    method update_reserves (line 523) | def update_reserves(self,
    method update_sqrt_price (line 540) | def update_sqrt_price(self,
    method debug_message (line 553) | def debug_message(self,

FILE: data/dex_streams.py
  function default_message_format (line 24) | def default_message_format(symbol: str,
  class DexStream (line 53) | class DexStream:
    method __init__ (line 55) | def __init__(self,
    method publish (line 79) | def publish(self, data: Any):
    method start_streams (line 83) | def start_streams(self):
    method stream_uniswap_v2_events (line 107) | async def stream_uniswap_v2_events(self, chain: str):
    method stream_uniswap_v3_events (line 163) | async def stream_uniswap_v3_events(self, chain: str):
    method stream_new_blocks (line 220) | async def stream_new_blocks(self, chain: str):

FILE: data/utils.py
  function reconnecting_websocket_loop (line 7) | async def reconnecting_websocket_loop(stream_fn: Callable, tag: str):
  function calculate_next_block_base_fee (line 22) | def calculate_next_block_base_fee(block: Dict[str, Any]):

FILE: execution/dex_order.py
  class DexOrder (line 35) | class DexOrder:
    method __init__ (line 43) | def __init__(self,
    method send_bundle (line 87) | async def send_bundle(self,
    method transfer_in (line 134) | async def transfer_in(self,
    method transfer_out (line 160) | async def transfer_out(self,
    method approve_handlers (line 184) | async def approve_handlers(self,
    method make_params (line 212) | def make_params(self,
    method _make_buy_params (line 224) | def _make_buy_params(self,
    method _make_sell_params (line 261) | def _make_sell_params(self, path: List[List[int]], pools: List[int]):
    method send_order (line 293) | async def send_order(self,

FILE: external/influxdb.py
  class InfluxDB (line 15) | class InfluxDB:
    method __init__ (line 21) | def __init__(self,
    method send (line 43) | async def send(self, measurement: str, data: Dict[str, float]):
    method close (line 55) | async def close(self):
  function test_send (line 60) | async def test_send():

FILE: external/telegram_bot.py
  class Telegram (line 12) | class Telegram:
    method __init__ (line 14) | def __init__(self,
    method get_updates (line 27) | def get_updates(self):
    method send (line 35) | async def send(self, message: str):
  function test_send (line 40) | async def test_send():

FILE: simulation/online_simulator.py
  class OnlineSimulator (line 19) | class OnlineSimulator:
    method __init__ (line 26) | def __init__(self,
    method make_params (line 72) | def make_params(self,
    method _make_buy_params (line 84) | def _make_buy_params(self,
    method _make_sell_params (line 121) | def _make_sell_params(self, path: List[List[int]], pools: List[int]):
    method simulate (line 153) | def simulate(self, chain: str, params: List[Dict[str, Any]]) -> int:

FILE: simulation/uniswap_v2.py
  class UniswapV2Simulator (line 1) | class UniswapV2Simulator:
    method __init__ (line 3) | def __init__(self):
    method reserves_to_price (line 6) | def reserves_to_price(self,
    method get_amount_out (line 22) | def get_amount_out(self,
    method get_amount_in (line 42) | def get_amount_in(self,
    method get_max_amount_in (line 53) | def get_max_amount_in(self,

FILE: simulation/uniswap_v3.py
  class UniswapV3Simulator (line 5) | class UniswapV3Simulator:
    method __init__ (line 25) | def __init__(self):
    method sqrtx96_to_tick (line 28) | def sqrtx96_to_tick(self, sqrtx96: float):
    method sqrtx96_to_price (line 36) | def sqrtx96_to_price(self,
    method tick_to_price (line 50) | def tick_to_price(self,
    method tick_to_price_range (line 56) | def tick_to_price_range(self,
    method get_amount_out (line 71) | def get_amount_out(self):
    method get_amount_in (line 74) | def get_amount_in(self):

FILE: strategies/dex_arb_base.py
  function cycle_name (line 26) | def cycle_name(pools_1: List[int],
  function dex_stream_process (line 51) | def dex_stream_process(publisher: aioprocessing.AioQueue,
  class Pending (line 113) | class Pending:
    method __init__ (line 115) | def __init__(self):
    method can_add (line 118) | def can_add(self):
    method get_pending (line 125) | def get_pending(self):
    method add_pending (line 128) | def add_pending(self, pending: Dict[str, Any]):
    method set_order_processing (line 131) | def set_order_processing(self):
    method delete_pending (line 134) | def delete_pending(self):
  function strategy (line 138) | async def strategy(subscriber: aioprocessing.AioQueue,
  function main (line 413) | async def main():

FILE: tests/test_WhackAMoleBotV1.py
  class WhackAMoleBotV1Tests (line 21) | class WhackAMoleBotV1Tests(TestCase):
    method setUp (line 56) | def setUp(self):
    method test_recover_tokens (line 142) | def test_recover_tokens(self):
    method test_v2_swap (line 170) | def test_v2_swap(self):
    method test_v3_swap (line 207) | def test_v3_swap(self):
    method test_2_hop_swaps (line 243) | def test_2_hop_swaps(self):
    method test_n_hop_swap_gas (line 295) | def test_n_hop_swap_gas(self):
    method test_price_impact_simulation (line 366) | def test_price_impact_simulation(self):

FILE: tests/test_simulation.py
  class SimulationTests (line 22) | class SimulationTests(TestCase):
    method setUp (line 60) | def setUp(self):
    method test_sim_v3_get_amount_out (line 150) | def test_sim_v3_get_amount_out(self):
Condensed preview — 51 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (695K chars).
[
  {
    "path": ".gitignore",
    "chars": 298,
    "preview": "# Dotenv file\n.env\n\n# Mac\n.DS_Store\n\n# IDE\n.idea/\n\n# Cache / Env\nvenv/\n__pycache__/\n\n# Large files\n*.pyc\n*.zip\n*.csv\n\n# "
  },
  {
    "path": ".gitmodules",
    "chars": 275,
    "preview": "[submodule \"contracts/lib/forge-std\"]\n\tpath = contracts/lib/forge-std\n\turl = https://github.com/foundry-rs/forge-std\n[su"
  },
  {
    "path": "README.md",
    "chars": 3036,
    "preview": "# Whack-A-Mole\n\n<p align=\"center\">\n    <img src = \"https://github.com/solidquant/whack-a-mole/assets/134243834/841a91df-"
  },
  {
    "path": "abi/ERC20.json",
    "chars": 4840,
    "preview": "[\n    {\n        \"constant\": true,\n        \"inputs\": [],\n        \"name\": \"name\",\n        \"outputs\": [\n            {\n     "
  },
  {
    "path": "abi/UniswapV2Pool.json",
    "chars": 8279,
    "preview": "[{\"inputs\":[],\"payable\":false,\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexe"
  },
  {
    "path": "abi/UniswapV2Router2.json",
    "chars": 11875,
    "preview": "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_WETH\",\"type"
  },
  {
    "path": "abi/UniswapV3Pool.json",
    "chars": 11944,
    "preview": "[{\"inputs\":[],\"stateMutability\":\"nonpayable\",\"type\":\"constructor\"},{\"anonymous\":false,\"inputs\":[{\"indexed\":true,\"interna"
  },
  {
    "path": "abi/UniswapV3Quoter2.json",
    "chars": 3358,
    "preview": "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_factory\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"_WETH9\",\"typ"
  },
  {
    "path": "abi/UniswapV3SwapRouter2.json",
    "chars": 12833,
    "preview": "[{\"inputs\":[{\"internalType\":\"address\",\"name\":\"_factoryV2\",\"type\":\"address\"},{\"internalType\":\"address\",\"name\":\"factoryV3\""
  },
  {
    "path": "abi/WETH.json",
    "chars": 2723,
    "preview": "[{\"constant\":true,\"inputs\":[],\"name\":\"name\",\"outputs\":[{\"name\":\"\",\"type\":\"string\"}],\"payable\":false,\"stateMutability\":\"v"
  },
  {
    "path": "addresses/__init__.py",
    "chars": 498,
    "preview": "from addresses.arbitrum import TOKENS as ARBITRUM_TOKENS\nfrom addresses.arbitrum import POOLS as ARBITRUM_POOLS\n\nfrom ad"
  },
  {
    "path": "addresses/arbitrum.py",
    "chars": 912,
    "preview": "EXCHANGE = 'arbitrum'\n\nTOKENS = {\n    'ETH': ['0x82aF49447D8a07e3bd95BD0d56f35241523fBab1', 18],\n    'USDT': ['0xFd086bC"
  },
  {
    "path": "addresses/ethereum.py",
    "chars": 3071,
    "preview": "EXCHANGE = 'ethereum'\n\nTOKENS = {\n    'ETH': ['0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', 18],\n    'USDT': ['0xdAC17F9"
  },
  {
    "path": "addresses/polygon.py",
    "chars": 917,
    "preview": "EXCHANGE = 'polygon'\n\nTOKENS = {\n    'ETH': ['0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619', 18],\n    'USDT': ['0xc2132D05"
  },
  {
    "path": "configs.py",
    "chars": 941,
    "preview": "import os\nfrom dotenv import load_dotenv\n\nfrom addresses import (\n    ETHEREUM_TOKENS,\n    POLYGON_TOKENS,\n    ARBITRUM_"
  },
  {
    "path": "contracts/foundry.toml",
    "chars": 140,
    "preview": "[profile.default]\nsrc = \"src\"\nout = \"out\"\nlibs = [\"lib\"]\n\n# See more config options https://github.com/foundry-rs/foundr"
  },
  {
    "path": "contracts/src/SimulatorV1.sol",
    "chars": 3182,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\nimport \"openzeppelin-contracts/contracts/utils/math/SafeMath.so"
  },
  {
    "path": "contracts/src/WhackAMoleBotV1.sol",
    "chars": 4368,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\nimport \"openzeppelin-contracts/contracts/utils/math/SafeMath.so"
  },
  {
    "path": "contracts/src/lib/SafeTransfer.sol",
    "chars": 1198,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\nimport \"../protocols/IERC20.sol\";\n\nlibrary SafeTransfer {\n    f"
  },
  {
    "path": "contracts/src/protocols/IERC20.sol",
    "chars": 756,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\ninterface IERC20 {\n    function totalSupply() external view ret"
  },
  {
    "path": "contracts/src/protocols/IWETH.sol",
    "chars": 198,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\nimport \"./IERC20.sol\";\n\ninterface IWETH is IERC20 {\n    functio"
  },
  {
    "path": "contracts/src/protocols/curve/ICurvePool.sol",
    "chars": 2518,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\ninterface ICurvePool {\n    function get_virtual_price() externa"
  },
  {
    "path": "contracts/src/protocols/uniswap/IQuoterV2.sol",
    "chars": 4944,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\npragma abicoder v2;\n\n/// @title QuoterV2 Interface\n/// @notice S"
  },
  {
    "path": "contracts/src/protocols/uniswap/IUniswapV2Pair.sol",
    "chars": 2867,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\ninterface IUniswapV2Pair {\n    event Approval(\n        address "
  },
  {
    "path": "contracts/src/protocols/uniswap/IUniswapV2Router.sol",
    "chars": 513,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\ninterface IUniswapV2Router {\n    function swapExactTokensForTok"
  },
  {
    "path": "contracts/src/protocols/uniswap/IUniswapV3SwapRouter.sol",
    "chars": 1265,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\ninterface ISwapRouter {\n    struct ExactInputSingleParams {\n   "
  },
  {
    "path": "contracts/src/protocols/uniswap/UniswapV2Library.sol",
    "chars": 4944,
    "preview": "// SPDX-License-Identifier: MIT\npragma solidity ^0.8.9;\n\nimport \"./IUniswapV2Pair.sol\";\n\nimport \"openzeppelin-contracts/"
  },
  {
    "path": "contracts/test/WhackAMoleBotV1.t.sol",
    "chars": 557,
    "preview": "// SPDX-License-Identifier: UNLICENSED\npragma solidity ^0.8.9;\n\nimport \"forge-std/Test.sol\";\nimport \"../src/WhackAMoleBo"
  },
  {
    "path": "data/__init__.py",
    "chars": 53,
    "preview": "from data.dex import *\nfrom data.dex_streams import *"
  },
  {
    "path": "data/cex.py",
    "chars": 668,
    "preview": "import ccxt\nfrom typing import List\n\n\nclass CEX:\n    \"\"\"\n    Use CCXT to build CEX execution\n\n    The Python project is "
  },
  {
    "path": "data/cex_streams.py",
    "chars": 3358,
    "preview": "import json\nimport time\nimport eth_abi\nimport asyncio\nimport datetime\nimport eth_utils\nimport websockets\nimport numpy as"
  },
  {
    "path": "data/dex.py",
    "chars": 22412,
    "preview": "import numpy as np\nfrom web3 import Web3\nfrom typing import Any, Dict, List\nfrom multicall import Call, Multicall\n\nfrom "
  },
  {
    "path": "data/dex_streams.py",
    "chars": 12256,
    "preview": "import os\nimport json\nimport time\nimport eth_abi\nimport asyncio\nimport aiohttp\nimport datetime\nimport eth_utils\nimport w"
  },
  {
    "path": "data/utils.py",
    "chars": 1169,
    "preview": "import random\nimport asyncio\nimport websockets\nfrom typing import Any, Callable, Dict\n\n\nasync def reconnecting_websocket"
  },
  {
    "path": "examples/dex.py",
    "chars": 952,
    "preview": "import numpy as np\n\nfrom data.dex import DEX\nfrom configs import RPC_ENDPOINTS, TOKENS, POOLS\n\n\nif __name__ == '__main__"
  },
  {
    "path": "execution/WhackAMoleBotV1.json",
    "chars": 243276,
    "preview": "{\n  \"abi\": [\n    {\n      \"inputs\": [],\n      \"stateMutability\": \"nonpayable\",\n      \"type\": \"constructor\"\n    },\n    {\n "
  },
  {
    "path": "execution/__init__.py",
    "chars": 40,
    "preview": "from execution.dex_order import DexOrder"
  },
  {
    "path": "execution/dex_order.py",
    "chars": 13692,
    "preview": "import os\nimport json\nimport asyncio\nfrom web3 import Web3\nfrom uuid import uuid4\nfrom pathlib import Path\nfrom dotenv i"
  },
  {
    "path": "external/__init__.py",
    "chars": 67,
    "preview": "from external.influxdb import *\nfrom external.telegram_bot import *"
  },
  {
    "path": "external/influxdb.py",
    "chars": 1788,
    "preview": "import os\nfrom typing import Dict\nfrom dotenv import load_dotenv\nfrom influxdb_client import Point\nfrom influxdb_client."
  },
  {
    "path": "external/telegram_bot.py",
    "chars": 1095,
    "preview": "import os\nimport requests\nfrom telegram import Bot\nfrom dotenv import load_dotenv\n\nload_dotenv(override=True)\n\nTELEGRAM_"
  },
  {
    "path": "main.py",
    "chars": 155,
    "preview": "import asyncio\nimport nest_asyncio\n\nfrom strategies.dex_arb_base import main\n\n\nif __name__ == '__main__':\n    nest_async"
  },
  {
    "path": "requirements.txt",
    "chars": 1074,
    "preview": "aiohttp==3.8.5\naioprocessing==2.0.1\naiosignal==1.3.1\nanyio==3.7.1\nasync-timeout==4.0.2\nattrs==23.1.0\nbase58==2.1.1\nbitar"
  },
  {
    "path": "simulation/SimulatorV1.json",
    "chars": 187507,
    "preview": "{\n  \"abi\": [\n    {\n      \"inputs\": [],\n      \"stateMutability\": \"nonpayable\",\n      \"type\": \"constructor\"\n    },\n    {\n "
  },
  {
    "path": "simulation/__init__.py",
    "chars": 113,
    "preview": "from simulation.uniswap_v2 import *\nfrom simulation.uniswap_v3 import *\nfrom simulation.online_simulator import *"
  },
  {
    "path": "simulation/online_simulator.py",
    "chars": 7530,
    "preview": "import os\nimport json\nfrom web3 import Web3\nfrom pathlib import Path\nfrom typing import Any, Dict, List\n\nPROTOCOL_TO_ID "
  },
  {
    "path": "simulation/uniswap_v2.py",
    "chars": 5906,
    "preview": "class UniswapV2Simulator:\n\n    def __init__(self):\n        pass\n\n    def reserves_to_price(self,\n                       "
  },
  {
    "path": "simulation/uniswap_v3.py",
    "chars": 2736,
    "preview": "import math\nimport numpy as np\n\n\nclass UniswapV3Simulator:\n    \"\"\"\n    For the time being, V3 simulator limits swap simu"
  },
  {
    "path": "strategies/dex_arb_base.py",
    "chars": 17813,
    "preview": "import os\nimport time\nimport asyncio\nimport datetime\nimport aioprocessing\nfrom functools import partial\nfrom dotenv impo"
  },
  {
    "path": "tests/test_WhackAMoleBotV1.py",
    "chars": 14659,
    "preview": "import json\nfrom web3 import Web3\nfrom unittest import TestCase\nfrom eth_account.account import Account\n\nPOOL_V2_ABI = j"
  },
  {
    "path": "tests/test_simulation.py",
    "chars": 13655,
    "preview": "import json\nimport eth_abi\nimport datetime\nfrom web3 import Web3\nfrom unittest import TestCase\nfrom eth_account.account "
  }
]

About this extraction

This page contains the full source code of the solidquant/whack-a-mole GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 51 files (630.1 KB), approximately 145.7k tokens, and a symbol index with 100 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!