├── files ├── wallets.txt ├── query1.json ├── query2.json └── query3.json ├── .gitignore ├── requirements.txt ├── README.md └── main.py /files/wallets.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | files/database1.json 2 | files/database2.json 3 | LayerZero Stats.csv 4 | LayerZero Stats.xlsx 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | art==5.9 2 | inquirer==3.1.3 3 | loguru==0.6.0 4 | termcolor==2.3.0 5 | tls_client==0.2.1 6 | XlsxWriter==3.1.1 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LayerZeroStats 2 | A simple script for downloading the LayerZero table from Dune and filtering by the specified wallets. 3 | 4 | Add wallet addresses to files/wallets.txt and run the script 5 | 6 | TG: https://t.me/cryptogovnozavod 7 | 8 | ## **Donate:** 9 | 0x0Cb5b78520ac6de655d49582dB3Ee81840238C0F 10 | -------------------------------------------------------------------------------- /files/query1.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName": "GetResult", 3 | "variables": { 4 | "query_id": 2463827, 5 | "parameters": [], 6 | "can_refresh": true 7 | }, 8 | "query": "query GetResult($query_id: Int!, $parameters: [Parameter!]!, $can_refresh: Boolean!) {\n get_result_v4(\n query_id: $query_id\n parameters: $parameters\n can_refresh: $can_refresh\n ) {\n job_id\n result_id\n error_id\n __typename\n }\n}\n" 9 | } 10 | -------------------------------------------------------------------------------- /files/query2.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName":"GetExecution", 3 | "variables":{ 4 | "execution_id":"01H10Z5KG360K7SYCPF3W33V48", 5 | "query_id":2464151, 6 | "parameters":[ 7 | 8 | ] 9 | }, 10 | "query":"query GetExecution($execution_id: String!, $query_id: Int!, $parameters: [Parameter!]!) {\n get_execution(\n execution_id: $execution_id\n query_id: $query_id\n parameters: $parameters\n ) {\n execution_queued {\n execution_id\n execution_user_id\n position\n execution_type\n created_at\n __typename\n }\n execution_running {\n execution_id\n execution_user_id\n execution_type\n started_at\n created_at\n __typename\n }\n execution_succeeded {\n execution_id\n runtime_seconds\n generated_at\n columns\n data\n __typename\n }\n execution_failed {\n execution_id\n type\n message\n metadata {\n line\n column\n hint\n __typename\n }\n runtime_seconds\n generated_at\n __typename\n }\n __typename\n }\n}\n" 11 | } 12 | -------------------------------------------------------------------------------- /files/query3.json: -------------------------------------------------------------------------------- 1 | { 2 | "operationName":"GetExecution", 3 | "variables":{ 4 | "execution_id":"01H12V7WD9AXW2VNJE0SJ86KMZ", 5 | "query_id":2492847, 6 | "parameters":[ 7 | 8 | ] 9 | }, 10 | "query": "query GetExecution($execution_id: String!, $query_id: Int!, $parameters: [Parameter!]!) {\n get_execution(\n execution_id: $execution_id\n query_id: $query_id\n parameters: $parameters\n ) {\n execution_queued {\n execution_id\n execution_user_id\n position\n execution_type\n created_at\n __typename\n }\n execution_running {\n execution_id\n execution_user_id\n execution_type\n started_at\n created_at\n __typename\n }\n execution_succeeded {\n execution_id\n runtime_seconds\n generated_at\n columns\n data\n __typename\n }\n execution_failed {\n execution_id\n type\n message\n metadata {\n line\n column\n hint\n __typename\n }\n runtime_seconds\n generated_at\n __typename\n }\n __typename\n }\n}\n" 11 | } 12 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from sys import stderr, exit 4 | 5 | import tls_client 6 | import inquirer 7 | import xlsxwriter 8 | from art import text2art 9 | from loguru import logger 10 | from termcolor import colored 11 | from inquirer.themes import load_theme_from_dict as loadth 12 | 13 | 14 | # FILES SETTINGS 15 | cwd = os.getcwd() 16 | file_data1 = f'{cwd}/files/database1.json' 17 | file_data2 = f'{cwd}/files/database2.json' 18 | file_query1 = f'{cwd}/files/query1.json' 19 | file_query2 = f'{cwd}/files/query2.json' 20 | file_query3 = f'{cwd}/files/query3.json' 21 | file_wallets = f'{cwd}/files/wallets.txt' 22 | file_excel_table = f'{cwd}/LayerZero Stats.xlsx' 23 | 24 | # LOGGING SETTING 25 | logger.remove() 26 | logger.add(stderr, format="{time:HH:mm:ss} | {level: <8} | {line} - {message}") 27 | 28 | WALLETS = [] 29 | QUERY1 = 2464151 30 | QUERY2 = 2492847 31 | 32 | 33 | def is_exists(path: str) -> bool: 34 | return os.path.isfile(path) 35 | 36 | 37 | def filter_wallets1(wallet: dict) -> bool: 38 | if (wallet['ua'].lower() in WALLETS): 39 | return True 40 | return False 41 | 42 | def filter_wallets2(wallet: dict) -> bool: 43 | try: 44 | if (wallet['address'].lower() in WALLETS): 45 | return True 46 | except: 47 | return False 48 | return False 49 | 50 | 51 | def load_wallets() -> None: 52 | global WALLETS 53 | with open(file_wallets, 'r') as file: 54 | WALLETS = [row.strip().lower() for row in file] 55 | 56 | 57 | def edit_dates1(wallets: list) -> None: 58 | for wallet in wallets: 59 | for i in wallet: 60 | if (i in (['ibt'])): 61 | wallet[i] = wallet[i][:19] 62 | if (i == 'amount_usd' and wallet[i] != None): 63 | wallet[i] = round(wallet[i],2) 64 | 65 | def edit_dates2(wallets: list) -> None: 66 | for wallet in wallets: 67 | for i in wallet: 68 | if (i == 'eth_total' and wallet[i] != None): 69 | wallet[i] = f'{round(wallet[i],4)} ({round(wallet[i]*1800,2)})' 70 | if (i == 'usd_total' and wallet[i] != None): 71 | wallet[i] = round(wallet[i],2) 72 | 73 | def get_filtered_wallets(data_file: str) -> list: 74 | with open(data_file, 'r') as file: 75 | data = json.load(file) 76 | 77 | all_wallet_info = data['data']['get_execution']['execution_succeeded']['data'] 78 | 79 | if (data_file == file_data1): 80 | filtered_wallets = list(filter(filter_wallets1, all_wallet_info)) 81 | edit_dates1(filtered_wallets) 82 | else: 83 | filtered_wallets = list(filter(filter_wallets2, all_wallet_info)) 84 | edit_dates2(filtered_wallets) 85 | return filtered_wallets 86 | 87 | 88 | def save_to_excel(wallets1: list, wallets2: list) -> None: 89 | pretty_columns = [ 90 | "Ranking", 91 | "User Address", 92 | "Ranking Score", 93 | "Transactions Count", 94 | "Bridged Amount ($)", 95 | "Eth Total", 96 | "Stables Total", 97 | "Interacted Source Chains / Destination Chains / Contracts Count", 98 | "Unique Active Days / Weeks/ Months", 99 | "LZ Age In Days", 100 | "Initial Active Data" 101 | ] 102 | 103 | columns = list(wallets1[0].keys()) 104 | columns.insert(5,"eth_total") 105 | columns.insert(6,"stables_total") 106 | 107 | for wallet in wallets1: 108 | for i, wallet2 in enumerate(wallets2): 109 | if wallet["ua"] == wallet2["address"]: 110 | break 111 | else: 112 | wallet["eth_total"] = 0 113 | wallet["stables_total"] = 0 114 | continue 115 | wallet["eth_total"] = wallets2[i]["eth_total"] 116 | wallet["stables_total"] = wallets2[i]["usd_total"] 117 | 118 | workbook = xlsxwriter.Workbook(file_excel_table) 119 | worksheet = workbook.add_worksheet("Stats") 120 | 121 | header_format = workbook.add_format({ 122 | 'bold': True, 123 | 'align': 'center', 124 | 'valign': 'vcenter', 125 | 'text_wrap': True, 126 | 'border': 1 127 | }) 128 | for col_num, column in enumerate(pretty_columns): 129 | worksheet.write(0, col_num, column, header_format) 130 | 131 | for row_num, wallet in enumerate(wallets1, 1): 132 | for col_num, col in enumerate(columns): 133 | worksheet.write(row_num, col_num, wallet[col]) 134 | 135 | worksheet.write(len(wallets1) + 3, 0, 'Donate:') 136 | worksheet.write(len(wallets1) + 3, 1, '0x2e69Da32b0F7e75549F920CD2aCB0532Cc2aF0E7') 137 | 138 | row_format = workbook.add_format({'align': 'center'}) 139 | sizes = [9, 45, 8, 12, 12, 13, 12, 17, 17, 8, 20] 140 | for col_num, size in enumerate(sizes): 141 | worksheet.set_column(col_num, col_num, size, row_format) 142 | 143 | first_row_format = workbook.add_format({ 144 | 'text_wrap': True, 145 | 'valign': 'vcenter', 146 | 'align': 'center', 147 | 'border': 1 148 | }) 149 | worksheet.set_row(0, 60, first_row_format) 150 | 151 | workbook.close() 152 | 153 | 154 | def get_execution_id(session: tls_client.Session, query_id: int) -> int: 155 | with open(file_query1, 'r') as file: 156 | payload = json.load(file) 157 | 158 | payload['variables']['query_id'] = query_id 159 | 160 | while True: 161 | try: 162 | response = session.post('https://core-hsr.dune.com/v1/graphql', json=payload) 163 | if (response.status_code == 200): 164 | break 165 | else: 166 | logger.error(f'Ошибка обновления базы данных: {response.text} | Cтатус запроса: {response.status_code}') 167 | except Exception as error: 168 | logger.error(f'Ошибка обновления базы данных: {error}') 169 | 170 | execution_id = response.json()['data']['get_result_v4']['result_id'] 171 | return execution_id 172 | 173 | 174 | 175 | def setup_session() -> tls_client.Session: 176 | session = tls_client.Session( 177 | client_identifier="chrome112", 178 | random_tls_extension_order=True 179 | ) 180 | 181 | headers = { 182 | 'origin': 'https://dune.com', 183 | 'referer': 'https://dune.com/', 184 | 'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36', 185 | } 186 | 187 | session.headers = headers 188 | session.timeout_seconds = 1000 189 | return session 190 | 191 | 192 | def update_database() -> None: 193 | session = setup_session() 194 | 195 | logger.info('Начинаю скачивание двух баз данных. Процесс может занять несколько минут...') 196 | 197 | with open(file_query2, 'r') as file: 198 | payload = json.load(file) 199 | 200 | execution_id = get_execution_id(session, QUERY1) 201 | logger.info(f'ID #1 базы данных №{QUERY1}: {execution_id}') 202 | payload['variables']['execution_id'] = execution_id 203 | 204 | while True: 205 | try: 206 | response = session.post('https://app-api.dune.com/v1/graphql', json=payload) 207 | if (response.status_code == 200): 208 | logger.success(f'База данных №{QUERY1} успешно скачана!') 209 | break 210 | else: 211 | logger.error(f'Ошибка обновления базы данных: {response.text} | Cтатус запроса: {response.status_code}') 212 | except Exception as error: 213 | logger.error(f'Ошибка обновления базы данных: {error}') 214 | 215 | with open(file_data1, 'w') as file: 216 | json.dump(response.json(), file) 217 | 218 | #---------------------------------------------- 219 | 220 | # logger.info(f'Скачиваю вторую базу данных') 221 | 222 | with open(file_query3, 'r') as file: 223 | payload = json.load(file) 224 | 225 | execution_id = get_execution_id(session, QUERY2) 226 | logger.info(f'ID #2 базы данных №{QUERY2}: {execution_id}') 227 | payload['variables']['execution_id'] = execution_id 228 | 229 | while True: 230 | try: 231 | response = session.post('https://app-api.dune.com/v1/graphql', json=payload) 232 | if (response.status_code == 200): 233 | logger.success(f'База данных №{QUERY2} успешно скачана!') 234 | break 235 | else: 236 | logger.error(f'Ошибка обновления базы данных: {response.text} | Cтатус запроса: {response.status_code}') 237 | except Exception as error: 238 | logger.error(f'Ошибка обновления базы данных: {error}') 239 | 240 | with open(file_data2, 'w') as file: 241 | json.dump(response.json(), file) 242 | 243 | logger.success(f'Готово!\n') 244 | 245 | 246 | def make_table() -> None: 247 | exists1 = is_exists(file_data1) 248 | exists2 = is_exists(file_data2) 249 | if (not exists1 or not exists2): 250 | logger.info('Файлы баз данных отстутствуют!') 251 | update_database() 252 | 253 | load_wallets() 254 | logger.info(f'Загружено {len(WALLETS)} кошельков') 255 | filtered_wallets1 = get_filtered_wallets(file_data1) 256 | filtered_wallets2 = get_filtered_wallets(file_data2) 257 | if (len(filtered_wallets1) == 0): 258 | logger.error('Не найден ни один кошелек в базе!') 259 | return 260 | save_to_excel(filtered_wallets1, filtered_wallets2) 261 | logger.success('Готово!\n') 262 | WALLETS.clear() 263 | 264 | 265 | def get_action() -> str: 266 | theme = { 267 | "Question": { 268 | "brackets_color": "bright_yellow" 269 | }, 270 | "List": { 271 | "selection_color": "bright_blue" 272 | } 273 | } 274 | 275 | question = [ 276 | inquirer.List( 277 | "action", 278 | message=colored("Выберите действие", 'light_yellow'), 279 | choices=["Обновить базу данных", "Составить Excel таблицу", "Выход"], 280 | ) 281 | ] 282 | action = inquirer.prompt(question, theme=loadth(theme))['action'] 283 | return action 284 | 285 | 286 | def main() -> None: 287 | art = text2art(text="LAYERZERO STATS", font="standart") 288 | print(colored(art,'light_blue')) 289 | print(colored('Автор: t.me/cryptogovnozavod\n','light_cyan')) 290 | 291 | while True: 292 | action = get_action() 293 | 294 | match action: 295 | case 'Обновить базу данных': 296 | update_database() 297 | case 'Составить Excel таблицу': 298 | make_table() 299 | case 'Выход': 300 | exit() 301 | case _: 302 | pass 303 | 304 | 305 | if (__name__ == '__main__'): 306 | main() 307 | --------------------------------------------------------------------------------