├── 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 |
--------------------------------------------------------------------------------