cryptointerface 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,307 @@
1
+ """
2
+ Aave V3 flash loan integration.
3
+
4
+ Usage
5
+ -----
6
+ 1. Deploy a contract that implements `executeOperation` (see the Solidity
7
+ template in `AaveFlashLoan.EXECUTOR_TEMPLATE`).
8
+ 2. Call `AaveFlashLoan.flash_loan_tx(...)` to build the initiating transaction.
9
+ 3. Sign and broadcast with `Wallet.sign_and_send(tx)`.
10
+
11
+ Flash-loan fee: 0.09 % (9 bps) on all Aave V3 deployments.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from web3 import Web3
17
+
18
+ # ---------------------------------------------------------------------------
19
+ # Aave V3 Pool addresses (PoolAddressesProvider-resolved Pool proxy)
20
+ # Chain IDs match the Network enum in periphery/enums.py
21
+ # ---------------------------------------------------------------------------
22
+ AAVE_V3_POOL: dict[str, str] = {
23
+ "1": "0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2", # Ethereum mainnet
24
+ "10": "0x794a61358D6845594F94dc1DB02A252b5b4814aD", # Optimism
25
+ "56": "0x6807dc923806fE8Fd134338EABCA509979a7e0cB", # BNB Chain
26
+ "137": "0x794a61358D6845594F94dc1DB02A252b5b4814aD", # Polygon
27
+ "8453": "0xA238Dd80C259a72e81d7e4664a9801593F98d1c5", # Base
28
+ "42161": "0x794a61358D6845594F94dc1DB02A252b5b4814aD", # Arbitrum One
29
+ "43114": "0x794a61358D6845594F94dc1DB02A252b5b4814aD", # Avalanche C-Chain
30
+ "11155111": "0x6Ae43d3271ff6888e7Fc43Fd7321a503ff738951", # Sepolia testnet
31
+ }
32
+
33
+ FLASH_LOAN_FEE_BPS = 9 # 0.09 %
34
+
35
+ # ---------------------------------------------------------------------------
36
+ # Minimal Aave V3 Pool ABI — only the flashLoan selector is needed.
37
+ # ---------------------------------------------------------------------------
38
+ _AAVE_POOL_ABI = [
39
+ {
40
+ "inputs": [
41
+ {"internalType": "address", "name": "receiverAddress", "type": "address"},
42
+ {"internalType": "address[]", "name": "assets", "type": "address[]"},
43
+ {"internalType": "uint256[]", "name": "amounts", "type": "uint256[]"},
44
+ {"internalType": "uint256[]", "name": "interestRateModes", "type": "uint256[]"},
45
+ {"internalType": "address", "name": "onBehalfOf", "type": "address"},
46
+ {"internalType": "bytes", "name": "params", "type": "bytes"},
47
+ {"internalType": "uint16", "name": "referralCode", "type": "uint16"},
48
+ ],
49
+ "name": "flashLoan",
50
+ "outputs": [],
51
+ "stateMutability": "nonpayable",
52
+ "type": "function",
53
+ },
54
+ {
55
+ "inputs": [
56
+ {"internalType": "address", "name": "receiverAddress", "type": "address"},
57
+ {"internalType": "address", "name": "asset", "type": "address"},
58
+ {"internalType": "uint256", "name": "amount", "type": "uint256"},
59
+ {"internalType": "uint256", "name": "interestRateMode","type": "uint256"},
60
+ {"internalType": "address", "name": "onBehalfOf", "type": "address"},
61
+ {"internalType": "bytes", "name": "params", "type": "bytes"},
62
+ {"internalType": "uint16", "name": "referralCode", "type": "uint16"},
63
+ ],
64
+ "name": "flashLoanSimple",
65
+ "outputs": [],
66
+ "stateMutability": "nonpayable",
67
+ "type": "function",
68
+ },
69
+ ]
70
+
71
+
72
+ class AaveFlashLoan:
73
+ """Build Aave V3 flash-loan transactions (unsigned)."""
74
+
75
+ # ------------------------------------------------------------------
76
+ # Solidity executor template — deploy this contract, fill in your
77
+ # arbitrage logic inside executeOperation, then pass its address as
78
+ # `receiver_contract` to flash_loan_tx / flash_loan_simple_tx.
79
+ # ------------------------------------------------------------------
80
+ EXECUTOR_TEMPLATE = """\
81
+ // SPDX-License-Identifier: MIT
82
+ pragma solidity ^0.8.20;
83
+
84
+ import {IFlashLoanReceiver} from "@aave/core-v3/contracts/flashloan/interfaces/IFlashLoanReceiver.sol";
85
+ import {IPoolAddressesProvider} from "@aave/core-v3/contracts/interfaces/IPoolAddressesProvider.sol";
86
+ import {IPool} from "@aave/core-v3/contracts/interfaces/IPool.sol";
87
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
88
+
89
+ /**
90
+ * @title FlashLoanExecutor
91
+ * @notice Receives Aave V3 flash loans and executes atomic arbitrage.
92
+ * Deploy once; call requestFlashLoan to trigger a loan.
93
+ */
94
+ contract FlashLoanExecutor is IFlashLoanReceiver {
95
+ IPoolAddressesProvider public immutable override ADDRESSES_PROVIDER;
96
+ IPool public immutable override POOL;
97
+ address public owner;
98
+
99
+ modifier onlyOwner() {
100
+ require(msg.sender == owner, "Not owner");
101
+ _;
102
+ }
103
+
104
+ constructor(address addressesProvider) {
105
+ ADDRESSES_PROVIDER = IPoolAddressesProvider(addressesProvider);
106
+ POOL = IPool(IPoolAddressesProvider(addressesProvider).getPool());
107
+ owner = msg.sender;
108
+ }
109
+
110
+ /**
111
+ * @notice Called by Aave Pool after sending `amounts` of `assets` to this contract.
112
+ * Must repay amounts[i] + premiums[i] for each asset before returning.
113
+ */
114
+ function executeOperation(
115
+ address[] calldata assets,
116
+ uint256[] calldata amounts,
117
+ uint256[] calldata premiums,
118
+ address /*initiator*/,
119
+ bytes calldata params
120
+ ) external override returns (bool) {
121
+ require(msg.sender == address(POOL), "Caller not Pool");
122
+
123
+ // ----------------------------------------------------------------
124
+ // INSERT YOUR ARBITRAGE LOGIC HERE
125
+ // params is ABI-encoded data passed from Python (e.g. swap routes).
126
+ // Example: decode buy/sell DEX router addresses and call swap().
127
+ // ----------------------------------------------------------------
128
+
129
+ // Approve repayment for each asset
130
+ for (uint256 i = 0; i < assets.length; i++) {
131
+ uint256 amountOwed = amounts[i] + premiums[i];
132
+ IERC20(assets[i]).approve(address(POOL), amountOwed);
133
+ }
134
+ return true;
135
+ }
136
+
137
+ /**
138
+ * @notice Initiate a multi-asset flash loan.
139
+ * @param assets Token addresses to borrow.
140
+ * @param amounts Amounts (in token's native decimals).
141
+ * @param params Arbitrary bytes forwarded to executeOperation.
142
+ */
143
+ function requestFlashLoan(
144
+ address[] calldata assets,
145
+ uint256[] calldata amounts,
146
+ bytes calldata params
147
+ ) external onlyOwner {
148
+ uint256[] memory modes = new uint256[](assets.length); // 0 = no debt
149
+ POOL.flashLoan(address(this), assets, amounts, modes, address(this), params, 0);
150
+ }
151
+
152
+ /**
153
+ * @notice Initiate a single-asset flash loan (cheaper gas).
154
+ */
155
+ function requestFlashLoanSimple(
156
+ address asset,
157
+ uint256 amount,
158
+ bytes calldata params
159
+ ) external onlyOwner {
160
+ POOL.flashLoanSimple(address(this), asset, amount, params, 0);
161
+ }
162
+
163
+ /// Rescue any tokens accidentally sent to this contract.
164
+ function rescue(address token) external onlyOwner {
165
+ IERC20(token).transfer(owner, IERC20(token).balanceOf(address(this)));
166
+ }
167
+ }
168
+ """
169
+
170
+ def __init__(self, chain_id: str | int, rpc_url: str):
171
+ self.chain_id = str(chain_id)
172
+ self.rpc_url = rpc_url
173
+ pool_address = AAVE_V3_POOL.get(self.chain_id)
174
+ if pool_address is None:
175
+ raise ValueError(
176
+ f"Aave V3 Pool not configured for chain_id={chain_id}. "
177
+ f"Supported chains: {list(AAVE_V3_POOL.keys())}"
178
+ )
179
+ self.pool_address = pool_address
180
+ self._w3 = Web3(Web3.HTTPProvider(rpc_url, request_kwargs={"timeout": 15}))
181
+ self._pool = self._w3.eth.contract(
182
+ address=Web3.to_checksum_address(pool_address),
183
+ abi=_AAVE_POOL_ABI,
184
+ )
185
+
186
+ # ------------------------------------------------------------------
187
+ # Transaction builders (return unsigned tx dicts)
188
+ # ------------------------------------------------------------------
189
+
190
+ def flash_loan_tx(
191
+ self,
192
+ receiver_contract: str,
193
+ assets: list[str],
194
+ amounts: list[int],
195
+ sender: str,
196
+ params: bytes = b"",
197
+ referral_code: int = 0,
198
+ ) -> dict:
199
+ """
200
+ Build an unsigned `flashLoan` transaction (multi-asset).
201
+
202
+ Parameters
203
+ ----------
204
+ receiver_contract : str
205
+ Address of the deployed FlashLoanExecutor contract.
206
+ assets : list[str]
207
+ Token addresses to borrow (checksummed).
208
+ amounts : list[int]
209
+ Amounts in raw token units (use `to_wei` to scale).
210
+ sender : str
211
+ EOA that will sign and send the transaction.
212
+ params : bytes
213
+ ABI-encoded data forwarded to `executeOperation`.
214
+ referral_code : int
215
+ Aave referral code (0 for none).
216
+
217
+ Returns
218
+ -------
219
+ dict
220
+ Unsigned transaction dict ready for `Wallet.sign_and_send`.
221
+ """
222
+ assets_cs = [Web3.to_checksum_address(a) for a in assets]
223
+ modes = [0] * len(assets) # 0 = no debt (must repay in same tx)
224
+ receiver_cs = Web3.to_checksum_address(receiver_contract)
225
+ sender_cs = Web3.to_checksum_address(sender)
226
+
227
+ return self._pool.functions.flashLoan(
228
+ receiver_cs,
229
+ assets_cs,
230
+ amounts,
231
+ modes,
232
+ receiver_cs, # onBehalfOf = the executor itself
233
+ params,
234
+ referral_code,
235
+ ).build_transaction({"from": sender_cs})
236
+
237
+ def flash_loan_simple_tx(
238
+ self,
239
+ receiver_contract: str,
240
+ asset: str,
241
+ amount: int,
242
+ sender: str,
243
+ params: bytes = b"",
244
+ referral_code: int = 0,
245
+ ) -> dict:
246
+ """
247
+ Build an unsigned `flashLoanSimple` transaction (single asset, lower gas).
248
+
249
+ Parameters
250
+ ----------
251
+ receiver_contract : str
252
+ Address of the deployed FlashLoanExecutor contract.
253
+ asset : str
254
+ Token address to borrow.
255
+ amount : int
256
+ Amount in raw token units.
257
+ sender : str
258
+ EOA that will sign and send the transaction.
259
+ params : bytes
260
+ ABI-encoded data forwarded to `executeOperation`.
261
+ """
262
+ receiver_cs = Web3.to_checksum_address(receiver_contract)
263
+ asset_cs = Web3.to_checksum_address(asset)
264
+ sender_cs = Web3.to_checksum_address(sender)
265
+
266
+ return self._pool.functions.flashLoanSimple(
267
+ receiver_cs,
268
+ asset_cs,
269
+ amount,
270
+ params,
271
+ referral_code,
272
+ ).build_transaction({"from": sender_cs})
273
+
274
+ # ------------------------------------------------------------------
275
+ # Convenience: estimate profit after flash-loan fee
276
+ # ------------------------------------------------------------------
277
+
278
+ @staticmethod
279
+ def net_profit(
280
+ amount: int,
281
+ gross_profit_wei: int,
282
+ token_decimals: int = 18,
283
+ ) -> dict:
284
+ """
285
+ Estimate net profit after the 0.09 % Aave flash-loan fee.
286
+
287
+ Parameters
288
+ ----------
289
+ amount : int
290
+ Flash-loan amount in raw token units.
291
+ gross_profit_wei : int
292
+ Expected gross profit in raw token units.
293
+ token_decimals : int
294
+ Decimal places of the borrowed token.
295
+
296
+ Returns
297
+ -------
298
+ dict with keys: fee_wei, net_profit_wei, net_profit_human, is_profitable
299
+ """
300
+ fee_wei = (amount * FLASH_LOAN_FEE_BPS) // 10_000
301
+ net = gross_profit_wei - fee_wei
302
+ return {
303
+ "fee_wei": fee_wei,
304
+ "net_profit_wei": net,
305
+ "net_profit_human": net / 10 ** token_decimals,
306
+ "is_profitable": net > 0,
307
+ }
@@ -0,0 +1,46 @@
1
+ import os
2
+ import json
3
+ import platform
4
+ from pathlib import Path
5
+
6
+ ENV_VAR = "CRYPTOINTERFACE_DB"
7
+ CONFIG_DIR = "cryptointerface"
8
+ CONFIG_FILE = "config.json"
9
+ DEFAULT_DB = "cryptointerface.db"
10
+
11
+
12
+ def _get_config_dir() -> Path:
13
+ system = platform.system()
14
+ if system == "Windows":
15
+ base = os.environ.get("APPDATA", Path.home() / "AppData" / "Roaming")
16
+ elif system == "Darwin":
17
+ base = Path.home() / "Library" / "Application Support"
18
+ else:
19
+ base = os.environ.get("XDG_CONFIG_HOME", Path.home() / ".config")
20
+
21
+ return Path(base) / CONFIG_DIR
22
+
23
+
24
+ def get_db_path() -> Path:
25
+ # 1. Environment variable
26
+ env_path = os.environ.get(ENV_VAR, "")
27
+ if env_path:
28
+ return Path(env_path)
29
+
30
+ # 2. Config file
31
+ config_dir = _get_config_dir()
32
+ config_path = config_dir / CONFIG_FILE
33
+
34
+ if config_path.exists():
35
+ try:
36
+ with open(config_path) as f:
37
+ config = json.load(f)
38
+ db_path = config.get("database", "")
39
+ if db_path:
40
+ return Path(db_path)
41
+ except (json.JSONDecodeError, OSError):
42
+ pass
43
+
44
+ # 3. Default: inside the config directory
45
+ config_dir.mkdir(parents=True, exist_ok=True)
46
+ return config_dir / DEFAULT_DB
@@ -0,0 +1,136 @@
1
+ import threading
2
+ import duckdb
3
+ from .config import get_db_path
4
+
5
+ _db_path: str | None = None
6
+ _lock = threading.Lock()
7
+ _thread_local = threading.local()
8
+
9
+ _CREATE_TABLES_SQL = """
10
+ CREATE TABLE IF NOT EXISTS tokens (
11
+ symbol VARCHAR NOT NULL,
12
+ chain_id VARCHAR NOT NULL,
13
+ address VARCHAR NOT NULL,
14
+ decimals INTEGER,
15
+ PRIMARY KEY (chain_id, address)
16
+ );
17
+ CREATE TABLE IF NOT EXISTS pools_v2 (
18
+ dex VARCHAR NOT NULL,
19
+ chain_id INTEGER NOT NULL,
20
+ token_0 VARCHAR NOT NULL,
21
+ token_1 VARCHAR NOT NULL,
22
+ address VARCHAR NOT NULL,
23
+ PRIMARY KEY (dex, chain_id, token_0, token_1)
24
+ );
25
+ CREATE TABLE IF NOT EXISTS pools_v3 (
26
+ dex VARCHAR NOT NULL,
27
+ chain_id INTEGER NOT NULL,
28
+ token_0 VARCHAR NOT NULL,
29
+ token_1 VARCHAR NOT NULL,
30
+ fee INTEGER NOT NULL,
31
+ address VARCHAR NOT NULL,
32
+ PRIMARY KEY (dex, chain_id, token_0, token_1, fee)
33
+ );
34
+ """
35
+
36
+
37
+ def _init_tables(db_path: str = None) -> duckdb.DuckDBPyConnection:
38
+ global _db_path
39
+ if db_path is not None:
40
+ _db_path = db_path
41
+ if _db_path is None:
42
+ _db_path = str(get_db_path())
43
+
44
+ if not hasattr(_thread_local, "conn"):
45
+ _thread_local.conn = duckdb.connect(_db_path)
46
+ for stmt in _CREATE_TABLES_SQL.strip().split(";"):
47
+ stmt = stmt.strip()
48
+ if stmt:
49
+ _thread_local.conn.execute(stmt)
50
+
51
+ return _thread_local.conn
52
+
53
+
54
+ def insert_data(df, db_cols: list, table_name: str, conn, pk_cols: list = None):
55
+ if df.is_empty():
56
+ return
57
+ final_cols = [c for c in db_cols if c in df.columns]
58
+ df = df.select(final_cols)
59
+ col_names = ", ".join(final_cols)
60
+
61
+ with _lock:
62
+ if pk_cols:
63
+ pk_where = " AND ".join([f"existing.{c} = df.{c}" for c in pk_cols])
64
+ conn.execute(
65
+ f"""
66
+ INSERT INTO {table_name} ({col_names})
67
+ SELECT {col_names} FROM df
68
+ WHERE NOT EXISTS (
69
+ SELECT 1 FROM {table_name} existing
70
+ WHERE {pk_where}
71
+ )
72
+ """
73
+ )
74
+ else:
75
+ conn.execute(
76
+ f"INSERT INTO {table_name} ({col_names}) SELECT {col_names} FROM df"
77
+ )
78
+
79
+
80
+ def drop_tables(table_name: str):
81
+ conn = _init_tables()
82
+ conn.execute(f"DROP TABLE {table_name}")
83
+ conn.commit()
84
+ conn.close()
85
+
86
+
87
+ def export_tokens_to_csv(csv_path: str = "tokens.csv"):
88
+ conn = _init_tables()
89
+ conn.execute(f"COPY tokens TO '{csv_path}' (HEADER, DELIMITER ',')")
90
+
91
+
92
+ def export_pools_to_csv(v2_path: str = "pools_v2.csv", v3_path: str = "pools_v3.csv"):
93
+ conn = _init_tables()
94
+ conn.execute(f"COPY pools_v2 TO '{v2_path}' (HEADER, DELIMITER ',')")
95
+ conn.execute(f"COPY pools_v3 TO '{v3_path}' (HEADER, DELIMITER ',')")
96
+
97
+
98
+ def insert_tokens_csv_to_db(csv_path: str = "tokens.csv"):
99
+ conn = _init_tables()
100
+ conn.execute(
101
+ f"""
102
+ INSERT INTO tokens (symbol, chain_id, address, decimals)
103
+ SELECT symbol, chain_id, address, decimals FROM read_csv_auto('{csv_path}')
104
+ WHERE NOT EXISTS (
105
+ SELECT 1 FROM tokens existing
106
+ WHERE existing.chain_id = chain_id AND existing.address = address
107
+ )
108
+ """
109
+ )
110
+
111
+
112
+ def insert_pools_csv_to_db(v2_path: str = "pools_v2.csv", v3_path: str = "pools_v3.csv"):
113
+ conn = _init_tables()
114
+ conn.execute(
115
+ f"""
116
+ INSERT INTO pools_v2 (dex, chain_id, token_0, token_1, address)
117
+ SELECT dex, chain_id, token_0, token_1, address FROM read_csv_auto('{v2_path}')
118
+ WHERE NOT EXISTS (
119
+ SELECT 1 FROM pools_v2 existing
120
+ WHERE existing.dex = dex AND existing.chain_id = chain_id
121
+ AND existing.token_0 = token_0 AND existing.token_1 = token_1
122
+ )
123
+ """
124
+ )
125
+ conn.execute(
126
+ f"""
127
+ INSERT INTO pools_v3 (dex, chain_id, token_0, token_1, fee, address)
128
+ SELECT dex, chain_id, token_0, token_1, fee, address FROM read_csv_auto('{v3_path}')
129
+ WHERE NOT EXISTS (
130
+ SELECT 1 FROM pools_v3 existing
131
+ WHERE existing.dex = dex AND existing.chain_id = chain_id
132
+ AND existing.token_0 = token_0 AND existing.token_1 = token_1
133
+ AND existing.fee = fee
134
+ )
135
+ """
136
+ )
@@ -0,0 +1,119 @@
1
+ {
2
+ "uniswap_v2": {
3
+ "version": 2,
4
+ "protocol": "uniswap_v2",
5
+ "factory_fn": "getPair",
6
+ "router_fn": "swapExactTokensForTokens",
7
+ "fee_tiers": null,
8
+ "swap_fee_bps": 30,
9
+ "notes": "Original Uniswap V2. Fixed 0.3% fee baked into the pair contract."
10
+ },
11
+ "uniswap_v3": {
12
+ "version": 3,
13
+ "protocol": "uniswap_v3",
14
+ "factory_fn": "getPool",
15
+ "router_fn": "exactInputSingle",
16
+ "fee_tiers": [100, 500, 3000, 10000],
17
+ "swap_fee_bps": null,
18
+ "notes": "Original Uniswap V3. Fee is per pool — use the matched fee tier."
19
+ },
20
+ "sushiswap_v2": {
21
+ "version": 2,
22
+ "protocol": "uniswap_v2",
23
+ "factory_fn": "getPair",
24
+ "router_fn": "swapExactTokensForTokens",
25
+ "fee_tiers": null,
26
+ "swap_fee_bps": 30,
27
+ "notes": "Uniswap V2 fork. ABI-compatible with uniswap_v2."
28
+ },
29
+ "sushiswap_v3": {
30
+ "version": 3,
31
+ "protocol": "uniswap_v3",
32
+ "factory_fn": "getPool",
33
+ "router_fn": "exactInputSingle",
34
+ "fee_tiers": [100, 500, 3000, 10000],
35
+ "swap_fee_bps": null,
36
+ "notes": "Uniswap V3 fork. Fee is per pool — use the matched fee tier."
37
+ },
38
+ "pancakeswap_v2": {
39
+ "version": 2,
40
+ "protocol": "uniswap_v2",
41
+ "factory_fn": "getPair",
42
+ "router_fn": "swapExactTokensForTokens",
43
+ "fee_tiers": null,
44
+ "swap_fee_bps": 25,
45
+ "notes": "Uniswap V2 fork. 0.25% swap fee."
46
+ },
47
+ "pancakeswap_v3": {
48
+ "version": 3,
49
+ "protocol": "uniswap_v3",
50
+ "factory_fn": "getPool",
51
+ "router_fn": "exactInputSingle",
52
+ "fee_tiers": [100, 500, 2500, 10000],
53
+ "swap_fee_bps": null,
54
+ "notes": "Uniswap V3 fork. Fee is per pool — use the matched fee tier."
55
+ },
56
+ "quickswap_v2": {
57
+ "version": 2,
58
+ "protocol": "uniswap_v2",
59
+ "factory_fn": "getPair",
60
+ "router_fn": "swapExactTokensForTokens",
61
+ "fee_tiers": null,
62
+ "swap_fee_bps": 30,
63
+ "notes": "Uniswap V2 fork on Polygon."
64
+ },
65
+ "quickswap_v3": {
66
+ "version": 3,
67
+ "protocol": "algebra_v3",
68
+ "factory_fn": "poolByPair",
69
+ "router_fn": "exactInputSingle",
70
+ "fee_tiers": null,
71
+ "swap_fee_bps": null,
72
+ "notes": "Algebra V3 fork. Fee is dynamic per pool."
73
+ },
74
+ "camelot_v2": {
75
+ "version": 2,
76
+ "protocol": "uniswap_v2",
77
+ "factory_fn": "getPair",
78
+ "router_fn": "swapExactTokensForTokens",
79
+ "fee_tiers": null,
80
+ "swap_fee_bps": 30,
81
+ "notes": "Uniswap V2 fork on Arbitrum."
82
+ },
83
+ "camelot_v3": {
84
+ "version": 3,
85
+ "protocol": "algebra_v3",
86
+ "factory_fn": "poolByPair",
87
+ "router_fn": "exactInputSingle",
88
+ "fee_tiers": null,
89
+ "swap_fee_bps": null,
90
+ "notes": "Algebra V3 fork on Arbitrum. Fee is dynamic per pool."
91
+ },
92
+ "traderjoe_v1": {
93
+ "version": 2,
94
+ "protocol": "uniswap_v2",
95
+ "factory_fn": "getPair",
96
+ "router_fn": "swapExactTokensForTokens",
97
+ "fee_tiers": null,
98
+ "swap_fee_bps": 30,
99
+ "notes": "Uniswap V2 fork on Avalanche/Arbitrum."
100
+ },
101
+ "aerodrome": {
102
+ "version": 2,
103
+ "protocol": "solidly_v2",
104
+ "factory_fn": "getPool",
105
+ "router_fn": "swapExactTokensForTokens",
106
+ "fee_tiers": null,
107
+ "swap_fee_bps": 2,
108
+ "notes": "Solidly/Velodrome fork on Base. 0.02% volatile pool fee (stable pools vary)."
109
+ },
110
+ "velodrome": {
111
+ "version": 2,
112
+ "protocol": "solidly_v2",
113
+ "factory_fn": "getPool",
114
+ "router_fn": "swapExactTokensForTokens",
115
+ "fee_tiers": null,
116
+ "swap_fee_bps": 2,
117
+ "notes": "Solidly fork on Optimism. 0.02% volatile pool fee (stable pools vary)."
118
+ }
119
+ }