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.
- cryptointerface/__init__.py +5 -0
- cryptointerface/dex.py +892 -0
- cryptointerface/flashloan.py +307 -0
- cryptointerface/periphery/config.py +46 -0
- cryptointerface/periphery/db.py +136 -0
- cryptointerface/periphery/dex_architecture.json +119 -0
- cryptointerface/periphery/dex_contracts.json +301 -0
- cryptointerface/periphery/dex_contracts.py +56 -0
- cryptointerface/periphery/enums.py +31 -0
- cryptointerface/periphery/mapping.py +118 -0
- cryptointerface/periphery/utils.py +6 -0
- cryptointerface/providers/endpoints.json +25 -0
- cryptointerface/providers/infura.py +36 -0
- cryptointerface/routes.py +90 -0
- cryptointerface/token.py +264 -0
- cryptointerface/wallet.py +28 -0
- cryptointerface-0.1.0.dist-info/METADATA +354 -0
- cryptointerface-0.1.0.dist-info/RECORD +19 -0
- cryptointerface-0.1.0.dist-info/WHEEL +4 -0
|
@@ -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
|
+
}
|