Ethscriptions commited on
Commit
c18992d
·
1 Parent(s): 9c1300a

Add application file

Browse files
.DS_Store CHANGED
Binary files a/.DS_Store and b/.DS_Store differ
 
data/.DS_Store ADDED
Binary file (8.2 kB). View file
 
data/1.txt DELETED
File without changes
img/.DS_Store CHANGED
Binary files a/img/.DS_Store and b/img/.DS_Store differ
 
img/alipay_qrcode.png ADDED
img/pushover_logo.jpg ADDED
logo/.DS_Store CHANGED
Binary files a/logo/.DS_Store and b/logo/.DS_Store differ
 
logo/favicon.png CHANGED
logo/icon-512.png CHANGED
logo/icon-57.png CHANGED
logo/icon-60.png CHANGED
logo/icon-60@3x.png CHANGED
logo/icon-hdpi.png CHANGED
logo/icon-ldpi.png CHANGED
logo/icon-mdpi.png CHANGED
logo/icon-xhdpi.png CHANGED
logo/icon-xxhdpi.png CHANGED
logo/splash-1125x2436.png CHANGED
logo/splash-1242x2208.png CHANGED
logo/splash-640x1136.png CHANGED
pages/.DS_Store CHANGED
Binary files a/pages/.DS_Store and b/pages/.DS_Store differ
 
pages/1_🔍_批量查询铭文状态.py CHANGED
@@ -1,11 +1,15 @@
 
 
 
 
 
1
  import streamlit as st
2
  import re
3
  import os
4
  import sqlite3
5
  import pandas as pd
6
- import time
7
- import matplotlib.pyplot as plt
8
- import random
9
 
10
 
11
  # 获取当前文件目录
@@ -18,14 +22,151 @@ name_set_cursor = name_set_conn.cursor()
18
  content_set = ''
19
  check_type = ''
20
 
 
 
 
 
 
 
 
 
 
 
21
 
22
  # 把文字转换成 16 进制
23
  def text_to_hex(text):
24
  return '0x' + ''.join(format(byte, '02x') for byte in text.encode('utf-8'))
25
 
26
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  st.set_page_config(page_title="EthPen - 批量查询铭文状态", page_icon="🔍", layout='centered', initial_sidebar_state='auto')
28
- st.subheader(r'🔍 :rainbow[EthPen - 批量查询铭文状态]', anchor=False, divider='rainbow')
 
29
 
30
  st.write("<style>div.row-widget.stRadio > div{background-color:white;border: 1px solid #e6e9ef;border-radius:8px;padding:10px;box-shadow: 2px 2px 10px #e6e9ef;}</style>", unsafe_allow_html=True)
31
  st.markdown(f'### 💖 查找你心仪的铭文')
@@ -175,13 +316,12 @@ if not token_check:
175
  if not token_check:
176
  content = st.text_area('铭文列表:', value=content_set, key=1)
177
 
178
-
179
  hide_inscribed = st.toggle('隐藏已题写')
180
- if hide_inscribed:
181
- hide_inscribed = True
182
- else:
183
- hide_inscribed = False
184
 
 
 
 
 
185
  if st.button('🔎 查询', key='🔎 查询'):
186
  st.markdown(f'### 📄 查询结果')
187
  all_items = []
@@ -215,31 +355,42 @@ if st.button('🔎 查询', key='🔎 查询'):
215
 
216
  ethscriptions_cursor = ethscriptions_conn.cursor()
217
  names_total = len(names)
218
- name_not_yet_total = 0
219
  result = []
220
- show_unwritten_list = []
221
- for index in range(len(names)):
222
- ethscriptions_cursor.execute("SELECT * FROM data WHERE data=?", (all_items[index],))
223
- row = ethscriptions_cursor.fetchone()
224
- if hide_inscribed:
225
- if not row:
226
- result.append({"铭文": names[index], "状态": "未题写", "十六进制数据": all_items[index][2:]})
227
- name_not_yet_total += 1
228
- show_unwritten_list.append(names[index])
229
- else:
230
- if row:
231
- result.append({"铭文": names[index], "状态": "已题写", "十六进制数据": all_items[index][2:]})
232
  else:
233
- result.append({"铭文": names[index], "状态": "未题写", "十六进制数据": all_items[index][2:]})
234
- name_not_yet_total += 1
235
- show_unwritten_list.append(names[index])
 
 
236
  st.progress(1 - (name_not_yet_total / names_total), f'题写进度 {names_total - name_not_yet_total}/{names_total} ({(1 - (name_not_yet_total / names_total)) * 100:.0f}%):')
237
- if not result:
238
- st.markdown(f'### ☹️ 你来迟了~')
239
- else:
240
  # 将结果转换为Pandas DataFrame
241
  result_df = pd.DataFrame(result)
 
242
  st.dataframe(result_df, use_container_width=True, hide_index=True)
 
 
 
 
 
 
 
 
 
243
  # Convert DataFrame to CSV with proper encoding
244
  csv_export = result_df.to_csv(index=False, encoding='utf-8')
245
  # Add a download button for the DataFrame
@@ -249,5 +400,10 @@ if st.button('🔎 查询', key='🔎 查询'):
249
  file_name="ethpen_result_data.csv",
250
  mime="text/csv"
251
  )
 
 
252
 
253
-
 
 
 
 
1
+ import base64
2
+ import hashlib
3
+ import json
4
+
5
+ import requests
6
  import streamlit as st
7
  import re
8
  import os
9
  import sqlite3
10
  import pandas as pd
11
+ from concurrent.futures import ThreadPoolExecutor
12
+
 
13
 
14
 
15
  # 获取当前文件目录
 
22
  content_set = ''
23
  check_type = ''
24
 
25
+ name_not_yet_total = 0
26
+
27
+ etherscan_api_key = os.environ.get('etherscan_api_key', '')
28
+
29
+
30
+ # 图片Base64
31
+ def image_to_base64(img_path):
32
+ with open(img_path, "rb") as image_file:
33
+ return base64.b64encode(image_file.read()).decode()
34
+
35
 
36
  # 把文字转换成 16 进制
37
  def text_to_hex(text):
38
  return '0x' + ''.join(format(byte, '02x') for byte in text.encode('utf-8'))
39
 
40
 
41
+ # str SHA256
42
+ def sha256(input_string):
43
+ sha256 = hashlib.sha256()
44
+ sha256.update(input_string.encode('utf-8'))
45
+ return sha256.hexdigest()
46
+
47
+
48
+ # 使用 SHA256 值检查内容是否已被 ethscribed
49
+ def check_content_exists(sha):
50
+ endpoint = f"/ethscriptions/exists/{sha}"
51
+ try:
52
+ response = requests.get("https://mainnet-api.ethscriptions.com/api" + endpoint, timeout=5)
53
+ response.raise_for_status() # 如果响应状态码不是200,这会引发HTTPError
54
+ return response.json()['result']
55
+ except requests.RequestException: # 捕获所有与requests相关的异常
56
+ return "未知"
57
+
58
+
59
+ # 处理数据
60
+ def orginize_data(index):
61
+ global name_not_yet_total
62
+ sha_value = sha256(names[index])
63
+ response_data = check_content_exists(sha_value)
64
+ if hide_inscribed:
65
+ if not response_data:
66
+ result.append({"铭文": names[index], "状态": '未题写', "十六进制数据": all_items[index][2:]})
67
+ name_not_yet_total += 1
68
+ if response_data == '未知':
69
+ result.append({"铭文": names[index], "状态": '未知', "十六进制数据": all_items[index][2:]})
70
+ name_not_yet_total += 1
71
+ else:
72
+ if response_data:
73
+ result.append({"铭文": names[index], "状态": '已题写', "十六进制数据": all_items[index][2:]})
74
+ elif response_data == '未知':
75
+ result.append({"铭文": names[index], "状态": '未知', "十六进制数据": all_items[index][2:]})
76
+ name_not_yet_total += 1
77
+ else:
78
+ result.append({"铭文": names[index], "状态": '未题写', "十六进制数据": all_items[index][2:]})
79
+ name_not_yet_total += 1
80
+
81
+
82
+ # 确定索引器是否落后
83
+ def get_block_status():
84
+ endpoint = "/block_status"
85
+ response = requests.get("https://mainnet-api.ethscriptions.com/api" + endpoint)
86
+
87
+ try:
88
+ data = response.json()
89
+ return data.get('blocks_behind', None)
90
+ except json.JSONDecodeError:
91
+ print("Failed to decode JSON. Response content:")
92
+ print(response.text)
93
+ return None
94
+
95
+
96
+ # 获取题写铭文的矿工费
97
+ def estimate_transaction_fee(text_length: int, speed: str = 'Propose') -> float:
98
+ """
99
+ Estimate the transaction fee for an Ethereum transaction based on text length using Etherscan API.
100
+
101
+ :param api_key: Etherscan API key.
102
+ :param text_length: Length of the input text.
103
+ :param speed: Desired confirmation speed. Can be 'Safe', 'Propose', or 'Fast'.
104
+ :param request_timeout: Timeout for the HTTP request in seconds.
105
+ :return: Estimated transaction fee in ETH or None in case of errors.
106
+ """
107
+ try:
108
+ # Get current gas prices from Etherscan
109
+ base_url = "https://api.etherscan.io/api"
110
+ params = {
111
+ "module": "gastracker",
112
+ "action": "gasoracle",
113
+ "apikey": etherscan_api_key
114
+ }
115
+ response = requests.get(base_url, params=params, timeout=5)
116
+ response.raise_for_status()
117
+
118
+ data = response.json()
119
+ if data['status'] != '1':
120
+ return None
121
+
122
+ if speed not in ['Safe', 'Propose', 'Fast']:
123
+ return None
124
+
125
+ selected_gas_price = int(data['result'][f"{speed}GasPrice"])
126
+
127
+ # Calculate gas for the input text
128
+ NON_ZERO_BYTE_GAS = 68
129
+ ZERO_BYTE_GAS = 4
130
+
131
+ # Assuming every character is 1 byte (for simplification)
132
+ # In reality, certain UTF-8 characters may be more than 1 byte
133
+ data_gas = text_length * NON_ZERO_BYTE_GAS
134
+
135
+ # Calculate total gas and fee
136
+ BASE_GAS = 21000
137
+ total_gas = BASE_GAS + data_gas
138
+ total_fee_eth = total_gas * selected_gas_price * 1e-9
139
+
140
+ return total_fee_eth
141
+
142
+ except (requests.Timeout, requests.RequestException):
143
+ return None
144
+
145
+
146
+ # 查询 ETH token 价格
147
+ def get_eth_price():
148
+ try:
149
+ # 使用Etherscan的API endpoint获取以太坊的价格
150
+ url = f"https://api.etherscan.io/api?module=stats&action=ethprice&apikey=1I2ERGUVJZNJAFKY7MG4S317DUGRPXEQPF"
151
+ response = requests.get(url)
152
+ response.raise_for_status() # 将引发HTTPError,如果HTTP请求返回了不成功的状态码
153
+
154
+ data = response.json()
155
+
156
+ if data["message"] == "OK":
157
+ eth_price = float(data["result"]["ethusd"])
158
+ return eth_price
159
+ else:
160
+ print("Error fetching the ETH price")
161
+ return None
162
+ except requests.RequestException as e:
163
+ print(f"Error while accessing the API: {e}")
164
+ return None
165
+
166
+
167
  st.set_page_config(page_title="EthPen - 批量查询铭文状态", page_icon="🔍", layout='centered', initial_sidebar_state='auto')
168
+ st.markdown(f'# <img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "ethpen_logo.svg"))}" alt="Image" width="64px" height="64px" style="border-radius: 8px; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/> :rainbow[批量查询铭文状态]', unsafe_allow_html=True)
169
+ st.subheader(r' ', anchor=False, divider='rainbow')
170
 
171
  st.write("<style>div.row-widget.stRadio > div{background-color:white;border: 1px solid #e6e9ef;border-radius:8px;padding:10px;box-shadow: 2px 2px 10px #e6e9ef;}</style>", unsafe_allow_html=True)
172
  st.markdown(f'### 💖 查找你心仪的铭文')
 
316
  if not token_check:
317
  content = st.text_area('铭文列表:', value=content_set, key=1)
318
 
 
319
  hide_inscribed = st.toggle('隐藏已题写')
 
 
 
 
320
 
321
+ enable_api = st.toggle('启用 Ethscriptions.com API (v1)')
322
+ if enable_api:
323
+ print(get_block_status())
324
+ st.success(f"铭文数据来自 [Ethscriptions.com](https://ethscriptions.com/) 官方网站,当前索引器状态落后: {get_block_status()} 个区块。我们将以 100 个线程进行网络请求查询。尽管查询速度稍显缓慢,但所得数据确保了真实性与准确性。")
325
  if st.button('🔎 查询', key='🔎 查询'):
326
  st.markdown(f'### 📄 查询结果')
327
  all_items = []
 
355
 
356
  ethscriptions_cursor = ethscriptions_conn.cursor()
357
  names_total = len(names)
 
358
  result = []
359
+ if enable_api:
360
+ with ThreadPoolExecutor(max_workers=100) as executor:
361
+ for index in range(len(names)):
362
+ executor.submit(orginize_data, index) # Submit task to thread pool
363
+ else:
364
+ for index in range(len(names)):
365
+ ethscriptions_cursor.execute("SELECT * FROM data WHERE data=?", (all_items[index],))
366
+ row = ethscriptions_cursor.fetchone()
367
+ if hide_inscribed:
368
+ if not row:
369
+ result.append({"铭文": names[index], "状态": "未题写", "十六进制数据": all_items[index][2:]})
370
+ name_not_yet_total += 1
371
  else:
372
+ if row:
373
+ result.append({"铭文": names[index], "状态": "已题写", "十六进制数据": all_items[index][2:]})
374
+ else:
375
+ result.append({"铭文": names[index], "状态": "未题写", "十六进制数据": all_items[index][2:]})
376
+ name_not_yet_total += 1
377
  st.progress(1 - (name_not_yet_total / names_total), f'题写进度 {names_total - name_not_yet_total}/{names_total} ({(1 - (name_not_yet_total / names_total)) * 100:.0f}%):')
378
+ name_not_yet_total = 0
379
+
380
+ if result:
381
  # 将结果转换为Pandas DataFrame
382
  result_df = pd.DataFrame(result)
383
+ # print(result_df)
384
  st.dataframe(result_df, use_container_width=True, hide_index=True)
385
+ # 使用列表解析获取所有"未题写"状态的"十六进制数据"
386
+ not_written_data = [item['十六进制数据'] for item in result if item['状态'] == '未题写']
387
+ # 获取未题写数据的个数和总长度
388
+ num_not_written = len(not_written_data)
389
+ total_length = sum(len(data) for data in not_written_data)
390
+ gas_spent = 0 if num_not_written == 0 else estimate_transaction_fee(total_length // num_not_written)
391
+ eth_price = get_eth_price()
392
+ st.success(
393
+ f'题写上面未题��的铭文平均每个最低花费 {gas_spent:.10f} ETH,约为 {gas_spent * eth_price:.2f}USD;题写全部未题写铭文最低需要花费 {gas_spent * num_not_written:.10f} ETH,约为 {gas_spent * num_not_written * eth_price:.2f}USD。')
394
  # Convert DataFrame to CSV with proper encoding
395
  csv_export = result_df.to_csv(index=False, encoding='utf-8')
396
  # Add a download button for the DataFrame
 
400
  file_name="ethpen_result_data.csv",
401
  mime="text/csv"
402
  )
403
+ else:
404
+ st.markdown(f'### ☹️ 你来迟了~')
405
 
406
+ #
407
+ # print(len(content_set))
408
+ # gas_spent = 0 if len(content_set.split()) == 0 else estimate_transaction_fee(len(content_set) // len(content_set.split()))
409
+ # st.success(f'题写上面铭文平均每个最低花费 {gas_spent:.10f} ETH,约为 ${gas_spent * get_eth_price():.2f}。')
pages/2_🆔_ 批量题写域名铭文.py CHANGED
@@ -1,7 +1,6 @@
1
  # EthPen.com
2
  # 最后更新日期:2023 年 9 月 5 日
3
 
4
-
5
  # 导入运行代码所需要的库
6
  import streamlit as st # streamlit app
7
  from web3 import Web3 # 与以太坊交互的库
@@ -10,12 +9,19 @@ import requests # 用于发送网络请求
10
  import re # 用于正则表达式
11
  import time # 用于时间相关
12
  import os # 用于操作系统文件
 
13
 
14
 
15
  # 许可使用开关
16
  approved_use = False
17
 
18
 
 
 
 
 
 
 
19
  # 检查 ETH 地址是否有效
20
  def is_valid_eth_address(address):
21
  if re.match("^0x[0-9a-fA-F]{40}$", address):
@@ -104,7 +110,8 @@ def send_transaction(w3, account_address, private_key, chain_id, gas_price, inpu
104
  st.set_page_config(page_title="EthPen - 批量题写域名铭文", page_icon="🆔", layout='centered', initial_sidebar_state='auto')
105
 
106
  # 网页标题
107
- st.subheader(r'🆔 :rainbow[EthPen - 域名铭文批量题写]', anchor=False, divider='rainbow')
 
108
 
109
  # 提醒
110
  st.markdown('### 在开始使用前,请仔细阅读相关说明,并在确认无误后打上 ✅。感谢您的理解与配合。')
 
1
  # EthPen.com
2
  # 最后更新日期:2023 年 9 月 5 日
3
 
 
4
  # 导入运行代码所需要的库
5
  import streamlit as st # streamlit app
6
  from web3 import Web3 # 与以太坊交互的库
 
9
  import re # 用于正则表达式
10
  import time # 用于时间相关
11
  import os # 用于操作系统文件
12
+ import base64
13
 
14
 
15
  # 许可使用开关
16
  approved_use = False
17
 
18
 
19
+ # 图片Base64
20
+ def image_to_base64(img_path):
21
+ with open(img_path, "rb") as image_file:
22
+ return base64.b64encode(image_file.read()).decode()
23
+
24
+
25
  # 检查 ETH 地址是否有效
26
  def is_valid_eth_address(address):
27
  if re.match("^0x[0-9a-fA-F]{40}$", address):
 
110
  st.set_page_config(page_title="EthPen - 批量题写域名铭文", page_icon="🆔", layout='centered', initial_sidebar_state='auto')
111
 
112
  # 网页标题
113
+ st.markdown(f'# <img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "ethpen_logo.svg"))}" alt="Image" width="64px" height="64px" style="border-radius: 8px; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/> :rainbow[域名铭文批量题写]', unsafe_allow_html=True)
114
+ st.subheader(r'', anchor=False, divider='rainbow')
115
 
116
  # 提醒
117
  st.markdown('### 在开始使用前,请仔细阅读相关说明,并在确认无误后打上 ✅。感谢您的理解与配合。')
pages/3_🪙_ 批量题写代币铭文.py CHANGED
@@ -1,7 +1,6 @@
1
  # EthPen.com
2
  # 最后更新日期:2023 年 9 月 5 日
3
 
4
-
5
  # 导入运行代码所需要的库
6
  import streamlit as st # streamlit app
7
  from web3 import Web3 # 与以太坊交互的库
@@ -10,12 +9,19 @@ import requests # 用于发送网络请求
10
  import re # 用于正则表达式
11
  import time # 用于时间相关
12
  import os # 用于操作系统文件
 
13
 
14
 
15
  # 许可使用开关
16
  approved_use = False
17
 
18
 
 
 
 
 
 
 
19
  # 检查 ETH 地址是否有效
20
  def is_valid_eth_address(address):
21
  if re.match("^0x[0-9a-fA-F]{40}$", address):
@@ -87,7 +93,8 @@ def send_transaction(w3, account_address, private_key, chain_id, gas_price, inpu
87
  st.set_page_config(page_title="EthPen - 批量题写代币铭文", page_icon="🪙", layout='centered', initial_sidebar_state='auto')
88
 
89
  # 网页标题
90
- st.subheader(r'🪙 :rainbow[EthPen - 代币铭文批量题写]', anchor=False, divider='rainbow')
 
91
 
92
  # 提醒
93
  st.markdown('### 在开始使用前,请仔细阅读相关说明,并在确认无误后打上 ✅。感谢您的理解与配合。')
 
1
  # EthPen.com
2
  # 最后更新日期:2023 年 9 月 5 日
3
 
 
4
  # 导入运行代码所需要的库
5
  import streamlit as st # streamlit app
6
  from web3 import Web3 # 与以太坊交互的库
 
9
  import re # 用于正则表达式
10
  import time # 用于时间相关
11
  import os # 用于操作系统文件
12
+ import base64
13
 
14
 
15
  # 许可使用开关
16
  approved_use = False
17
 
18
 
19
+ # 图片Base64
20
+ def image_to_base64(img_path):
21
+ with open(img_path, "rb") as image_file:
22
+ return base64.b64encode(image_file.read()).decode()
23
+
24
+
25
  # 检查 ETH 地址是否有效
26
  def is_valid_eth_address(address):
27
  if re.match("^0x[0-9a-fA-F]{40}$", address):
 
93
  st.set_page_config(page_title="EthPen - 批量题写代币铭文", page_icon="🪙", layout='centered', initial_sidebar_state='auto')
94
 
95
  # 网页标题
96
+ st.markdown(f'# <img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "ethpen_logo.svg"))}" alt="Image" width="64px" height="64px" style="border-radius: 8px; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/> :rainbow[代币铭文批量题写]', unsafe_allow_html=True)
97
+ st.subheader(r' ', anchor=False, divider='rainbow')
98
 
99
  # 提醒
100
  st.markdown('### 在开始使用前,请仔细阅读相关说明,并在确认无误后打上 ✅。感谢您的理解与配合。')
pages/4_💹️_铭文数据分析.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import streamlit as st
2
  import requests
3
  import os
@@ -10,23 +11,982 @@ import base64
10
  import configparser
11
  import pandas as pd
12
  import pytz
 
 
 
13
 
14
 
15
  # 使用你的Ethereum节点的RPC地址
16
- w3 = Web3(Web3.HTTPProvider("https://mainnet.infura.io/v3/9bbc614b8a1d49d59869e97d0ee3bf61"))
 
 
 
 
 
17
 
18
  # 获取当前文件目录
19
  current_dir = os.path.dirname(__file__)
20
  parent_dir = os.path.dirname(current_dir)
 
21
  # 构造数据库文件路径
22
- eths_db_file = os.path.join(parent_dir, 'data', 'eths_data.db')
23
- ethscrptions_db_file = os.path.join(parent_dir, 'data', 'ethscriptions_data.db')
24
- config_ini_file = os.path.join(parent_dir, 'data', 'config.ini')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
 
 
 
26
  ethscriptions_con = sqlite3.connect(ethscrptions_db_file)
27
  ethscriptions_cur = ethscriptions_con.cursor()
28
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30
  def get_eths_data():
31
  global eths_conn, eths_cur
32
  # Initialize connection to SQLite database
@@ -66,7 +1026,7 @@ def get_eths_data():
66
  eths_market_cap = eths_price * 21000000
67
  eths_owners = int(price_data['owners'])
68
  eths_volume24h = float(price_data['volume24h']) * eth_price
69
- eths_totalLocked = int(staking_data['totalLocked']) * 2
70
  eths_stakers = int(staking_data['stakers'])
71
  eths_tvl = float(staking_data['tvl']) * eth_price
72
  # Current date/time
@@ -79,11 +1039,59 @@ def get_eths_data():
79
  eths_conn.commit()
80
  except Exception as e:
81
  print(e)
82
- time.sleep(60)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
 
84
 
 
85
  def get_ethscriptions_data():
86
- global ethscrptions_db_file
87
  # 创建表
88
  ethscriptions_thread_con = sqlite3.connect(ethscrptions_db_file)
89
  ethscriptions_thread_cur = ethscriptions_thread_con.cursor()
@@ -92,7 +1100,6 @@ def get_ethscriptions_data():
92
 
93
  ethscriptions_thread_cur.execute('''CREATE TABLE IF NOT EXISTS process_blocks
94
  (block_number integer)''')
95
-
96
  # 获取最新的区块
97
  latest_block_number_thread = w3.eth.block_number
98
  ethscriptions_thread_cur.execute("SELECT MAX(block_number) FROM process_blocks")
@@ -108,9 +1115,7 @@ def get_ethscriptions_data():
108
  transactions = block['transactions']
109
  for tx in transactions:
110
  input_data = tx['input']
111
-
112
  if input_data.startswith(r"0x646174613a2c"):
113
- # if bytes.fromhex(input_data).startswith(b"data:,"):
114
 
115
  existing_data = ethscriptions_thread_cur.execute("SELECT * FROM data WHERE data=?",
116
  (input_data,)).fetchone()
@@ -125,7 +1130,7 @@ def get_ethscriptions_data():
125
  tx['hash'].hex()))
126
 
127
  ethscriptions_thread_con.commit()
128
- print(f'-----{input_data}-----')
129
 
130
  # 更新process_blocks表
131
  ethscriptions_thread_cur.execute("INSERT INTO process_blocks (block_number) VALUES (?)",
@@ -134,26 +1139,118 @@ def get_ethscriptions_data():
134
  ethscriptions_thread_cur.execute("DELETE FROM process_blocks WHERE block_number != ?",
135
  (block_number,))
136
  ethscriptions_thread_con.commit()
137
-
138
- print(block_number)
139
 
140
  # 获取下次开始的区块号
141
  ethscriptions_thread_cur.execute("SELECT block_number FROM process_blocks")
142
  result_thread = ethscriptions_thread_cur.fetchone()
143
  start_block_thread = result_thread[0] - 1
144
-
 
145
  # 获取最新的区块
146
  latest_block_number_thread = w3.eth.block_number
 
 
 
147
 
148
- # 等待
 
 
 
 
 
 
 
 
 
 
 
 
 
149
  time.sleep(15)
150
 
151
 
152
- # Streamlit app layout
153
- st.set_page_config(page_title="EthPen - 铭文数据分析", page_icon="💹", layout='centered', initial_sidebar_state='auto')
154
- st.subheader(r'💹 :rainbow[EthPen - 铭文数据分析]', anchor=False, divider='rainbow')
 
 
 
 
 
155
 
156
- st.markdown(f'### eths 数据总揽')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
157
  eths_conn = sqlite3.connect(eths_db_file)
158
  eths_cur = eths_conn.cursor()
159
  # 查询eths_data表中所有数据
@@ -179,7 +1276,6 @@ if eths_data:
179
 
180
  eths_stakers = st.metric(label='ETHS TVL', value=f'${eths_data[0][8]:,.0f}')
181
 
182
-
183
  st.markdown(f'### 新铭文题写')
184
  ethscriptions_cur.execute('''
185
  SELECT block_time, data
@@ -201,44 +1297,100 @@ for item in new_100_results_temp:
201
  result_df = pd.DataFrame(new_100_results)
202
  st.dataframe(result_df, use_container_width=True, hide_index=True)
203
 
204
- ethscriptions_cur.execute("SELECT block_number FROM process_blocks")
205
- index_result = ethscriptions_cur.fetchone()
206
- index_processed_block = index_result[0]
207
- index_latest_block_number = w3.eth.block_number
208
- st.markdown(f'*EthPen Ethscriptions Index Status: {index_processed_block}/{index_latest_block_number}*')
209
-
210
-
211
- download = st.toggle(" ", value=False)
212
- if download:
213
- current_dir = os.path.dirname(__file__)
214
- parent_dir = os.path.dirname(current_dir)
215
- # Constructing database file path
216
- ethscrptions_db_file = os.path.join(parent_dir, 'data', 'ethscriptions_data.db')
217
- st.download_button(
218
- label='下载数据库',
219
- data=open(ethscrptions_db_file, 'rb'),
220
- file_name='EthPen_ethscriptions_data.db',
221
- mime='application/octet-stream'
222
- )
223
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
224
  config = configparser.ConfigParser()
225
  config.read(config_ini_file)
226
- thread_start_state_value = config['thread_start_state']['state']
227
  if thread_start_state_value == '0':
228
- print('线程未启动。')
229
- config['thread_start_state']['state'] = '1'
230
  with open(config_ini_file, 'w') as configfile:
231
  config.write(configfile)
232
 
233
  eths_thread = threading.Thread(target=get_eths_data)
234
  ethscriptions_thread = threading.Thread(target=get_ethscriptions_data)
 
 
 
235
 
236
  # 启动线程
237
  eths_thread.start()
238
  ethscriptions_thread.start()
 
 
 
239
 
240
  # 等待线程结束
241
  eths_thread.join()
242
  ethscriptions_thread.join()
 
 
 
 
243
  else:
244
- print('线程已经启动。')
 
1
+ import pickle
2
  import streamlit as st
3
  import requests
4
  import os
 
11
  import configparser
12
  import pandas as pd
13
  import pytz
14
+ import json
15
+ from concurrent.futures import ThreadPoolExecutor
16
+ from collections import defaultdict
17
 
18
 
19
  # 使用你的Ethereum节点的RPC地址
20
+ infura_api_key_eths = os.environ.get('infura_api_key_eths', '')
21
+ etherscan_api_key = os.environ.get('etherscan_api_key', '')
22
+ w3 = Web3(Web3.HTTPProvider(infura_api_key_eths))
23
+
24
+ # Etherscan API
25
+ ETHERSCAN_API_URL = "https://api.etherscan.io/api"
26
 
27
  # 获取当前文件目录
28
  current_dir = os.path.dirname(__file__)
29
  parent_dir = os.path.dirname(current_dir)
30
+
31
  # 构造数据库文件路径
32
+ # eths 相关数据 文件路径
33
+ eths_db_file = os.path.join(parent_dir, 'data', 'eths_data.db') # eths 相关
34
+ # 所有已题写 ethscrptions 文件路径
35
+ ethscrptions_db_file = os.path.join(parent_dir, 'data', 'ethscriptions_data.db') # 所有 ethscriptions,只检查有或没有
36
+ # 配置文件 文件路径
37
+ config_ini_file = os.path.join(parent_dir, 'data', 'config.ini') # 程序配置文件
38
+ # ETCH 批量 文件路径
39
+ etch_batch_orders_db_file = os.path.join(parent_dir, 'data', 'batch_orders.db') # ETCH 批量购买订单数据库
40
+ # ETCH 单个 文件路径
41
+ etch_single_orders_db_file = os.path.join(parent_dir, 'data', 'single_orders.db') # ETCH 单个购买订单数据库
42
+ # 所有 ethscription 数据 文件路径
43
+ all_ethscription_db_file = os.path.join(parent_dir, 'data', 'all_ethscription.db') # ID 对应铭文的数据库
44
+ # Etch Market数据 pkl 文件路径
45
+ etch_market_pkl_file = os.path.join(parent_dir, 'data', 'etch_market_data.pkl')
46
+ # ETCH eths 数据 pkl 文件路径
47
+ etch_eths_pkl_file = os.path.join(parent_dir, 'data', 'etch_eths_data.pkl')
48
+ # ETCH eths 数据 pkl 文件路径
49
+ etch_mfpurrs_pkl_file = os.path.join(parent_dir, 'data', 'etch_mfpurrs_data.pkl')
50
+ # ETCH eths 数据 pkl 文件路径
51
+ etch_Hyppocritez_pkl_file = os.path.join(parent_dir, 'data', 'etch_Hyppocritez_data.pkl')
52
 
53
+ all_pkl_file = [etch_market_pkl_file, etch_eths_pkl_file, etch_mfpurrs_pkl_file, etch_Hyppocritez_pkl_file]
54
+
55
+ # 设置数据库
56
  ethscriptions_con = sqlite3.connect(ethscrptions_db_file)
57
  ethscriptions_cur = ethscriptions_con.cursor()
58
 
59
+ etch_batch_orders_show_con = sqlite3.connect(etch_batch_orders_db_file)
60
+ etch_batch_orders_show_cur = etch_batch_orders_show_con.cursor()
61
+
62
+ etch_single_orders_show_con = sqlite3.connect(etch_single_orders_db_file)
63
+ etch_single_orders_show_cur = etch_single_orders_show_con.cursor()
64
+
65
+ # all_ethscription_con = sqlite3.connect(all_ethscription_db_file)
66
+ # all_ethscription_cur = all_ethscription_con.cursor()
67
+
68
+ # 配置合约 ABI 文件
69
+ with open(os.path.join(parent_dir, 'data', 'batch_contract_abi.json'), "r") as batch:
70
+ contract_abi = json.load(batch)
71
+ batch_contract = w3.eth.contract(abi=contract_abi)
72
+
73
+ with open(os.path.join(parent_dir, 'data', 'single_contract_abi.json'), "r") as single:
74
+ contract_abi = json.load(single)
75
+ single_contract = w3.eth.contract(abi=contract_abi)
76
+
77
+ with open(os.path.join(parent_dir, 'data', 'single_contract_old_abi.json'), "r") as single_old:
78
+ contract_abi = json.load(single_old)
79
+ single_old_contract = w3.eth.contract(abi=contract_abi)
80
+
81
+ my_style = '''
82
+ <style>
83
+ .tag {
84
+ display: inline-block;
85
+ padding: 2px 6px;
86
+ background-color: #f2f2f2; /* 默认的背景颜色 */
87
+ border-radius: 5px; /* 圆角效果 */
88
+ margin: 0 2px;
89
+ transition: background-color 0.3s; /* 平滑的颜色过渡效果 */
90
+ }
91
+
92
+ .tag:hover {
93
+ background-color: #cffd51; /* 鼠标经过时的背景颜色 */
94
+ }
95
+
96
+ .tag:active {
97
+ background-color: #cffd51; /* 鼠标按下时的背景颜色 */
98
+ }
99
+ </style>
100
+ '''
101
+
102
+
103
+ # 图片Base64
104
+ def image_to_base64(img_path):
105
+ with open(img_path, "rb") as image_file:
106
+ return base64.b64encode(image_file.read()).decode()
107
+
108
+
109
+ # 查询 ETH token 价格
110
+ def get_eth_price():
111
+ try:
112
+ # 使用Etherscan的API endpoint获取以太坊的价格
113
+ url = f"https://api.etherscan.io/api?module=stats&action=ethprice&apikey=1I2ERGUVJZNJAFKY7MG4S317DUGRPXEQPF"
114
+ response = requests.get(url)
115
+ response.raise_for_status() # 将引发HTTPError,如果HTTP请求返回了不成功的状态码
116
+
117
+ data = response.json()
118
+
119
+ if data["message"] == "OK":
120
+ eth_price = float(data["result"]["ethusd"])
121
+ return eth_price
122
+ else:
123
+ print("Error fetching the ETH price")
124
+ return None
125
+ except requests.RequestException as e:
126
+ print(f"Error while accessing the API: {e}")
127
+ return None
128
+
129
+
130
+ # 查询 ETCH 交易量和交易数
131
+ def get_etch_volume_total(ethscription=None, days=None):
132
+ # 获取当前时间戳
133
+ current_time = time.time()
134
+
135
+ # 如果有days参数,则计算筛选的最小时间戳
136
+ min_timestamp = None
137
+ if days:
138
+ min_timestamp = current_time - days * 24 * 60 * 60
139
+
140
+ # 查询数据库并获取结果的函数
141
+ def query_db(db_file):
142
+ conn = sqlite3.connect(db_file)
143
+ cursor = conn.cursor()
144
+
145
+ # 基本的查询
146
+ query = "SELECT SUM(price), COUNT(DISTINCT transactionHash) FROM orders WHERE 1"
147
+
148
+ # 添加条件
149
+ if ethscription:
150
+ query += " AND ethscription = ?"
151
+
152
+ if min_timestamp:
153
+ query += " AND startTime >= ?"
154
+
155
+ # 执行查询
156
+ params = [ethscription] if ethscription else []
157
+ if min_timestamp:
158
+ params.append(min_timestamp)
159
+
160
+ cursor.execute(query, tuple(params))
161
+ result = cursor.fetchone()
162
+ conn.close()
163
+
164
+ # 检查并替换None值为0
165
+ result = tuple((0 if val is None else val) for val in result)
166
+
167
+ return result
168
+
169
+ # 从两个数据库中查询
170
+ single_result = query_db(etch_single_orders_db_file)
171
+ batch_result = query_db(etch_batch_orders_db_file)
172
+
173
+ # 计算总的交易量和交易数
174
+ total_volume = single_result[0] + batch_result[0]
175
+ total_count = single_result[1] + batch_result[1]
176
+
177
+ return total_volume, total_count
178
+
179
+
180
+ # 查询 ETCH 买家卖家数量
181
+ def get_etch_address_total(ethscription=None, days=None):
182
+
183
+ # 如果提供了天数限制,计算相应的UNIX时间戳
184
+ min_timestamp = None
185
+ if days:
186
+ current_time = time.time()
187
+ days_in_seconds = days * 24 * 60 * 60
188
+ min_timestamp = current_time - days_in_seconds
189
+
190
+ def query_db(db_file, ethscription, min_timestamp):
191
+ conn = sqlite3.connect(db_file)
192
+ cursor = conn.cursor()
193
+
194
+ where_clauses = []
195
+ params = []
196
+
197
+ if ethscription:
198
+ where_clauses.append("o.ethscription = ?")
199
+ params.append(ethscription)
200
+
201
+ if min_timestamp:
202
+ where_clauses.append("t.timeStamp >= ?")
203
+ params.append(min_timestamp)
204
+
205
+ where_str = ""
206
+ if where_clauses:
207
+ where_str = "WHERE " + " AND ".join(where_clauses)
208
+
209
+ # 查询addresses和signers
210
+ query = f"""
211
+ SELECT DISTINCT t.fromAddress, o.signer
212
+ FROM transactions t
213
+ JOIN orders o ON t.hash = o.transactionHash
214
+ {where_str}
215
+ """
216
+ cursor.execute(query, tuple(params))
217
+
218
+ addresses = set()
219
+ signers = set()
220
+ for row in cursor.fetchall():
221
+ addresses.add(row[0])
222
+ signers.add(row[1])
223
+
224
+ conn.close()
225
+
226
+ return addresses, signers
227
+
228
+ single_addresses, single_signers = query_db(etch_single_orders_db_file, ethscription, min_timestamp)
229
+ batch_addresses, batch_signers = query_db(etch_batch_orders_db_file, ethscription, min_timestamp)
230
+
231
+ # 合并两个集合
232
+ total_addresses = single_addresses | batch_addresses
233
+ total_signers = single_signers | batch_signers
234
+
235
+ # 计算结果
236
+ num_buyers = len(total_addresses)
237
+ num_sellers = len(total_signers)
238
+ total_traders = len(total_addresses | total_signers)
239
+
240
+ return num_buyers, num_sellers, total_traders
241
+
242
+
243
+ # 查询 ETCH 地址数
244
+ def get_etch_address_total_list(ethscription=None, days=None):
245
+ # 获取当前时间戳(Unix时间格式)
246
+ current_timestamp = int(datetime.now().timestamp())
247
+
248
+ # 计算几天前的时间戳,如果提供了days参数
249
+ days_ago_timestamp = current_timestamp - days * 24 * 60 * 60 if days else None
250
+
251
+ # 定义一个查询数据库的内部函数
252
+ def query_db(db_file):
253
+ conn = sqlite3.connect(db_file)
254
+ cursor = conn.cursor()
255
+
256
+ # 构建SQL查询语句
257
+ signer_query = "SELECT DISTINCT o.signer FROM orders o JOIN transactions t ON o.transactionHash = t.hash WHERE 1=1"
258
+ from_address_query = "SELECT DISTINCT t.fromAddress FROM transactions t WHERE 1=1"
259
+
260
+ params = []
261
+ if ethscription:
262
+ signer_query += " AND o.ethscription = ?"
263
+ params.append(ethscription)
264
+
265
+ if days_ago_timestamp:
266
+ signer_query += " AND t.timeStamp >= ?"
267
+ from_address_query += " AND t.timeStamp >= ?"
268
+ params.append(days_ago_timestamp)
269
+
270
+ cursor.execute(signer_query, tuple(params))
271
+ signer_addresses = set([item[0] for item in cursor.fetchall()])
272
+
273
+ cursor.execute(from_address_query, tuple(params[:1] if days_ago_timestamp else []))
274
+ from_address_addresses = set([item[0] for item in cursor.fetchall()])
275
+
276
+ conn.close()
277
+
278
+ return signer_addresses, from_address_addresses
279
+
280
+ # 查询两个数据库并取并集
281
+ single_signer_addresses, single_from_address_addresses = query_db(etch_single_orders_db_file)
282
+ batch_signer_addresses, batch_from_address_addresses = query_db(etch_batch_orders_db_file)
283
+
284
+ total_signer_addresses = single_signer_addresses.union(batch_signer_addresses)
285
+ total_from_address_addresses = single_from_address_addresses.union(batch_from_address_addresses)
286
+
287
+ # 获取所有的signer和fromAddress地址,进行去重
288
+ all_addresses = total_signer_addresses.union(total_from_address_addresses)
289
+
290
+ return {
291
+ 'signer_count': len(total_signer_addresses),
292
+ 'from_address_count': len(total_from_address_addresses),
293
+ 'unique_addresses': all_addresses,
294
+ 'unique_address_count': len(all_addresses)
295
+ }
296
+
297
+
298
+ # 查询 ETCH 矿工费
299
+ def get_etch_fee_total(ethscription=None):
300
+ # 定义用于统计的变量
301
+ total_fee = 0
302
+
303
+ # 定义数据库路径
304
+ dbs = [etch_single_orders_db_file, etch_batch_orders_db_file]
305
+
306
+ # 对每个数据库执行查询操作
307
+ for db in dbs:
308
+ # 创建数据库连接
309
+ conn = sqlite3.connect(db)
310
+ cursor = conn.cursor()
311
+
312
+ # 根据是否有ethscription参数来生成不同的查询语句
313
+ if ethscription:
314
+ # 过滤特定ethscription并统计transactions中的transactionFee
315
+ query = """
316
+ SELECT SUM(transactions.transactionFee)
317
+ FROM transactions
318
+ JOIN orders ON transactions.hash = orders.transactionHash
319
+ WHERE orders.ethscription = ?
320
+ """
321
+ cursor.execute(query, (ethscription,))
322
+ else:
323
+ # 不过滤ethscription,直接统计transactions中的transactionFee
324
+ query = "SELECT SUM(transactionFee) FROM transactions"
325
+ cursor.execute(query)
326
+
327
+ # 获取查询结果
328
+ result = cursor.fetchone()
329
+ if result[0]: # 防止结果为None
330
+ total_fee += result[0]
331
+
332
+ # 关闭数据库连接
333
+ conn.close()
334
+
335
+ return total_fee
336
+
337
+
338
+ # etch 买入次数卖出次数买入金额卖出金额排行
339
+ def get_etch_combined_rank(ethscription=None):
340
+ # 连接到两个数据库
341
+ conn_single = sqlite3.connect(etch_single_orders_db_file)
342
+ conn_batch = sqlite3.connect(etch_batch_orders_db_file)
343
+ cur_single = conn_single.cursor()
344
+ cur_batch = conn_batch.cursor()
345
+
346
+ # 设置过滤条件
347
+ filter_condition = ""
348
+ if ethscription:
349
+ filter_condition = f" WHERE ethscription = '{ethscription}'"
350
+
351
+ # 查询单订单数据库的卖出信息
352
+ cur_single.execute(f"""
353
+ SELECT
354
+ signer,
355
+ SUM(price) as sell_amount,
356
+ COUNT(signer) as sell_count
357
+ FROM orders
358
+ {filter_condition}
359
+ GROUP BY signer
360
+ """)
361
+ single_sell_data = cur_single.fetchall()
362
+
363
+ # 查询批量订单数据库的卖出信息
364
+ cur_batch.execute(f"""
365
+ SELECT
366
+ signer,
367
+ SUM(price) as sell_amount,
368
+ COUNT(signer) as sell_count
369
+ FROM orders
370
+ {filter_condition}
371
+ GROUP BY signer
372
+ """)
373
+ batch_sell_data = cur_batch.fetchall()
374
+
375
+ # 查询购买信息
376
+ buy_query = f"""
377
+ SELECT
378
+ fromAddress,
379
+ SUM(value) as buy_amount,
380
+ COUNT(fromAddress) as buy_count
381
+ FROM transactions
382
+ JOIN orders ON transactions.hash = orders.transactionHash
383
+ {filter_condition}
384
+ GROUP BY fromAddress
385
+ """
386
+ cur_single.execute(buy_query)
387
+ single_buy_data = cur_single.fetchall()
388
+
389
+ cur_batch.execute(buy_query)
390
+ batch_buy_data = cur_batch.fetchall()
391
+
392
+ # 数据处理,确保每个地址的所有交易都被汇总
393
+ data = {}
394
+
395
+ for signer, amount, count in single_sell_data + batch_sell_data:
396
+ signer = signer.lower()
397
+ if signer not in data:
398
+ data[signer] = {'sell_amount': 0, 'sell_count': 0, 'buy_amount': 0, 'buy_count': 0}
399
+ data[signer]['sell_amount'] += amount
400
+ data[signer]['sell_count'] += count
401
+
402
+ for buyer, amount, count in single_buy_data + batch_buy_data:
403
+ buyer = buyer.lower()
404
+ if buyer not in data:
405
+ data[buyer] = {'sell_amount': 0, 'sell_count': 0, 'buy_amount': 0, 'buy_count': 0}
406
+ data[buyer]['buy_amount'] += amount
407
+ data[buyer]['buy_count'] += count
408
+
409
+ # 结果整理
410
+ result = []
411
+ for address, values in data.items():
412
+ net_buy = values['buy_amount'] - values['sell_amount']
413
+ net_sell = values['sell_amount'] - values['buy_amount']
414
+ volume = values['buy_amount'] + values['sell_amount']
415
+ result.append({
416
+ 'ETH 地址': address,
417
+ '买入次数': values['buy_count'],
418
+ '卖出次数': values['sell_count'],
419
+ '净买入 ETH': net_buy,
420
+ '净卖出 ETH': net_sell,
421
+ '交易总额 ETH': volume
422
+ })
423
+ conn_batch.close()
424
+ conn_single.close()
425
+ return result
426
+
427
+
428
+ # 统计 price 排行
429
+ def get_etch_top_price_rank(ethscription=None, limit=100):
430
+ # 连接到两个sqlite数据库
431
+ conn_single = sqlite3.connect(etch_single_orders_db_file)
432
+ conn_batch = sqlite3.connect(etch_batch_orders_db_file)
433
+
434
+ if ethscription:
435
+ filter_clause = f"AND ethscription = '{ethscription}'"
436
+ else:
437
+ filter_clause = ""
438
+
439
+ # 使用SQL查询进行排序、计数和筛选
440
+ query_template = f"""
441
+ SELECT
442
+ t.timeStamp, t.fromAddress, t.value, t.hash, COUNT(o.transactionHash) as order_count
443
+ FROM
444
+ transactions t
445
+ JOIN
446
+ orders o ON t.hash = o.transactionHash
447
+ WHERE
448
+ 1 = 1 {filter_clause}
449
+ GROUP BY
450
+ t.hash
451
+ ORDER BY
452
+ t.value DESC
453
+ LIMIT {limit}
454
+ """
455
+
456
+ data_single = conn_single.execute(query_template).fetchall()
457
+ data_batch = conn_batch.execute(query_template).fetchall()
458
+
459
+ combined_data = data_single + data_batch
460
+ combined_data = sorted(combined_data, key=lambda x: x[2], reverse=True)[:limit]
461
+
462
+ results = []
463
+ for item in combined_data:
464
+ timeStamp, fromAddress, value, hash_val, order_count = item
465
+ formatted_time = datetime.utcfromtimestamp(timeStamp).strftime('%Y-%m-%d %H:%M:%S')
466
+ results.append({
467
+ "时间": formatted_time,
468
+ "买家": fromAddress,
469
+ "金额": value,
470
+ "交易哈希": hash_val,
471
+ "订单数量": order_count
472
+ })
473
+ conn_batch.close()
474
+ conn_single.close()
475
+ return results
476
+
477
+
478
+ # 统计 ethscriptionID 排行
479
+ def get_etch_top_ethscriptionid_rank(ethscription=None):
480
+ # 定义数据库文件路径
481
+ single_orders_db = etch_single_orders_db_file
482
+ batch_orders_db = etch_batch_orders_db_file
483
+
484
+ # 定义从一个数据库中获取所有ethscriptionId的函数
485
+ def get_ethscriptionids_from_db(db_path, ethscription):
486
+ conn = sqlite3.connect(db_path)
487
+ cursor = conn.cursor()
488
+ if ethscription: # 如果提供了ethscription参数,按该参数过滤数据
489
+ cursor.execute("SELECT ethscriptionId, ethscription FROM orders WHERE ethscription = ?", (ethscription,))
490
+ else:
491
+ cursor.execute("SELECT ethscriptionId, ethscription FROM orders")
492
+ ids = cursor.fetchall()
493
+ conn.close()
494
+ return ids
495
+
496
+ # 从两个数据库中获取ethscriptionId数据
497
+ single_ids = get_ethscriptionids_from_db(single_orders_db, ethscription)
498
+ batch_ids = get_ethscriptionids_from_db(batch_orders_db, ethscription)
499
+
500
+ # 合并从两个数据库中获取的数据
501
+ combined_ids = single_ids + batch_ids
502
+
503
+ # 使用defaultdict统计ethscriptionId的出现次数
504
+ counter = defaultdict(int)
505
+ id_to_ethscription = dict()
506
+ for eid, ethscription in combined_ids:
507
+ counter[eid] += 1
508
+ id_to_ethscription[eid] = ethscription
509
+
510
+ # 根据出现次数对ethscriptionId进行排序
511
+ sorted_ids = sorted(counter.keys(), key=lambda x: counter[x], reverse=True)
512
+
513
+ # 返回排序后的ethscriptionId列表及其对应的ethscription描述
514
+ result = [
515
+ {
516
+ 'EthscriptionID': eid,
517
+ '铭文类别': id_to_ethscription[eid],
518
+ '交易次数': counter[eid]
519
+ }
520
+ for eid in sorted_ids
521
+ ]
522
+ return result[:100]
523
+
524
+
525
+ # 列出最近 100 条交易
526
+ def get_etch_last_100_orders(ethscription=None):
527
+
528
+ query = """
529
+ SELECT
530
+ t.blockNumber, t.timeStamp, t.hash, t.fromAddress, o.signer, o.price, o.ethscriptionId
531
+ FROM
532
+ orders o
533
+ JOIN
534
+ transactions t
535
+ ON
536
+ o.transactionHash = t.hash
537
+ {}
538
+ ORDER BY
539
+ t.timeStamp DESC
540
+ LIMIT 100
541
+ """
542
+
543
+ if ethscription:
544
+ filter_query = "WHERE o.ethscription = ?"
545
+ query = query.format(filter_query)
546
+ params = (ethscription,)
547
+ else:
548
+ query = query.format("")
549
+ params = tuple()
550
+
551
+ # Combine results from both databases
552
+ results = []
553
+ for db_file in [etch_single_orders_db_file, etch_batch_orders_db_file]:
554
+ with sqlite3.connect(db_file) as conn:
555
+ cursor = conn.cursor()
556
+ cursor.execute(query, params)
557
+ results.extend(cursor.fetchall())
558
+
559
+ # Sort combined results by timestamp and pick the top 100
560
+ results = sorted(results, key=lambda x: x[1], reverse=True)[:100]
561
+
562
+ # Transform the results into a list of dictionaries and process timeStamp
563
+ keys = ["区块号", "时间", "交易哈希", "买家", "卖家", "金额", "EthscriptionId"]
564
+ processed_results = []
565
+ for result in results:
566
+ processed_result = dict(zip(keys, result))
567
+ processed_result["时间"] = datetime.utcfromtimestamp(processed_result["时间"]).strftime(
568
+ '%Y-%m-%d %H:%M:%S')
569
+ processed_results.append(processed_result)
570
+
571
+ return processed_results
572
+
573
+
574
+ # 画出价格成交量成交额图
575
+ def daily_transaction_volume_and_price_data(ethscription=None):
576
+ eth_price = get_eth_price()
577
+ # 连接到两个数据库
578
+ conn_single = sqlite3.connect(etch_single_orders_db_file)
579
+ conn_batch = sqlite3.connect(etch_batch_orders_db_file)
580
+
581
+ # 构造查询语句,获取orders和transactions的数据
582
+ if ethscription:
583
+ query_single = f"SELECT o.*, t.timeStamp FROM orders o INNER JOIN transactions t ON o.transactionHash = t.hash WHERE o.ethscription = '{ethscription}'"
584
+ query_batch = f"SELECT o.*, t.timeStamp FROM orders o INNER JOIN transactions t ON o.transactionHash = t.hash WHERE o.ethscription = '{ethscription}'"
585
+ else:
586
+ query_single = "SELECT o.*, t.timeStamp FROM orders o INNER JOIN transactions t ON o.transactionHash = t.hash"
587
+ query_batch = "SELECT o.*, t.timeStamp FROM orders o INNER JOIN transactions t ON o.transactionHash = t.hash"
588
+
589
+ df_single = pd.read_sql(query_single, conn_single)
590
+ df_batch = pd.read_sql(query_batch, conn_batch)
591
+
592
+ # 合并两个数据集
593
+ df = pd.concat([df_single, df_batch])
594
+
595
+ # 转换时间戳为日期
596
+ df['date'] = pd.to_datetime(df['timeStamp'], unit='s').dt.date
597
+
598
+ # 计算单价
599
+ df['unit_price'] = df['price'] / df['quantity'] * eth_price
600
+
601
+ # 按日期分组并聚合
602
+ grouped = df.groupby('date').agg({
603
+ 'unit_price': ['first', 'max', 'min', 'last'],
604
+ 'quantity': 'sum',
605
+ 'price': 'sum'
606
+ })
607
+
608
+ grouped.columns = ['开', '高', '低', '收', '交易量', '交易额']
609
+ conn_single.close()
610
+ conn_batch.close()
611
+ return grouped
612
+
613
+
614
+ # 获取所有 ethscription 数据
615
+ def get_all_ethscription_data(all_ethscription_conn, all_ethscription_cursor, ethscriptionId, db_lock):
616
+ endpoint = f"/ethscriptions/{ethscriptionId}"
617
+ try:
618
+ response = requests.get("https://mainnet-api.ethscriptions.com/api" + endpoint)
619
+ response.raise_for_status() # Raises stored HTTPError, if one occurred.
620
+ data = response.json()
621
+
622
+ with db_lock: # Acquire DB lock to update the database
623
+ creator = data.get('creator', None)
624
+ creation_timestamp = data.get('creation_timestamp', None)
625
+ ethscription_number = data.get('ethscription_number', None)
626
+ mimetype = data.get('mimetype', None)
627
+ image_removed_by_request = data.get('image_removed_by_request_of_rights_holder', None)
628
+ content_uri = data.get('content_uri', None)
629
+ # 检查 name 是否存在于 collections 中
630
+ if data['collections']:
631
+ name = data['collections'][0]['name']
632
+ else:
633
+ name = None
634
+ # 5. 更新数据到相应的列
635
+ update_query = '''
636
+ UPDATE id_content
637
+ SET ethscription = ?,
638
+ creator = ?,
639
+ creation_timestamp = ?,
640
+ ethscription_number = ?,
641
+ mimetype = ?,
642
+ image_removed_by_request_of_rights_holder = ?,
643
+ content_uri = ?,
644
+ name = ?
645
+ WHERE ethscriptionId = ?
646
+ '''
647
+ all_ethscription_cursor.execute(update_query, (data, creator, creation_timestamp, ethscription_number, mimetype,
648
+ image_removed_by_request, content_uri, name, ethscriptionId))
649
+
650
+ all_ethscription_conn.commit()
651
+
652
+ except requests.RequestException as req_err:
653
+ print(f"Request error for identifier {ethscriptionId}: {req_err}")
654
+ except ValueError:
655
+ print(f"JSON decoding error for identifier {ethscriptionId}")
656
+ except Exception as e:
657
+ print(f"An unexpected error occurred for identifier {ethscriptionId}: {e}")
658
+
659
 
660
+ # 获取单个 ethscription 数据
661
+ def get_specific_ethscription(ethscription_identifier):
662
+ endpoint = f"/ethscriptions/{ethscription_identifier}"
663
+
664
+ try:
665
+ response = requests.get("https://mainnet-api.ethscriptions.com/api" + endpoint, timeout=3)
666
+ response.raise_for_status() # Will raise an HTTPError if the HTTP request returned an unsuccessful status code
667
+
668
+ if response.status_code == 200:
669
+ # Assuming the Content-Type is JSON
670
+ return response.json()
671
+ else:
672
+ print(f'Unexpected status code {response.status_code} for identifier: {ethscription_identifier}')
673
+ return None
674
+
675
+ except requests.Timeout:
676
+ print(f"Request timed out for identifier: {ethscription_identifier}")
677
+ except requests.ConnectionError:
678
+ print(f"Connection error for identifier: {ethscription_identifier}")
679
+ except requests.TooManyRedirects:
680
+ print(f"Too many redirects for identifier: {ethscription_identifier}")
681
+ except requests.HTTPError as http_err:
682
+ print(f"HTTP error for identifier {ethscription_identifier}: {http_err}")
683
+ except requests.RequestException as req_err:
684
+ print(f"General error for identifier {ethscription_identifier}: {req_err}")
685
+ except Exception as e:
686
+ # Catch-all for any other unforeseen exceptions
687
+ print(f"An unexpected error occurred for identifier {ethscription_identifier}: {e}")
688
+
689
+ return None # Ensure the function always returns a value even in the case of an error
690
+
691
+
692
+ # 通过 ID 从 all_ethscription 获取铭文内容
693
+ def get_ethscription_by_id(ethscription_id, all_ethscription_cur):
694
+ # 执行查询语句
695
+ all_ethscription_cur.execute("SELECT name FROM id_content WHERE ethscriptionId=?", (ethscription_id,))
696
+ result = all_ethscription_cur.fetchone()
697
+
698
+ # 返回结果
699
+ if result:
700
+ return result[0]
701
+ else:
702
+ return None
703
+
704
+
705
+ # 更新 all_ethscription 铭文内容
706
+ def update_all_ethscription_with_id(all_ethscription_conn, all_ethscription_cursor, db_lock):
707
+ all_ethscription_cursor.execute(
708
+ "SELECT ethscriptionId FROM id_content WHERE ethscription IS NULL OR ethscription = ''")
709
+ rows = all_ethscription_cursor.fetchall()
710
+ print(f'Not Processed EthscriptionID: {len(rows)}')
711
+
712
+ # Use a thread pool with a fixed number of threads (e.g., 10 threads)
713
+ with ThreadPoolExecutor(max_workers=10) as executor:
714
+ for row in rows:
715
+ ethscriptionId = row[0]
716
+ executor.submit(get_all_ethscription_data, all_ethscription_conn, all_ethscription_cursor, ethscriptionId, db_lock)
717
+
718
+
719
+ # 根据 hash 查重
720
+ def single_transaction_exists(tx_hash, single_orders_cursor):
721
+ single_orders_cursor.execute("SELECT hash FROM transactions WHERE hash = ?", (tx_hash,))
722
+ return bool(single_orders_cursor.fetchone())
723
+
724
+
725
+ # 批量购买合约 tx 检查
726
+ def batch_transaction_exists(tx_hash, batch_orders_cursor):
727
+ batch_orders_cursor.execute("SELECT hash FROM transactions WHERE hash = ?", (tx_hash,))
728
+ return bool(batch_orders_cursor.fetchone())
729
+
730
+
731
+ # ABI 处理单笔购买合约交易 input data 数据
732
+ def parse_single_input_data(input_data):
733
+ if input_data[:10] == '0xd2234424':
734
+ decoded_data = single_old_contract.decode_function_input(input_data)
735
+ return decoded_data # Returns the input data as a dictionary
736
+ if input_data[:10] == '0xd92a1740':
737
+ decoded_data = single_contract.decode_function_input(input_data)
738
+ return decoded_data # Returns the input data as a dictionary
739
+
740
+
741
+ # ABI 处理批量购买合约交易 input data 数据
742
+ def parse_batch_input_data(input_data):
743
+ decoded_data = batch_contract.decode_function_input(input_data)
744
+ return decoded_data # Returns the input data as a dictionary
745
+
746
+
747
+ # 处理和储存单笔购买合约交易数据
748
+ def process_and_store_single_transaction(tx, single_orders_cursor, single_orders_conn, all_ethscription_cur):
749
+ if tx['txreceipt_status'] == '1':
750
+ if tx['input'][:10] == '0xd2234424' or tx['input'][:10] == '0xd92a1740':
751
+ if not single_transaction_exists(tx['hash'],
752
+ single_orders_cursor): # Check if transaction already exists in DB
753
+ try:
754
+ data = parse_single_input_data(tx['input'])
755
+ # Calculate transaction fee (gasPrice * gasUsed)
756
+ transaction_fee = (float(tx['gasPrice']) * float(tx['gasUsed'])) / 1e18
757
+
758
+ function_name = data[0]
759
+ params = data[1]
760
+ # Handle bytes data
761
+ for key, value in params.items():
762
+ if isinstance(value, bytes):
763
+ params[key] = value.hex()
764
+ # 获取并处理订单数据
765
+ order = params.get('order', {})
766
+ if order: # 确保order不为空
767
+ for key in ['ethscriptionId', 'r', 's', 'params']:
768
+ if key in order: # 确保键在order中
769
+ order[key] = order[key].hex()
770
+
771
+ ethscription = get_ethscription_by_id(f'0x{order["ethscriptionId"]}', all_ethscription_cur)
772
+ if ethscription is None:
773
+ ethscription = get_specific_ethscription(f'0x{order["ethscriptionId"]}')
774
+ if ethscription is not None:
775
+ if ethscription['collections']:
776
+ ethscription = ethscription['collections'][0]['name']
777
+ else:
778
+ ethscription = None
779
+ # Store transaction data first
780
+ single_orders_cursor.execute('''
781
+ INSERT OR IGNORE INTO transactions (blockNumber, timeStamp, hash, fromAddress, value, txreceipt_status, transactionFee)
782
+ VALUES (?, ?, ?, ?, ?, ?, ?)
783
+ ''', (
784
+ tx['blockNumber'], tx['timeStamp'], tx['hash'], tx['from'], float(tx['value']) / 1e18,
785
+ tx['txreceipt_status'],
786
+ transaction_fee))
787
+
788
+ single_orders_cursor.execute('''
789
+ INSERT INTO orders (transactionHash, signer, creator, ethscriptionId, quantity, currency, price,
790
+ startTime, endTime, protocolFeeDiscounted, creatorFee, params, v, r, s, ethscription)
791
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
792
+ ''', (
793
+ tx['hash'], order['signer'], order['creator'], '0x' + order['ethscriptionId'], order['quantity'],
794
+ order['currency'], float(order['price']) / 1e18, order['startTime'], order['endTime'],
795
+ order['protocolFeeDiscounted'], order['creatorFee'], json.dumps(order['params']),
796
+ order['v'], order['r'], order['s'], ethscription))
797
+ single_orders_conn.commit()
798
+
799
+ except Exception as e:
800
+ print(f"Error processing transaction {tx['hash']}: {e}")
801
+ pass
802
+
803
+
804
+ # 处理和储存批量购买合约交易数据
805
+ def process_and_store_batch_transaction(tx, batch_orders_cursor, batch_orders_conn, all_ethscription_cur):
806
+ if not batch_transaction_exists(tx['hash'], batch_orders_cursor): # Check if transaction already exists in DB
807
+ try:
808
+ data = parse_batch_input_data(tx['input'])
809
+
810
+ # Calculate transaction fee (gasPrice * gasUsed)
811
+ transaction_fee = (float(tx['gasPrice']) * float(tx['gasUsed'])) / 1e18
812
+
813
+ function_name = data[0]
814
+ params = data[1]
815
+ # Handle bytes data
816
+ for key, value in params.items():
817
+ if isinstance(value, bytes):
818
+ params[key] = value.hex()
819
+
820
+ # Insert order data
821
+ orders_data = params.get('orders', [])
822
+
823
+ # Store transaction data first
824
+ batch_orders_cursor.execute('''
825
+ INSERT OR IGNORE INTO transactions (blockNumber, timeStamp, hash, fromAddress, value, txreceipt_status, transactionFee)
826
+ VALUES (?, ?, ?, ?, ?, ?, ?)
827
+ ''', (
828
+ tx['blockNumber'], tx['timeStamp'], tx['hash'], tx['from'], float(tx['value']) / 1e18,
829
+ tx['txreceipt_status'],
830
+ transaction_fee))
831
+
832
+ for order in orders_data:
833
+ for key in ['ethscriptionId', 'r', 's', 'params']:
834
+ order[key] = order[key].hex()
835
+
836
+ ethscription = get_ethscription_by_id(f'0x{order["ethscriptionId"]}', all_ethscription_cur)
837
+ if ethscription is None:
838
+ ethscription = get_specific_ethscription(f'0x{order["ethscriptionId"]}')
839
+ if ethscription is not None:
840
+ if ethscription['collections']:
841
+ ethscription = ethscription['collections'][0]['name']
842
+ else:
843
+ ethscription = None
844
+
845
+ batch_orders_cursor.execute('''
846
+ INSERT INTO orders (transactionHash, signer, creator, ethscriptionId, quantity, currency, price,
847
+ startTime, endTime, protocolFeeDiscounted, creatorFee, params, v, r, s, ethscription)
848
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
849
+ ''', (tx['hash'], order['signer'], order['creator'], '0x' + order['ethscriptionId'], order['quantity'],
850
+ order['currency'], float(order['price']) / 1e18, order['startTime'], order['endTime'],
851
+ order['protocolFeeDiscounted'], order['creatorFee'], json.dumps(order['params']),
852
+ order['v'], order['r'], order['s'], ethscription))
853
+
854
+ batch_orders_conn.commit()
855
+
856
+ except Exception as e:
857
+ print(f"Error processing transaction {tx['hash']}: {e}")
858
+ pass
859
+
860
+
861
+ # 获取所有单笔购买合约交易
862
+ def get_single_all_transactions(single_orders_cursor, single_orders_conn, all_ethscription_cur):
863
+ single_orders_cursor.execute('SELECT MAX(blockNumber) FROM transactions')
864
+ start_block_number = single_orders_cursor.fetchone()[0] - 100
865
+ print(f'Get single all transactions: {start_block_number}')
866
+
867
+ # 整理 ethscription 为空的内容
868
+ single_orders_cursor.execute("SELECT ethscriptionId FROM orders WHERE ethscription IS NULL OR ethscription = ''")
869
+ ethscription_ids = [row[0] for row in single_orders_cursor.fetchall()]
870
+ for eid in ethscription_ids:
871
+ all_ethscription_cur.execute("SELECT name FROM id_content WHERE ethscriptionId = ?", (eid,))
872
+ result = all_ethscription_cur.fetchone()
873
+ if result:
874
+ # If found, update the ethscription in orders table
875
+ single_orders_cursor.execute("UPDATE orders SET ethscription = ? WHERE ethscriptionId = ?", (result[0], eid))
876
+
877
+ # Commit changes and close connections
878
+ single_orders_cursor.commit()
879
+
880
+ page = 1
881
+
882
+ while True:
883
+ time.sleep(1)
884
+ params = {
885
+ "module": "account",
886
+ "action": "txlist",
887
+ "address": "0x57b8792c775D34Aa96092400983c3e112fCbC296",
888
+ "startblock": start_block_number,
889
+ "endblock": 99999999,
890
+ "page": page,
891
+ "offset": 100,
892
+ "sort": "asc",
893
+ "apikey": etherscan_api_key
894
+ }
895
+ try:
896
+ response = requests.get(ETHERSCAN_API_URL, params=params)
897
+ response.raise_for_status() # This will raise an HTTPError if the HTTP request returned an unsuccessful status code
898
+ txs = response.json().get('result', [])
899
+ except requests.ConnectionError:
900
+ print("Failed to connect to the server.")
901
+ txs = []
902
+ except requests.Timeout:
903
+ print("The request timed out.")
904
+ txs = []
905
+ except requests.RequestException as e:
906
+ print(f"An error occurred: {e}")
907
+ txs = []
908
+ except ValueError: # This will catch JSON decoding errors
909
+ print("Failed to parse the response.")
910
+ txs = []
911
+
912
+ if not txs:
913
+ break
914
+
915
+ for tx in txs:
916
+ process_and_store_single_transaction(tx, single_orders_cursor, single_orders_conn, all_ethscription_cur)
917
+ print(f'tx:{page}')
918
+
919
+ page += 1
920
+
921
+
922
+ # 获取所有批量购买合约交易
923
+ def get_batch_all_transactions(batch_orders_cursor, batch_orders_conn, all_ethscription_cur):
924
+ batch_orders_cursor.execute('SELECT MAX(blockNumber) FROM transactions')
925
+ start_block_number = batch_orders_cursor.fetchone()[0] - 100
926
+ print(f'Get batch all transactions: {start_block_number}')
927
+
928
+ # 整理 ethscription 为空的内容
929
+ batch_orders_cursor.execute("SELECT ethscriptionId FROM orders WHERE ethscription IS NULL OR ethscription = ''")
930
+ ethscription_ids = [row[0] for row in batch_orders_cursor.fetchall()]
931
+ for eid in ethscription_ids:
932
+ all_ethscription_cur.execute("SELECT name FROM id_content WHERE ethscriptionId = ?", (eid,))
933
+ result = all_ethscription_cur.fetchone()
934
+ if result:
935
+ # If found, update the ethscription in orders table
936
+ batch_orders_cursor.execute("UPDATE orders SET ethscription = ? WHERE ethscriptionId = ?",
937
+ (result[0], eid))
938
+
939
+ # Commit changes and close connections
940
+ batch_orders_cursor.commit()
941
+
942
+ page = 1
943
+
944
+ while True:
945
+ params = {
946
+ "module": "account",
947
+ "action": "txlist",
948
+ "address": "0x941Bc2E04A776d436E183Fe4204Bb84FeBA564D3",
949
+ "startblock": start_block_number,
950
+ "endblock": 99999999,
951
+ "page": page,
952
+ "offset": 100,
953
+ "sort": "asc",
954
+ "apikey": etherscan_api_key
955
+ }
956
+
957
+ try:
958
+ response = requests.get(ETHERSCAN_API_URL, params=params)
959
+ response.raise_for_status() # This will raise an HTTPError if the HTTP request returned an unsuccessful status code
960
+ txs = response.json().get('result', [])
961
+ except requests.ConnectionError:
962
+ print("Failed to connect to the server.")
963
+ txs = []
964
+ except requests.Timeout:
965
+ print("The request timed out.")
966
+ txs = []
967
+ except requests.RequestException as e:
968
+ print(f"An error occurred: {e}")
969
+ txs = []
970
+ except ValueError: # This will catch JSON decoding errors
971
+ print("Failed to parse the response.")
972
+ txs = []
973
+
974
+ if not txs:
975
+ break
976
+ if page == 1:
977
+ for tx in txs[2:]:
978
+ process_and_store_batch_transaction(tx, batch_orders_cursor, batch_orders_conn, all_ethscription_cur)
979
+ else:
980
+ for tx in txs:
981
+ process_and_store_batch_transaction(tx, batch_orders_cursor, batch_orders_conn, all_ethscription_cur)
982
+
983
+ # for tx in txs:
984
+ # process_and_store_transaction(tx)
985
+
986
+ page += 1
987
+
988
+
989
+ # 从 ETCH 获取 eths 数据(线程入口)
990
  def get_eths_data():
991
  global eths_conn, eths_cur
992
  # Initialize connection to SQLite database
 
1026
  eths_market_cap = eths_price * 21000000
1027
  eths_owners = int(price_data['owners'])
1028
  eths_volume24h = float(price_data['volume24h']) * eth_price
1029
+ eths_totalLocked = int(staking_data['totalLocked'])
1030
  eths_stakers = int(staking_data['stakers'])
1031
  eths_tvl = float(staking_data['tvl']) * eth_price
1032
  # Current date/time
 
1039
  eths_conn.commit()
1040
  except Exception as e:
1041
  print(e)
1042
+ etch_type_of_data = [None, 'erc-20 eths token', 'mfpurrs', 'Hyppocritez']
1043
+
1044
+ for index, i in enumerate(etch_type_of_data):
1045
+ # ETCH 全网总成交量和成交订单数
1046
+ etch_total_volume_and_orders = get_etch_volume_total(ethscription=i)
1047
+ # ETCH 全网 30 日成交量和成交订单数
1048
+ etch_30d_volume_and_orders = get_etch_volume_total(ethscription=i, days=30)
1049
+ # ETCH 全网 7 日成交量和成交订单数
1050
+ etch_7d_volume_and_orders = get_etch_volume_total(ethscription=i, days=7)
1051
+ # ETCH 全网 3 日成交量和成交订单数
1052
+ etch_3d_volume_and_orders = get_etch_volume_total(ethscription=i, days=3)
1053
+ # ETCH 全网 1 日成交量和成交订单数
1054
+ etch_1d_volume_and_orders = get_etch_volume_total(ethscription=i, days=1)
1055
+ # ETCH 全网总买家和卖家
1056
+ etch_total_buyers_sellers = get_etch_address_total(ethscription=i)
1057
+ # ETCH 全网 1 日买家和卖家
1058
+ etch_1d_buyers_sellers = get_etch_address_total(ethscription=i, days=1)
1059
+ # ETCH 全网总矿工费
1060
+ etch_total_fee = get_etch_fee_total(ethscription=i)
1061
+ # ETCH 买入数卖出数买入金额卖出金额排行
1062
+ etch_combined_rank = get_etch_combined_rank(ethscription=i)
1063
+ # ETCH 根据订单金额排行
1064
+ etch_top_price_rank = get_etch_top_price_rank(ethscription=i, limit=20)
1065
+ # ETCH 根据 ethsctriptionID 交易次数排行
1066
+ etch_top_id_rank = get_etch_top_ethscriptionid_rank(ethscription=i)
1067
+ # ETCH 列出最近 100 条交易
1068
+ etch_last_100_orders = get_etch_last_100_orders(ethscription=i)
1069
+ # 画图
1070
+ daily_transaction_volume = daily_transaction_volume_and_price_data(ethscription=i)
1071
+ etch_pkl_data_to_save = {'etch_total_volume_and_orders': etch_total_volume_and_orders,
1072
+ 'etch_30d_volume_and_orders': etch_30d_volume_and_orders,
1073
+ 'etch_7d_volume_and_orders': etch_7d_volume_and_orders,
1074
+ 'etch_3d_volume_and_orders': etch_3d_volume_and_orders,
1075
+ 'etch_1d_volume_and_orders': etch_1d_volume_and_orders,
1076
+ 'etch_total_buyers_sellers': etch_total_buyers_sellers,
1077
+ 'etch_1d_buyers_sellers': etch_1d_buyers_sellers,
1078
+ 'etch_total_fee': etch_total_fee,
1079
+ 'etch_combined_rank': etch_combined_rank,
1080
+ 'etch_top_price_rank': etch_top_price_rank,
1081
+ 'etch_top_id_rank': etch_top_id_rank,
1082
+ 'etch_last_100_orders': etch_last_100_orders,
1083
+ 'daily_transaction_volume': daily_transaction_volume
1084
+ }
1085
+ with open(all_pkl_file[index], 'wb') as file:
1086
+ pickle.dump(etch_pkl_data_to_save, file)
1087
+ time.sleep(180)
1088
+ while True:
1089
+ print('ETCH 获取数据错误退出')
1090
+ time.sleep(15)
1091
 
1092
 
1093
+ # 获取所有 ethscriptions 数据,只检查有或者没有(线程入口)
1094
  def get_ethscriptions_data():
 
1095
  # 创建表
1096
  ethscriptions_thread_con = sqlite3.connect(ethscrptions_db_file)
1097
  ethscriptions_thread_cur = ethscriptions_thread_con.cursor()
 
1100
 
1101
  ethscriptions_thread_cur.execute('''CREATE TABLE IF NOT EXISTS process_blocks
1102
  (block_number integer)''')
 
1103
  # 获取最新的区块
1104
  latest_block_number_thread = w3.eth.block_number
1105
  ethscriptions_thread_cur.execute("SELECT MAX(block_number) FROM process_blocks")
 
1115
  transactions = block['transactions']
1116
  for tx in transactions:
1117
  input_data = tx['input']
 
1118
  if input_data.startswith(r"0x646174613a2c"):
 
1119
 
1120
  existing_data = ethscriptions_thread_cur.execute("SELECT * FROM data WHERE data=?",
1121
  (input_data,)).fetchone()
 
1130
  tx['hash'].hex()))
1131
 
1132
  ethscriptions_thread_con.commit()
1133
+ # print(f'Get input data: {input_data}')
1134
 
1135
  # 更新process_blocks表
1136
  ethscriptions_thread_cur.execute("INSERT INTO process_blocks (block_number) VALUES (?)",
 
1139
  ethscriptions_thread_cur.execute("DELETE FROM process_blocks WHERE block_number != ?",
1140
  (block_number,))
1141
  ethscriptions_thread_con.commit()
1142
+ print(f'Process current block: {block_number}')
 
1143
 
1144
  # 获取下次开始的区块号
1145
  ethscriptions_thread_cur.execute("SELECT block_number FROM process_blocks")
1146
  result_thread = ethscriptions_thread_cur.fetchone()
1147
  start_block_thread = result_thread[0] - 1
1148
+ # 等待
1149
+ time.sleep(10)
1150
  # 获取最新的区块
1151
  latest_block_number_thread = w3.eth.block_number
1152
+ while True:
1153
+ print('ETCH 获取已题写错误退出')
1154
+ time.sleep(15)
1155
 
1156
+ # 获取所有单笔购买订单(线程入口)
1157
+ def get_etch_single_orders():
1158
+ # SQLite setup
1159
+ single_orders_conn = sqlite3.connect(etch_single_orders_db_file)
1160
+ single_orders_cursor = single_orders_conn.cursor()
1161
+
1162
+ all_ethscription_con = sqlite3.connect(all_ethscription_db_file)
1163
+ all_ethscription_cur = all_ethscription_con.cursor()
1164
+
1165
+ while True:
1166
+ get_single_all_transactions(single_orders_cursor, single_orders_conn, all_ethscription_cur)
1167
+ time.sleep(15)
1168
+ while True:
1169
+ print('ETCH 单笔错误退出')
1170
  time.sleep(15)
1171
 
1172
 
1173
+ # 获取所有批量购买订单(线程入口)
1174
+ def get_etch_batch_orders():
1175
+ # SQLite setup
1176
+ batch_orders_conn = sqlite3.connect(etch_batch_orders_db_file)
1177
+ batch_orders_cursor = batch_orders_conn.cursor()
1178
+
1179
+ all_ethscription_con = sqlite3.connect(all_ethscription_db_file)
1180
+ all_ethscription_cur = all_ethscription_con.cursor()
1181
 
1182
+ while True:
1183
+ get_batch_all_transactions(batch_orders_cursor, batch_orders_conn, all_ethscription_cur)
1184
+ time.sleep(15)
1185
+ while True:
1186
+ print('ETCH 多笔错误退出')
1187
+ time.sleep(15)
1188
+
1189
+
1190
+ # 获取所有 ethscriptions 数据,只检查有或者没有(线程入口)
1191
+ def get_all_ethscription_by_id():
1192
+ # SQLite setup
1193
+ all_ethscription_conn = sqlite3.connect(all_ethscription_db_file, check_same_thread=False)
1194
+ all_ethscription_cursor = all_ethscription_conn.cursor()
1195
+
1196
+ # Database lock
1197
+ db_lock = threading.Lock()
1198
+
1199
+ while True:
1200
+ batch_conn = sqlite3.connect(etch_batch_orders_db_file)
1201
+ batch_cursor = batch_conn.cursor()
1202
+
1203
+ single_conn = sqlite3.connect(etch_single_orders_db_file)
1204
+ single_cursor = single_conn.cursor()
1205
+
1206
+ # 从 single_orders.db 中获取所有 ethscriptionId
1207
+ single_cursor.execute("SELECT ethscriptionId FROM orders")
1208
+ ethscriptions_single = single_cursor.fetchall()
1209
+
1210
+ # 从 batch_orders.db 中获取所有 ethscriptionId
1211
+ batch_cursor.execute("SELECT ethscriptionId FROM orders")
1212
+ ethscriptions_batch = batch_cursor.fetchall()
1213
+
1214
+ # 对于每一个 ethscriptionId,检查其是否在 all_ethscription.db 中
1215
+ for ethscription in ethscriptions_single:
1216
+ all_ethscription_cursor.execute("SELECT COUNT(*) FROM id_content WHERE ethscriptionId=?",
1217
+ (ethscription[0],))
1218
+ exists = all_ethscription_cursor.fetchone()[0]
1219
+
1220
+ # 如果不存在,则插入
1221
+ if exists == 0:
1222
+ all_ethscription_cursor.execute("INSERT INTO id_content (ethscriptionId, ethscription) VALUES (?, ?)",
1223
+ (ethscription[0], None))
1224
+ all_ethscription_conn.commit()
1225
+ # 对于每一个 ethscriptionId,检查其是否在 all_ethscription.db 中
1226
+ for ethscription in ethscriptions_batch:
1227
+ all_ethscription_cursor.execute("SELECT COUNT(*) FROM id_content WHERE ethscriptionId=?",
1228
+ (ethscription[0],))
1229
+ exists = all_ethscription_cursor.fetchone()[0]
1230
+
1231
+ # 如果不存在,则插入
1232
+ if exists == 0:
1233
+ all_ethscription_cursor.execute(
1234
+ "INSERT INTO id_content (ethscriptionId, ethscription) VALUES (?, ?)",
1235
+ (ethscription[0], None))
1236
+ all_ethscription_conn.commit()
1237
+ batch_conn.close()
1238
+ single_conn.close()
1239
+
1240
+ update_all_ethscription_with_id(all_ethscription_conn, all_ethscription_cursor, db_lock)
1241
+
1242
+ time.sleep(60)
1243
+ while True:
1244
+ print('ETCH get all 错误退出')
1245
+ time.sleep(15)
1246
+
1247
+
1248
+ # Streamlit app 布局设置
1249
+ st.set_page_config(page_title="EthPen - 铭文数据分析", page_icon="💹", layout='centered', initial_sidebar_state='auto')
1250
+ st.write("<style>div.row-widget.stRadio > div{background-color:white;border: 1px solid #e6e9ef;border-radius:8px;padding:10px;box-shadow: 2px 2px 10px #e6e9ef;}</style>", unsafe_allow_html=True)
1251
+ st.markdown(f'# <img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "ethpen_logo.svg"))}" alt="Image" width="64px" height="64px" style="border-radius: 8px; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/> :rainbow[铭文数据分析]', unsafe_allow_html=True)
1252
+ st.subheader(r'', anchor=False, divider='rainbow')
1253
+ st.markdown(f'### $eths 数据盘点')
1254
  eths_conn = sqlite3.connect(eths_db_file)
1255
  eths_cur = eths_conn.cursor()
1256
  # 查询eths_data表中所有数据
 
1276
 
1277
  eths_stakers = st.metric(label='ETHS TVL', value=f'${eths_data[0][8]:,.0f}')
1278
 
 
1279
  st.markdown(f'### 新铭文题写')
1280
  ethscriptions_cur.execute('''
1281
  SELECT block_time, data
 
1297
  result_df = pd.DataFrame(new_100_results)
1298
  st.dataframe(result_df, use_container_width=True, hide_index=True)
1299
 
1300
+ st.markdown(f'### Etch Market 数据洞察')
1301
+ eth_token_price = get_eth_price()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1302
 
1303
+ data_selected_option = st.radio("选择查询的类型:", ['Etch Market', '$eths Token', 'mfpurrs NFT', 'Hyppocritez NFT'], index=0, horizontal=True, label_visibility = "collapsed")
1304
+
1305
+ if data_selected_option == 'Etch Market':
1306
+ ethscription = 0
1307
+ if data_selected_option == '$eths Token':
1308
+ ethscription = 1
1309
+ if data_selected_option == 'mfpurrs NFT':
1310
+ ethscription = 2
1311
+ if data_selected_option == 'Hyppocritez NFT':
1312
+ ethscription = 3
1313
+
1314
+
1315
+ with open(all_pkl_file[ethscription], 'rb') as file:
1316
+ etch_loaded_data = pickle.load(file)
1317
+
1318
+ st.markdown(f'#### {data_selected_option} 数据概览')
1319
+
1320
+ col1, col2 = st.columns(2)
1321
+ with col1:
1322
+ st.metric(label=f'总交易量 {etch_loaded_data["etch_total_volume_and_orders"][0]:.0f} ETH', value=f'${etch_loaded_data["etch_total_volume_and_orders"][0] * eth_token_price:,.0f}')
1323
+ st.metric(label=f'30 日交易量 {etch_loaded_data["etch_30d_volume_and_orders"][0]:.0f} ETH', value=f'${etch_loaded_data["etch_30d_volume_and_orders"][0] * eth_token_price:,.0f}')
1324
+ st.metric(label=f'7 日交易量 {etch_loaded_data["etch_7d_volume_and_orders"][0]:.0f} ETH', value=f'${etch_loaded_data["etch_7d_volume_and_orders"][0] * eth_token_price:,.0f}')
1325
+ st.metric(label=f'3 日交易量 {etch_loaded_data["etch_3d_volume_and_orders"][0]:.0f} ETH', value=f'${etch_loaded_data["etch_3d_volume_and_orders"][0] * eth_token_price:,.0f}')
1326
+ st.metric(label=f'1 日交易量 {etch_loaded_data["etch_1d_volume_and_orders"][0]:.0f} ETH', value=f'${etch_loaded_data["etch_1d_volume_and_orders"][0] * eth_token_price:,.0f}')
1327
+
1328
+ st.metric(label=f'总交易人数 / 独立用户', value=f'{etch_loaded_data["etch_total_buyers_sellers"][2]:,.0f}')
1329
+ st.metric(label=f'1 日交易人数', value=f'{etch_loaded_data["etch_1d_buyers_sellers"][2]:,.0f}')
1330
+ st.metric(label=f'总消耗矿工费', value=f'{etch_loaded_data["etch_total_fee"]:,.2f}ETH')
1331
+ with col2:
1332
+ st.metric(label=f'总订单量', value=f'{etch_loaded_data["etch_total_volume_and_orders"][1]:,.0f}')
1333
+ st.metric(label=f'30 日订单量', value=f'{etch_loaded_data["etch_30d_volume_and_orders"][1]:,.0f}')
1334
+ st.metric(label=f'7 日订单量', value=f'{etch_loaded_data["etch_7d_volume_and_orders"][1]:,.0f}')
1335
+ st.metric(label=f'3 日订单量', value=f'{etch_loaded_data["etch_3d_volume_and_orders"][1]:,.0f}')
1336
+ st.metric(label=f'1 日订单量', value=f'{etch_loaded_data["etch_1d_volume_and_orders"][1]:,.0f}')
1337
+
1338
+ st.metric(label=f'总买家 / 总卖家', value=f'{etch_loaded_data["etch_total_buyers_sellers"][0]:,.0f} / {etch_loaded_data["etch_total_buyers_sellers"][1]:,.0f}')
1339
+ st.metric(label=f'1 日买家 / 卖家', value=f'{etch_loaded_data["etch_1d_buyers_sellers"][0]:,.0f} / {etch_loaded_data["etch_1d_buyers_sellers"][1]:,.0f}')
1340
+
1341
+ st.markdown(f'#### {data_selected_option} 综合排行榜')
1342
+ st.dataframe(etch_loaded_data["etch_combined_rank"], use_container_width=True, hide_index=True)
1343
+
1344
+ st.markdown(f'#### {data_selected_option} 订单金额 TOP 20 排行榜')
1345
+ st.dataframe(etch_loaded_data["etch_top_price_rank"], use_container_width=True, hide_index=True)
1346
+
1347
+ st.markdown(f'#### {data_selected_option} EthscriptionID 换手次数 TOP 100 排行榜')
1348
+ st.dataframe(etch_loaded_data["etch_top_id_rank"], use_container_width=True, hide_index=True)
1349
+
1350
+ st.markdown(f'#### {data_selected_option} 最近 100 条交易')
1351
+ st.dataframe(etch_loaded_data["etch_last_100_orders"], use_container_width=True, hide_index=True)
1352
+ if ethscription != 0:
1353
+ # daily_transaction_volume_and_price(etch_loaded_data["daily_transaction_volume"])
1354
+ # 显示蜡烛图
1355
+ st.markdown(f'#### {data_selected_option} 价格走势图')
1356
+ st.line_chart(etch_loaded_data["daily_transaction_volume"][['开', '高', '低', '收']])
1357
+ # 显示成交量图
1358
+ st.markdown(f'#### {data_selected_option} 每日成交量图')
1359
+ st.bar_chart(etch_loaded_data["daily_transaction_volume"]['交易量'])
1360
+
1361
+ # 显示每日成交额图
1362
+ st.markdown(f'#### {data_selected_option} 每日成交额图')
1363
+ st.bar_chart(etch_loaded_data["daily_transaction_volume"]['交易额'])
1364
+
1365
+ # 线程入口
1366
  config = configparser.ConfigParser()
1367
  config.read(config_ini_file)
1368
+ thread_start_state_value = config['data_thread_start_state']['state']
1369
  if thread_start_state_value == '0':
1370
+ print('数据分析线程未启动。')
1371
+ config['data_thread_start_state']['state'] = '1'
1372
  with open(config_ini_file, 'w') as configfile:
1373
  config.write(configfile)
1374
 
1375
  eths_thread = threading.Thread(target=get_eths_data)
1376
  ethscriptions_thread = threading.Thread(target=get_ethscriptions_data)
1377
+ etch_single_orders_thread = threading.Thread(target=get_etch_single_orders)
1378
+ etch_batch_orders_thread = threading.Thread(target=get_etch_batch_orders)
1379
+ all_ethscription_by_id_thread = threading.Thread(target=get_all_ethscription_by_id)
1380
 
1381
  # 启动线程
1382
  eths_thread.start()
1383
  ethscriptions_thread.start()
1384
+ etch_single_orders_thread.start()
1385
+ etch_batch_orders_thread.start()
1386
+ all_ethscription_by_id_thread.start()
1387
 
1388
  # 等待线程结束
1389
  eths_thread.join()
1390
  ethscriptions_thread.join()
1391
+ etch_single_orders_thread.join()
1392
+ etch_batch_orders_thread.join()
1393
+ all_ethscription_by_id_thread.join()
1394
+
1395
  else:
1396
+ print('数据分析线程已经启动。')
pages/5_🏫_教程中心.py CHANGED
@@ -1,14 +1,22 @@
 
1
  import streamlit as st
2
  import re
3
  import os
4
  import sqlite3
5
  import pandas as pd
6
  import time
7
- import pandas as pd
 
 
 
 
 
 
8
 
9
  # Streamlit app layout
10
  st.set_page_config(page_title="EthPen - 教程中心", page_icon="🏫", layout='centered', initial_sidebar_state='auto')
11
- st.subheader(r'🏫 :rainbow[EthPen - 教程中心]', anchor=False, divider='rainbow')
 
12
  st.error('开发中,仅供参考...')
13
- st.error('如果你有更好的想法,可以联系我。')
14
 
 
1
+ import base64
2
  import streamlit as st
3
  import re
4
  import os
5
  import sqlite3
6
  import pandas as pd
7
  import time
8
+
9
+
10
+ # 图片Base64
11
+ def image_to_base64(img_path):
12
+ with open(img_path, "rb") as image_file:
13
+ return base64.b64encode(image_file.read()).decode()
14
+
15
 
16
  # Streamlit app layout
17
  st.set_page_config(page_title="EthPen - 教程中心", page_icon="🏫", layout='centered', initial_sidebar_state='auto')
18
+ st.markdown(f'# <img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "ethpen_logo.svg"))}" alt="Image" width="64px" height="64px" style="border-radius: 8px; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/> :rainbow[教程中心]', unsafe_allow_html=True)
19
+ st.subheader(r'', anchor=False, divider='rainbow')
20
  st.error('开发中,仅供参考...')
21
+ st.error('若您有更好的建议或想法,请随时与我取得联系。')
22
 
pages/6_📢_推送通知服务.py CHANGED
@@ -1,14 +1,326 @@
1
  import streamlit as st
2
- import re
3
  import os
 
4
  import sqlite3
5
- import pandas as pd
6
  import time
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
 
9
  # Streamlit app layout
10
  st.set_page_config(page_title="EthPen - 推送通知服务", page_icon="📢", layout='centered', initial_sidebar_state='auto')
11
- st.subheader(r'📢 :rainbow[EthPen - 推送通知服务]', anchor=False, divider='rainbow')
 
12
  st.error('开发中,仅供参考...')
13
- st.error('如果你有更好的想法,可以联系我。')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
 
 
 
 
1
  import streamlit as st
2
+ import requests
3
  import os
4
+ from web3 import Web3
5
  import sqlite3
 
6
  import time
7
+ import threading
8
+ import base64
9
+ import configparser
10
+ import pandas as pd
11
+ import json
12
+ from concurrent.futures import ThreadPoolExecutor
13
+ from datetime import datetime
14
+
15
+
16
+ my_style = '''
17
+ <style>
18
+ .tag {
19
+ display: inline-block;
20
+ padding: 2px 6px;
21
+ background-color: #f2f2f2; /* 默认的背景颜色 */
22
+ border-radius: 5px; /* 圆角效果 */
23
+ margin: 0 2px;
24
+ transition: background-color 0.3s; /* 平滑的颜色过渡效果 */
25
+ }
26
+
27
+ .tag:hover {
28
+ background-color: #cffd51; /* 鼠标经过时的背景颜色 */
29
+ }
30
+
31
+ .tag:active {
32
+ background-color: #cffd51; /* 鼠标按下时的背景颜色 */
33
+ }
34
+ </style>
35
+ '''
36
+
37
+ # 获取 pushover api
38
+ pushover_api_token = os.environ.get('pushover_api_token', '')
39
+ pushover_user_token = os.environ.get('pushover_user_token', '')
40
+
41
+ # 获取当前文件目录
42
+ current_dir = os.path.dirname(__file__)
43
+ parent_dir = os.path.dirname(current_dir)
44
+ config_ini_file = os.path.join(parent_dir, 'data', 'config.ini') # 程序配置文件
45
+ push_messages_db_file = os.path.join(parent_dir, 'data', 'push_messages.db')
46
+ all_ethscription_db_file = os.path.join(parent_dir, 'data', 'all_ethscription.db')
47
+
48
+ # 设置数据库
49
+ push_messages_conn = sqlite3.connect(push_messages_db_file)
50
+ push_messages_cur = push_messages_conn.cursor()
51
+
52
+ # 使用你的Ethereum节点的RPC地址
53
+ infura_api_key_eths = os.environ.get('infura_api_key_eths', '')
54
+ w3 = Web3(Web3.HTTPProvider(infura_api_key_eths))
55
+
56
+ # 设置 telegram api
57
+ telegram_bot_token = os.environ.get('telegram_bot_token', '')
58
+ telegram_channel_id = "@ethspush"
59
+ telegram_base_url = f"https://api.telegram.org/bot{telegram_bot_token}"
60
+
61
+ thread_id = 0
62
+ executor = ThreadPoolExecutor(max_workers=10)
63
+
64
+ # 配置合约 ABI 文件
65
+ with open(os.path.join(parent_dir, 'data', 'batch_contract_abi.json'), "r") as batch:
66
+ contract_abi = json.load(batch)
67
+ batch_contract = w3.eth.contract(abi=contract_abi)
68
+
69
+ with open(os.path.join(parent_dir, 'data', 'single_contract_abi.json'), "r") as single:
70
+ contract_abi = json.load(single)
71
+ single_contract = w3.eth.contract(abi=contract_abi)
72
+
73
+ with open(os.path.join(parent_dir, 'data', 'single_contract_old_abi.json'), "r") as single_old:
74
+ contract_abi = json.load(single_old)
75
+ single_old_contract = w3.eth.contract(abi=contract_abi)
76
+
77
+
78
+ # 图片 Base64
79
+ def image_to_base64(img_path):
80
+ with open(img_path, "rb") as image_file:
81
+ return base64.b64encode(image_file.read()).decode()
82
+
83
+
84
+ # 通过 ID 从 all_ethscription 获取铭文内容
85
+ def get_ethscription_by_id(ethscription_id, all_ethscription_cur):
86
+ # 执行查询语句
87
+ all_ethscription_cur.execute("SELECT name FROM id_content WHERE ethscriptionId=?", (ethscription_id,))
88
+ result = all_ethscription_cur.fetchone()
89
+
90
+ # 返回结果
91
+ if result:
92
+ return result[0]
93
+ else:
94
+ return None
95
+
96
+
97
+ # 获取单个 ethscription 数据
98
+ def get_specific_ethscription(ethscription_identifier):
99
+ endpoint = f"/ethscriptions/{ethscription_identifier}"
100
+
101
+ try:
102
+ response = requests.get("https://mainnet-api.ethscriptions.com/api" + endpoint, timeout=3)
103
+ response.raise_for_status() # Will raise an HTTPError if the HTTP request returned an unsuccessful status code
104
+
105
+ if response.status_code == 200:
106
+ # Assuming the Content-Type is JSON
107
+ return response.json()
108
+ else:
109
+ print(f'Unexpected status code {response.status_code} for identifier: {ethscription_identifier}')
110
+ return None
111
+
112
+ except requests.Timeout:
113
+ print(f"Request timed out for identifier: {ethscription_identifier}")
114
+ except requests.ConnectionError:
115
+ print(f"Connection error for identifier: {ethscription_identifier}")
116
+ except requests.TooManyRedirects:
117
+ print(f"Too many redirects for identifier: {ethscription_identifier}")
118
+ except requests.HTTPError as http_err:
119
+ print(f"HTTP error for identifier {ethscription_identifier}: {http_err}")
120
+ except requests.RequestException as req_err:
121
+ print(f"General error for identifier {ethscription_identifier}: {req_err}")
122
+ except Exception as e:
123
+ # Catch-all for any other unforeseen exceptions
124
+ print(f"An unexpected error occurred for identifier {ethscription_identifier}: {e}")
125
+
126
+ return None # Ensure the function always returns a value even in the case of an error
127
+
128
+
129
+ def send_message_to_channel(text):
130
+ url = f"{telegram_base_url}/sendMessage"
131
+ payload = {
132
+ "chat_id": telegram_channel_id,
133
+ "text": text
134
+ }
135
+
136
+ response = requests.post(url, data=payload)
137
+
138
+ if response.status_code == 200:
139
+ return response.json()
140
+ else:
141
+ return None
142
+
143
+
144
+ def send_pushover_notification(token, user_key, message, title):
145
+
146
+ url = "https://api.pushover.net/1/messages.json"
147
+
148
+ data = {
149
+ "token": token,
150
+ "user": user_key,
151
+ "message": message,
152
+ "title": title
153
+ }
154
+
155
+ response = requests.post(url, data=data)
156
+
157
+ if response.status_code == 200:
158
+ return response.json()
159
+ else:
160
+ return None
161
+
162
+
163
+ def send_message_to_channel_wrapper(text):
164
+ return send_message_to_channel(text)
165
+
166
+
167
+ def send_pushover_notification_wrapper(token, user_key, message, title):
168
+ return send_pushover_notification(token, user_key, message, title)
169
+
170
+
171
+ def get_push_data():
172
+ push_messages_w_conn = sqlite3.connect(push_messages_db_file)
173
+ push_messages_w_cur = push_messages_w_conn.cursor()
174
+ all_ethscription_conn = sqlite3.connect(all_ethscription_db_file)
175
+ all_ethscription_cur = all_ethscription_conn.cursor()
176
+
177
+ with ThreadPoolExecutor() as executor:
178
+
179
+ while True:
180
+ block_number = w3.eth.block_number
181
+ # block_number = 18205797
182
+ block = w3.eth.get_block(block_number, full_transactions=True)
183
+ transactions = block['transactions']
184
+ for tx in transactions:
185
+ query = "SELECT EXISTS(SELECT 1 FROM messages WHERE hash=?)"
186
+ push_messages_w_cur.execute(query, (str(tx['hash'].hex()),))
187
+ exists = push_messages_w_cur.fetchone()[0]
188
+ if not exists:
189
+ if tx['to'] == '0x57b8792c775D34Aa96092400983c3e112fCbC296':
190
+ if tx['value'] >= 1:
191
+ if tx['input'][:10] == '0xd2234424' or tx['input'][:10] == '0xd92a1740':
192
+ try:
193
+ if tx['input'][:10] == '0xd2234424':
194
+ data = single_old_contract.decode_function_input(tx['input'])
195
+ if tx['input'][:10] == '0xd92a1740':
196
+ data = single_contract.decode_function_input(tx['input'])
197
+
198
+ params = data[1]
199
+ # Handle bytes data
200
+ for key, value in params.items():
201
+ if isinstance(value, bytes):
202
+ params[key] = value.hex()
203
+ # 获取并处理订单数据
204
+ order = params.get('order', {})
205
+ if order: # 确保order不为空
206
+ for key in ['ethscriptionId', 'r', 's', 'params']:
207
+ if key in order: # 确保键在order中
208
+ order[key] = order[key].hex()
209
+
210
+ ethscription = get_ethscription_by_id(f'0x{order["ethscriptionId"]}', all_ethscription_cur)
211
+ if ethscription:
212
+ ethscription = get_specific_ethscription()
213
+ if ethscription['collections']:
214
+ name = ethscription['collections'][0]['name']
215
+ else:
216
+ name = ethscription['content_uri']
217
+ if len(name) > 128:
218
+ name = ethscription['ethscription_number']
219
+ msg_title = f"ETCH new order: {float(order['price']) / 1e18} ETH"
220
+ msg_text = f"""
221
+ Etch 新的单笔大额订单
222
+ 时间:{datetime.fromtimestamp(block['timestamp'])}
223
+ 铭文:{name}
224
+ 数量:{order['quantity']}
225
+ 单价:{float(order['price']) / 1e18 / order['quantity']}
226
+ 金额:{float(order['price']) / 1e18}
227
+ 买家:{tx['from']}
228
+ 卖家:{order['signer']}
229
+ 链接:{'https://etherscan.io/tx/' + tx['hash'].hex()}
230
+ """
231
+ # response = send_message_to_channel(msg_text)
232
+ telegram_future = executor.submit(send_message_to_channel_wrapper, msg_text)
233
+ pushover_future = executor.submit(send_pushover_notification_wrapper, pushover_api_token, pushover_user_token, msg_text, msg_title)
234
+
235
+ telegram_response = telegram_future.result()
236
+ pushover_response = pushover_future.result()
237
+
238
+ push_messages_w_cur.execute('''
239
+ INSERT INTO messages (time, title, hash) VALUES (?, ?, ?)
240
+ ''', (str(datetime.fromtimestamp(block['timestamp'])), str(msg_title), str(tx['hash'].hex())))
241
+ push_messages_w_conn.commit()
242
+ except Exception as e:
243
+ print(f"Error processing transaction {tx['hash'].hex()}: {e}")
244
+
245
+ if tx['to'] == '0x941Bc2E04A776d436E183Fe4204Bb84FeBA564D3':
246
+ if tx['value'] >= 1:
247
+ if tx['input'][:10] == '0x3bb23351':
248
+ try:
249
+ data = batch_contract.decode_function_input(tx['input'])
250
+ params = data[1]
251
+ # Handle bytes data
252
+ for key, value in params.items():
253
+ if isinstance(value, bytes):
254
+ params[key] = value.hex()
255
+ # 获取并处理订单数据
256
+ orders_data = params.get('orders', [])
257
+ for order in orders_data:
258
+ for key in ['ethscriptionId', 'r', 's', 'params']:
259
+ order[key] = order[key].hex()
260
+
261
+ msg_title = f"ETCH new order: {float(tx['value']) / 1e18} ETH"
262
+ msg_text = f"""
263
+ Etch 新的批量大额订单
264
+ 时间:{datetime.fromtimestamp(block['timestamp'])}
265
+ 金额:{float(tx['value']) / 1e18}
266
+ 买家:{tx['from']}
267
+ 链接:{'https://etherscan.io/tx/' + tx['hash'].hex()}
268
+ """
269
+ # response = send_message_to_channel(msg_text)
270
+ telegram_future = executor.submit(send_message_to_channel_wrapper, msg_text)
271
+ pushover_future = executor.submit(send_pushover_notification_wrapper, pushover_api_token, pushover_user_token, msg_text, msg_title)
272
+
273
+ telegram_response = telegram_future.result()
274
+ pushover_response = pushover_future.result()
275
+
276
+ push_messages_w_cur.execute('''
277
+ INSERT INTO messages (time, title, hash) VALUES (?, ?, ?)
278
+ ''', (
279
+ str(datetime.fromtimestamp(block['timestamp'])), str(msg_title), str(tx['hash'].hex())))
280
+ push_messages_w_conn.commit()
281
+ except Exception as e:
282
+ print(f"Error processing transaction {tx['hash'].hex()}: {e}")
283
+
284
+ time.sleep(10)
285
 
286
 
287
  # Streamlit app layout
288
  st.set_page_config(page_title="EthPen - 推送通知服务", page_icon="📢", layout='centered', initial_sidebar_state='auto')
289
+ st.markdown(f'# <img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "ethpen_logo.svg"))}" alt="Image" width="64px" height="64px" style="border-radius: 8px; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/> :rainbow[推送通知服务]', unsafe_allow_html=True)
290
+ st.subheader(r'', anchor=False, divider='rainbow')
291
  st.error('开发中,仅供参考...')
292
+ st.error('若您有更好的建议或想法,请随时与我取得联系。')
293
+ st.markdown('### 订阅服务')
294
+ push_service_option = st.radio("选择通知的服务:", ['Telegram', 'Pushover', 'Bark', 'Server 酱', 'PushDeer', '钉钉', '企业微信', '飞书', 'Discord', '短信', '电话'], index=0,
295
+ horizontal=True, disabled=True)
296
+ col1, col2 = st.columns(2)
297
+ # 在第一个列中添加文本输入框
298
+ text_input = col1.text_input("API Keys:", label_visibility='collapsed', disabled=True)
299
+ # 在第二个列中添加按钮
300
+ button_clicked = col2.button("订阅", disabled=True)
301
+ st.markdown(f'''{my_style}<span class="tag"><a href="https://pushover.net/subscribe/EthPencom-je7oe47in2fchv8" target="_blank" style="text-decoration: none;"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "pushover_logo.jpg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> Pushover - EthPen.com</span></a>
302
+ <span class="tag"><a href="https://t.me/ethspush" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "telegram.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> @ethspush</span></a>
303
+ ''', unsafe_allow_html=True)
304
+ st.markdown('考虑到通知的时效性及操作的便捷性,暂不支持个人定制服务,且推送的触发条件为合约交易金额大于等于 1ETH。目前我们只开放了 Telegram 频道和 Pushover 作为通知方式。欢迎各位订阅并体验推送服务。')
305
+
306
+ st.markdown('### 最近消息')
307
+ with sqlite3.connect(push_messages_db_file) as conn:
308
+ recent_messages = pd.read_sql_query("SELECT time, title, hash FROM messages ORDER BY time DESC LIMIT 10;", conn)
309
+ st.dataframe(recent_messages, use_container_width=True, hide_index=True)
310
+
311
+ config = configparser.ConfigParser()
312
+ config.read(config_ini_file)
313
+ thread_start_state_value = config['push_thread_start_state']['state']
314
+ if thread_start_state_value == '0':
315
+ print('消息推送线程未启动。')
316
+ config['push_thread_start_state']['state'] = '1'
317
+ with open(config_ini_file, 'w') as configfile:
318
+ config.write(configfile)
319
+
320
+ # 创建线程对象
321
+ push_thread = threading.Thread(target=get_push_data)
322
+ push_thread.start()
323
+ push_thread.join()
324
 
325
+ else:
326
+ print('消息推送线程已经启动。')
pages/7_ℹ️️_EthPen.com 简介.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import streamlit as st
2
+ import base64
3
+ import os
4
+ import shutil
5
+
6
+ my_style = '''
7
+ <style>
8
+ .tag {
9
+ display: inline-block;
10
+ padding: 2px 6px;
11
+ background-color: #f2f2f2; /* 默认的背景颜色 */
12
+ border-radius: 5px; /* 圆角效果 */
13
+ margin: 0 2px;
14
+ transition: background-color 0.3s; /* 平滑的颜色过渡效果 */
15
+ }
16
+
17
+ .tag:hover {
18
+ background-color: #cffd51; /* 鼠标经过时的背景颜色 */
19
+ }
20
+
21
+ .tag:active {
22
+ background-color: #cffd51; /* 鼠标按下时的背景颜色 */
23
+ }
24
+ </style>
25
+ '''
26
+
27
+
28
+ # 图片Base64
29
+ def image_to_base64(img_path):
30
+ with open(img_path, "rb") as image_file:
31
+ return base64.b64encode(image_file.read()).decode()
32
+
33
+
34
+ # 压缩文件
35
+ def zip_directory(directory_path, output_filename):
36
+ # 创建一个zip文件
37
+ shutil.make_archive(output_filename, 'zip', directory_path)
38
+ return output_filename + ".zip"
39
+
40
+
41
+ # Streamlit app layout
42
+ st.set_page_config(page_title="EthPen - EthPen.com 简介", page_icon="ℹ️", layout='centered',
43
+ initial_sidebar_state='auto')
44
+ st.markdown(f'# <img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "ethpen_logo.svg"))}" alt="Image" width="64px" height="64px" style="border-radius: 8px; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/> :rainbow[EthPen.com 简介]', unsafe_allow_html=True)
45
+ st.subheader(r'', anchor=False, divider='rainbow')
46
+
47
+ st.markdown(
48
+ f'欢迎踏足 EthPen - 以太之笔!这里汇聚了一系列关于 Ethscriptions 的精细工具集,无论是单一查询、铭文题写,还是批量检索、编码题写,乃至深入的教程导引、数据分析等,以太之笔都将助您铭文题写如飞。我们立志推广 Ethscriptions 的宏大理念,期望 $eths 翱翔于星空,与月相伴!若您携手建议或创意,我们热切期待您的声音。',
49
+ unsafe_allow_html=True)
50
+ st.markdown(r'## :rainbow[EthPen 题写铭文的好帮手!]')
51
+ st.markdown('')
52
+ st.markdown(
53
+ '顺便提一句,我是 pztuya。这个网站的所有内容都是我一手打造的。由于能力和时间所限,内容更新的速度可能较慢,甚至程序出错导致你的资金出现问题,我也是需要用户反馈后才去修改,希望您能理解。谢谢您的支持与耐心 😊~')
54
+
55
+ st.markdown(
56
+ f'''{my_style}<span class="tag"><a href="https://twitter.com/pztuya" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "x.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> @pztuya</span></a>
57
+ <span class="tag"><a href="https://t.me/NervosCKB" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "telegram.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> @NervosCKB</span></a>
58
+ ''', unsafe_allow_html=True)
59
+
60
+ st.markdown(
61
+ f'''# <span class="tag"><a href="https://huggingface.co/spaces/Ethscriptions/eths" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "huggingface_logo.svg"))}" alt="Image" width="60px" height="60px" style="border-radius: 9px;" /> @Ethscriptions </span></a>
62
+ ''', unsafe_allow_html=True)
63
+
64
+ st.markdown('### 更新记录')
65
+ st.markdown('- 2023 年 9 月 28 日以前,项目上线,多种优化。')
66
+ st.markdown('- 增加推送通知服务功能')
67
+ st.markdown('- 增加更多的数据分析功能')
68
+ st.markdown('### 更新计划')
69
+ st.markdown('1. 添加链接 Metamask 钱包功能')
70
+ st.markdown('2. 添加 Metamask 钱包题写铭文功能')
71
+ st.markdown('3. 增加教程中心的多种教程')
72
+ st.markdown('4. 页面优化、代码优化')
73
+
74
+
75
+ # 下载所有文件
76
+ st.markdown('### 下载数据库和配置文件')
77
+ database_download_password = os.environ.get('database_download_password', 'get_error')
78
+ col1, col2 = st.columns(2)
79
+ database_dl_pw_text_input = col1.text_input("输入密码:", label_visibility='collapsed')
80
+ database_dl_pw_button = col2.button("提交")
81
+ if database_dl_pw_button:
82
+ if database_dl_pw_text_input == database_download_password:
83
+ current_dir = os.path.dirname(__file__)
84
+ parent_dir = os.path.dirname(current_dir)
85
+ ethpen_data_file = os.path.join(parent_dir, 'data')
86
+ zipped_file = zip_directory(ethpen_data_file, "zipped_directory")
87
+ with open(zipped_file, "rb") as file:
88
+ bytes_data = file.read()
89
+ st.download_button(
90
+ label="下载文件",
91
+ data=bytes_data,
92
+ file_name="EthPen_data.zip",
93
+ mime="application/zip"
94
+ )
95
+ os.remove(zipped_file) # 删除临时创建的zip文件
96
+ else:
97
+ st.markdown('暂时未开放下载,密码输入错误,请重试。')
pages/7_ℹ️️_关于 EthPen.com.py DELETED
@@ -1,50 +0,0 @@
1
- import streamlit as st
2
- import base64
3
- import os
4
-
5
-
6
- my_style = '''
7
- <style>
8
- .tag {
9
- display: inline-block;
10
- padding: 2px 6px;
11
- background-color: #f2f2f2; /* 默认的背景颜色 */
12
- border-radius: 5px; /* 圆角效果 */
13
- margin: 0 2px;
14
- transition: background-color 0.3s; /* 平滑的颜色过渡效果 */
15
- }
16
-
17
- .tag:hover {
18
- background-color: #cffd51; /* 鼠标经过时的背景颜色 */
19
- }
20
-
21
- .tag:active {
22
- background-color: #cffd51; /* 鼠标按下时的背景颜色 */
23
- }
24
- </style>
25
- '''
26
-
27
-
28
- # 图片Base64
29
- def image_to_base64(img_path):
30
- with open(img_path, "rb") as image_file:
31
- return base64.b64encode(image_file.read()).decode()
32
-
33
-
34
- # Streamlit app layout
35
- st.set_page_config(page_title="EthPen - 关于 EthPen.com", page_icon="ℹ️", layout='centered', initial_sidebar_state='auto')
36
- st.subheader(r'ℹ️ :rainbow[EthPen - 关于 EthPen.com]', anchor=False, divider='rainbow')
37
-
38
- st.markdown(
39
- f'欢迎踏足 EthPen - 以太之笔!这里汇聚了一系列关于 Ethscriptions 的精细工具集,无论是单一查询、铭文题写,还是批量检索、编码题写,乃至深入的教程导引,以太之笔都将助您铭文题写如飞。我们立志推广 Ethscriptions 的宏大理念,期望 $eths 翱翔于星空,与月相伴!若您携手建议或创意,我们热切期待您的声音。',
40
- unsafe_allow_html=True)
41
- st.markdown(r'## :rainbow[EthPen 题写铭文的好帮手!]')
42
- st.markdown('')
43
- st.markdown('顺便提一句,我是 pztuya。这个网站的所有内容都是我一手打造的。由于能力和时间所限,内容更新的速度可能较慢,希望您能理解。谢谢您的支持与耐心 😊~')
44
-
45
- st.markdown(f'''{my_style}<span class="tag"><a href="https://twitter.com/pztuya" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "x.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> @pztuya</span></a>
46
- <span class="tag"><a href="https://t.me/NervosCKB" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "telegram.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> @NervosCKB</span></a>
47
- ''', unsafe_allow_html=True)
48
-
49
- st.markdown(f'''# <span class="tag"><a href="https://huggingface.co/spaces/Ethscriptions/eths" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "huggingface_logo.svg"))}" alt="Image" width="60px" height="60px" style="border-radius: 9px;" /> @Ethscriptions </span></a>
50
- ''', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
requirements.txt CHANGED
@@ -1,111 +1,6 @@
1
- aiofiles==23.1.0
2
- aiohttp==3.8.5
3
- aiosignal==1.3.1
4
- altair==5.0.1
5
- annotated-types==0.5.0
6
- anyio==3.7.1
7
- async-timeout==4.0.2
8
- attrs==23.1.0
9
- bitarray==2.8.0
10
- blinker==1.6.2
11
- cachetools==5.3.1
12
- certifi==2023.5.7
13
- charset-normalizer==3.1.0
14
- click==8.1.6
15
- contourpy==1.1.0
16
- cycler==0.11.0
17
- cytoolz==0.12.2
18
- decorator==5.1.1
19
- docopt==0.6.2
20
- eth-abi==4.1.0
21
- eth-account==0.9.0
22
- eth-hash==0.5.2
23
- eth-keyfile==0.6.1
24
- eth-keys==0.4.0
25
- eth-rlp==0.3.0
26
- eth-typing==3.4.0
27
- eth-utils==2.2.0
28
- exceptiongroup==1.1.2
29
- fastapi==0.100.1
30
- ffmpy==0.3.1
31
- filelock==3.12.2
32
- fonttools==4.42.0
33
- frozenlist==1.4.0
34
- fsspec==2023.6.0
35
- gitdb==4.0.10
36
- GitPython==3.1.32
37
- gradio==3.39.0
38
- gradio_client==0.3.0
39
- h11==0.14.0
40
- hexbytes==0.3.1
41
- httpcore==0.17.3
42
- httpx==0.24.1
43
- huggingface-hub==0.16.4
44
- idna==3.4
45
- importlib-metadata==6.8.0
46
- importlib-resources==6.0.0
47
- Jinja2==3.1.2
48
- jsonschema==4.18.4
49
- jsonschema-specifications==2023.7.1
50
- kiwisolver==1.4.4
51
- linkify-it-py==2.0.2
52
- lru-dict==1.2.0
53
- markdown-it-py==2.2.0
54
- MarkupSafe==2.1.3
55
- matplotlib==3.7.2
56
- mdit-py-plugins==0.3.3
57
- mdurl==0.1.2
58
- multidict==6.0.4
59
- numpy==1.25.1
60
- orjson==3.9.2
61
- packaging==23.1
62
  pandas==2.0.3
63
- parsimonious==0.9.0
64
- Pillow==9.5.0
65
- pipreqs==0.4.13
66
- protobuf==4.23.4
67
- pyarrow==12.0.1
68
- pycryptodome==3.18.0
69
- pydantic==2.1.1
70
- pydantic_core==2.4.0
71
- pydeck==0.8.0
72
- pydub==0.25.1
73
- Pygments==2.15.1
74
- Pympler==1.0.1
75
- pyparsing==3.0.9
76
- python-dateutil==2.8.2
77
- python-multipart==0.0.6
78
  pytz==2023.3
79
- pytz-deprecation-shim==0.1.0.post0
80
- pyunormalize==15.0.0
81
- PyYAML==6.0.1
82
- referencing==0.30.0
83
- regex==2023.6.3
84
- requests==2.31.0
85
- rich==13.4.2
86
- rlp==3.0.0
87
- rpds-py==0.9.2
88
- schedule==1.2.0
89
- semantic-version==2.10.0
90
- six==1.16.0
91
- smmap==5.0.0
92
- sniffio==1.3.0
93
- starlette==0.27.0
94
  streamlit==1.26.0
95
- tenacity==8.2.2
96
- toml==0.10.2
97
- toolz==0.12.0
98
- tornado==6.3.2
99
- tqdm==4.65.0
100
- typing_extensions==4.7.1
101
- tzdata==2023.3
102
- tzlocal==4.3.1
103
- uc-micro-py==1.0.2
104
- urllib3==2.0.3
105
- uvicorn==0.23.2
106
- validators==0.20.0
107
  web3==6.6.1
108
- websockets==11.0.3
109
- yarg==0.1.9
110
- yarl==1.9.2
111
- zipp==3.16.2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  pandas==2.0.3
2
+ pandas==2.0.2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  pytz==2023.3
4
+ Requests==2.31.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  streamlit==1.26.0
 
 
 
 
 
 
 
 
 
 
 
 
6
  web3==6.6.1
 
 
 
 
🖊️EthPen-以太之笔.py CHANGED
@@ -8,8 +8,8 @@ import base64
8
  import sqlite3
9
  import os
10
 
11
- infura_api_key = "9bbc614b8a1d49d59869e97d0ee3bf61"
12
 
 
13
  # Ethscriptions Mainnet API 的基础 URL
14
  BASE_URL = "https://mainnet-api.ethscriptions.com/api"
15
 
@@ -95,7 +95,7 @@ def sha256(input_string):
95
 
96
  # 根据 TX 获取 input data
97
  def get_input_data(tx_hash):
98
- endpoint = f"https://mainnet.infura.io/v3/{infura_api_key}"
99
  headers = {
100
  "Content-Type": "application/json",
101
  }
@@ -221,16 +221,16 @@ def get_eths_staking():
221
  return {} # 返回一个空字典作为默认值
222
 
223
 
224
- st.set_page_config(page_title="EthPen - 以太之笔", page_icon="🖊", layout='centered', initial_sidebar_state='collapsed')
225
 
226
  # 首页
227
  st.markdown(
228
- f'# <a href="https://ethpen.com" target="_self"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "ethpen_logo.svg"))}" alt="Image" width="64px" height="64px" style="border-radius: 8px;"/></a> :rainbow[EthPen - 以太之笔]',
229
  unsafe_allow_html=True)
230
  st.subheader('', anchor=False, divider='rainbow')
231
 
232
  # 最近新闻
233
- st.markdown(f'#### 最近新闻')
234
 
235
  st.markdown(
236
  f'<a href="https://twitter.com/dumbnamenumbers/status/1696989307871826137" target="_blank"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "news.jpeg"))}" alt="Image" style="max-width: 100%; width: 100%; height: auto; border-radius: 10px; display: block; box-shadow: 5px 5px 15px rgba(0,0,0,0.3);"/></a>',
@@ -240,26 +240,32 @@ st.markdown(
240
  st.markdown(f'> 3 周前,我们提出了 Ethscriptions 虚拟机的构想——一种通过将其解释为计算机指令来显著增强 Ethscriptions 功能的方法。今天,我们宣布了该虚拟机的首个实现。已在 Goerli 网络上线,并已在 GitHub 上完全开源!👆')
241
 
242
  # 广告位图片
243
- st.markdown(f'#### 广告位')
244
 
245
 
 
 
 
 
 
 
 
246
  st.markdown(
247
- f'<a href="https://twitter.com/EtchMarket/status/1694024108672245953" target="_blank"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "ad.jpg"))}" alt="Image" style="max-width: 100%; width: 100%; height: auto; border-radius: 10px; display: block; box-shadow: 5px 5px 15px rgba(0,0,0,0.3);"/></a>',
248
  unsafe_allow_html=True)
 
 
249
 
250
- st.markdown(f'> 拆分方案现在面向所有人推出!让我们一起加入权益挖矿的浪潮,并分享50%的月度服务费。')
251
-
252
-
253
- st.markdown(f'#### 什么是 Ethscriptions?', unsafe_allow_html=True)
254
  st.markdown(f'{my_style}<span class="tag"><a href="https://ethscriptions.com/" target="_blank" style="text-decoration: none;"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "ethscriptions_logo_litto.png"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;"/> Ethscriptions</span></a> 是一种新的在以太坊上创建和分享数字资产的方法,它通过使用交易 calldata 存储数据而不是智能合约来实现,这使其比 NFT 更为经济。它们是完全在链上、无需许可、抗审查的,并且其成本只是 NFT 的一小部分。', unsafe_allow_html=True)
255
 
256
  st.markdown(f'<img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "input_data.png"))}" alt="Image" style="max-width: 100%; width: 100%; height: auto; border-radius: 10px; display: block; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/>', unsafe_allow_html=True)
257
  st.markdown('')
258
- st.markdown('#### 谁创造了 Ethscriptions?')
259
  st.markdown(f'首个 [Ethscription](https://ethscriptions.com/ethscriptions/0) 是在 2016 年创建的,但正式的协议是由 Tom Lehman,又名 <span class="tag"><a href="https://twitter.com/dumbnamenumbers" target="_blank" style="text-decoration: none;"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "Middlemarch.jpg"))}" alt="Image" width="20px" height="20px" style="border-radius: 10px;"/> @Middlemarch</span></a> 开发的。除了比特币的铭文,他还受到了来自 Poly Network 黑客的著名的 “原型 - Ethscription” 的启发,你可以在[这笔交易](https://etherscan.io/tx/0x0ae3d3ce3630b5162484db5f3bdfacdfba33724ffb195ea92a6056beaa169490)中看到它。', unsafe_allow_html=True)
260
  st.markdown(f'- 快来加入 <span class="tag"><a href="https://discord.gg/ethscriptions" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "discord.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> @ethscriptions</span></a>,一起讨论 Ethscriptions 的未来!', unsafe_allow_html=True)
261
  st.markdown(f'- 快来关注 <span class="tag"><a href="https://twitter.com/ethscriptions" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "x.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> @ethscriptions</span></a>,掌握 Ethscriptions 的最新动态!', unsafe_allow_html=True)
262
- st.markdown('#### 如何题写 Ethscriptions?')
263
  st.markdown(f'题写 Ethscriptions 是十分简单的,相当于在发送 ETH 交易时附带一些转账备注,我们就以使用人数最多的 MetaMask 钱包来举例,我们首先打开<span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "0x.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 显示十六进制数据</span>。', unsafe_allow_html=True)
264
  st.markdown(f'1. 打开 <span class="tag"><a href="https://matamask.io" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "metamask.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> Metamask</span></a> 钱包(如果已安装);', unsafe_allow_html=True)
265
  st.markdown(f'2. 在右上角点击打开 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "matamask-more.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 菜单栏</span>;', unsafe_allow_html=True)
@@ -268,7 +274,7 @@ st.markdown(f'4. 点击打开 <span class="tag"><img src="data:image/svg+xml;bas
268
  st.markdown(f'5. 点击打开 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "0x.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 显示十六进制数据</span>。', unsafe_allow_html=True)
269
  st.markdown(f'然后让我们构思一下我们心仪的铭文,我们需要把铭文的文本转换成十六进制形式的数据,在下方文本框输入。', unsafe_allow_html=True)
270
 
271
- input_ethscriptions_str = st.text_input('默认以 "data:," 开头,输入需要转换的文本:', key='输入需要转换的文本')
272
  if st.button('转换', key='文本转换到十六进制形式'):
273
  if not input_ethscriptions_str.startswith('data:,'):
274
  input_ethscriptions_str = f'data:,{input_ethscriptions_str}'
@@ -291,138 +297,41 @@ if st.button('转换', key='文本转换到十六进制形式'):
291
 
292
  st.markdown(f'好,让我们发送一笔交易吧,这笔交易是自己给自己发送 0ETH 的交易。', unsafe_allow_html=True)
293
  st.markdown(f'1. 点击发送 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "send.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 发送交易</span>;', unsafe_allow_html=True)
294
- st.markdown(f'2. 接收地址填写 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "address.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 自己的地址</span> ,这是铭文接收的地址,然后金额填写为 0ETH;', unsafe_allow_html=True)
295
- st.markdown(f'3. 填写 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "0x.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 十六进制数据</span> ,也就是我们刚才复制的文本;', unsafe_allow_html=True)
296
  st.markdown(f'4. 检查确认无误后发送交易。', unsafe_allow_html=True)
297
  st.markdown(f'稍等片刻,我们就可以在 <span class="tag"><a href="https://etherscan.io" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "etherscan-logo.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> Etherscan</span></a> 区块浏览器看到成功的交易。', unsafe_allow_html=True)
298
  st.markdown(f'<img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "etherscan_input_data.png"))}" alt="Image" style="max-width: 100%; width: 100%; height: auto; border-radius: 10px; display: block; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/>', unsafe_allow_html=True)
299
  st.markdown('')
300
 
301
- st.markdown(f'同时我们也可以前往 <span class="tag"><a href="https://ethscriptions.com/" target="_blank" style="text-decoration: none;"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "ethscriptions_logo_litto.png"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;"/> Ethscriptions</span></a> 官方网站查看我们的铭文。', unsafe_allow_html=True)
302
- st.markdown(f'如果你觉得上述的方法步骤繁琐,我们建议你使用 Ethscriptions 官方推荐的 [EthScriber](https://ethscriber.xyz/) 还有 [Etherscan IDM](https://etherscan.io/idm),其他题写工具须在你确保安全的情况下使用,因为你不能确定它要发送什么铭文的交易。', unsafe_allow_html=True)
303
- st.markdown(f'EthPen.com 不仅为你提供优质的工具,还有详尽的教程供你参考。欢迎你前来探索!', unsafe_allow_html=True)
304
-
305
- # st.expander
306
- search_rune_expander = st.expander("查询 Ethscriptions")
307
-
308
- st.markdown(f'🎉 更多功能尽在左上角的 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "greater-than.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 菜单栏</span>', unsafe_allow_html=True)
309
- # 查询铭文页面
310
- search_rune_expander.info(
311
- f"铭文数据来自 [Ethscriptions](https://ethscriptions.com/) 官方网站,当前索引器状态落后: {get_block_status()['blocks_behind']} 个区块。")
312
- search_rune_expander.markdown("##### 查询 ETH 地址所属铭文")
313
- user_address = search_rune_expander.text_input('输入 ETH 地址:', '')
314
-
315
- if search_rune_expander.button('🔍 查询', key='查询铭文'):
316
- # 检查ETH地址
317
- pattern = "^0x[a-fA-F0-9]{40}$"
318
- if re.match(pattern, user_address):
319
- data = get_ethscriptions_by_address(user_address)
320
- if not data:
321
- search_rune_expander.info(
322
- "该地址没有任何铭文,或许是区块暂时没同步过来,如果你不确定,请前往 Ethscriptions 官网再次查询!")
323
- else:
324
- token_total = 0
325
- for token in token_list:
326
- for ethscriptions in data:
327
- if is_valid_eths(ethscriptions['content_uri'], token['p'], token['tick'], token['id'],
328
- token['amt']):
329
- input_data_str = json.loads(ethscriptions['content_uri'][6:])
330
- token_total = token_total + int(input_data_str['amt'])
331
- search_rune_expander.success(f"${token['tick']} 有效总量为:{token_total}")
332
- token_total = 0
333
- search_rune_expander.warning(
334
- f"只统计 etch.market 流动性较高的资产,etch.market 上架的代币不计算在内,详细更多的信息请点击[这里](https://ethscriptions.com/{user_address})")
335
- else:
336
- search_rune_expander.error("输入的 ETH 地址不正确!")
337
-
338
- search_rune_expander.markdown("##### 查询铭文的完整信息")
339
- ethscriptions_str = search_rune_expander.text_input('输入完整的铭文文本:', '')
340
- if search_rune_expander.button('🔍 查询', key='查询信息'):
341
  if not ethscriptions_str.startswith('data:,'):
342
  ethscriptions_str = f'data:,{ethscriptions_str}'
343
  ethscriptions_all_str = sha256(ethscriptions_str)
344
  ethscriptions_data = check_content_exists(ethscriptions_all_str)
345
  if ethscriptions_data['result']:
346
- search_rune_expander.markdown(f'###### :green[{ethscriptions_str}] 相关信息如下:')
347
  selected_data = {
348
  '当前拥有者': ethscriptions_data["ethscription"]["current_owner"],
349
  '题写时间': ethscriptions_data["ethscription"]["creation_timestamp"],
350
  '铭文编号': f'#{ethscriptions_data["ethscription"]["ethscription_number"]}',
351
  '铭文完整内容': ethscriptions_data["ethscription"]["content_uri"],
352
  }
353
- search_rune_expander.json(selected_data)
354
  else:
355
- search_rune_expander.markdown(f'###### :green[{ethscriptions_str}] 铭文还没被题写!复制下方文本前去题写。')
356
- search_rune_expander.code(f'{text_to_hex(ethscriptions_str)}', line_numbers=False)
357
-
358
- # 批量查询铭文是否被存在
359
- runes = []
360
- search_rune_expander.markdown("##### 批量查询铭文是否被题写")
361
- search_rune_expander.markdown(r'''
362
- 1. **查域名**:可以用空格和换行符还有英文逗号(,)分开,不要带前缀 data:,
363
- 2. **查代币**:需要查询的代币铭文内容变动的部分文本如 id 请用 "@#" 代替,例如:`data:,{"p":"erc-20","op":"mint","tick":"eths","id":"@#","amt":"1000"}`
364
- 3. **查纯文本**:可任意填写,用空格和换行符还有英文逗号(,)分开''')
365
- input_content = search_rune_expander.text_area(f'输入铭文文本:')
366
- search_type = search_rune_expander.radio('选择查询类型:', ['查域名', '查代币', '查纯文本'],
367
- label_visibility="collapsed", horizontal=True, index=2)
368
- if search_type == '查域名':
369
- search_dotname = search_rune_expander.text_input('填写域名后缀:')
370
- elif search_type == '查代币':
371
- search_token_min_id = search_rune_expander.number_input(f'填写代币铭文范围的**最小 ID(id)**:', min_value=1, value=1,
372
- step=1)
373
- search_token_max_id = search_rune_expander.number_input(f'填写代币铭文范围的**最大 ID(id)**:', value=10, step=1)
374
-
375
- if search_rune_expander.button('🔍 查询', key='批量查询铭文'):
376
- runes = re.split(r'[\s,]+', input_content)
377
- # 过滤掉空或仅包含空白字符的项
378
- runes = [r for r in runes if r.strip() != '']
379
- if search_type == '查域名':
380
- runes = [r + search_dotname for r in runes]
381
- elif search_type == '查代币':
382
- token_runes = []
383
- if len(runes) != 1 and '@#' in input_content:
384
- for the_id in range(search_token_min_id, search_token_max_id + 1):
385
- token_runes.append(input_content.replace('@#', str(the_id)))
386
- else:
387
- st.stop()
388
- runes = token_runes
389
- # 创建一个空的结果列表
390
- results = []
391
- # 循环遍历每一个铭文并查询其存在状态
392
- for rune in runes:
393
- if not rune.startswith('data:,'):
394
- rune = f'data:,{rune}'
395
- sha_value = sha256(rune)
396
- response_data = check_content_exists(sha_value)
397
-
398
- # 根据返回的result,得到真或者假
399
- exists = "已题写" if response_data['result'] else "未题写"
400
- results.append([rune, exists])
401
-
402
- # 使用 st.table 显示结果
403
- table_data = pd.DataFrame(results, columns=['铭文', '状态'])
404
-
405
- # 根据状态生成一个辅助排序列
406
- table_data['sort_helper'] = table_data['状态'].apply(lambda x: 0 if x == "未题写" else 1)
407
-
408
- # 使用辅助列进行排序
409
- table_data.sort_values(by='sort_helper', ascending=True, inplace=True)
410
-
411
- # 删除辅助排序列
412
- table_data.drop(columns=['sort_helper'], inplace=True)
413
- result_df = pd.DataFrame(table_data)
414
- search_rune_expander.dataframe(result_df, use_container_width=True, hide_index=True)
415
- # Convert DataFrame to CSV with proper encoding
416
- csv_export = result_df.to_csv(index=False, encoding='utf-8')
417
- # Add a download button for the DataFrame
418
- search_rune_expander.download_button(
419
- label="下载搜索结果",
420
- data=csv_export,
421
- file_name="ethpen_result_data.csv",
422
- mime="text/csv"
423
- )
424
 
425
- st.markdown('#### Ethscriptions 上的龙头代币是?')
426
  st.markdown(
427
  f'毫无疑问,当之无愧,它必须是 Ethscriptions 上第一个代币 <span class="tag"><a href="https://www.etch.market/market/token?category=token&collectionName=erc-20%20eths" target="_blank" style="text-decoration: none;"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "eths_logo.png"))}" alt="Image" width="20px" height="20px"/> $eths</span></a>',
428
  unsafe_allow_html=True)
 
8
  import sqlite3
9
  import os
10
 
 
11
 
12
+ infura_api_key_eths = os.environ.get('infura_api_key_eths', '')
13
  # Ethscriptions Mainnet API 的基础 URL
14
  BASE_URL = "https://mainnet-api.ethscriptions.com/api"
15
 
 
95
 
96
  # 根据 TX 获取 input data
97
  def get_input_data(tx_hash):
98
+ endpoint = infura_api_key_eths
99
  headers = {
100
  "Content-Type": "application/json",
101
  }
 
221
  return {} # 返回一个空字典作为默认值
222
 
223
 
224
+ st.set_page_config(page_title="EthPen - 以太之笔", page_icon="🖊", layout='centered', initial_sidebar_state='auto')
225
 
226
  # 首页
227
  st.markdown(
228
+ f'# <a href="https://ethpen.com" target="_self"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "ethpen_logo.svg"))}" alt="Image" width="64px" height="64px" style="border-radius: 8px; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/></a> :rainbow[EthPen - 以太之笔]',
229
  unsafe_allow_html=True)
230
  st.subheader('', anchor=False, divider='rainbow')
231
 
232
  # 最近新闻
233
+ st.markdown(f'### 最近新闻')
234
 
235
  st.markdown(
236
  f'<a href="https://twitter.com/dumbnamenumbers/status/1696989307871826137" target="_blank"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "news.jpeg"))}" alt="Image" style="max-width: 100%; width: 100%; height: auto; border-radius: 10px; display: block; box-shadow: 5px 5px 15px rgba(0,0,0,0.3);"/></a>',
 
240
  st.markdown(f'> 3 周前,我们提出了 Ethscriptions 虚拟机的构想——一种通过将其解释为计算机指令来显著增强 Ethscriptions 功能的方法。今天,我们宣布了该虚拟机的首个实现。已在 Goerli 网络上线,并已在 GitHub 上完全开源!👆')
241
 
242
  # 广告位图片
243
+ st.markdown(f'### 广告位')
244
 
245
 
246
+ # st.markdown(
247
+ # f'<a href="https://twitter.com/EtchMarket/status/1694024108672245953" target="_blank"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "ad.jpg"))}" alt="Image" style="max-width: 100%; width: 100%; height: auto; border-radius: 10px; display: block; box-shadow: 5px 5px 15px rgba(0,0,0,0.3);"/></a>',
248
+ # unsafe_allow_html=True)
249
+ #
250
+ # st.markdown(f'> 拆分方案现在面向所有人推出!让我们一起加入权益挖矿的浪潮,并分享50%的月度服务费。')
251
+ st.markdown(f'😭 接不到广告,我太穷了。')
252
+
253
  st.markdown(
254
+ f'<img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "alipay_qrcode.png"))}" alt="Image" style="max-width: 100%; width: 50%; height: auto; border-radius: 10px; display: block; box-shadow: 5px 5px 15px rgba(0,0,0,0.3);"/>',
255
  unsafe_allow_html=True)
256
+ st.markdown('')
257
+ st.markdown(f'复制 <span class="tag">845076800</span>。 打开支付宝搜索,赏我一个猪脚饭。🥺', unsafe_allow_html=True)
258
 
259
+ st.markdown(f'### 什么是 Ethscriptions?', unsafe_allow_html=True)
 
 
 
260
  st.markdown(f'{my_style}<span class="tag"><a href="https://ethscriptions.com/" target="_blank" style="text-decoration: none;"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "ethscriptions_logo_litto.png"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;"/> Ethscriptions</span></a> 是一种新的在以太坊上创建和分享数字资产的方法,它通过使用交易 calldata 存储数据而不是智能合约来实现,这使其比 NFT 更为经济。它们是完全在链上、无需许可、抗审查的,并且其成本只是 NFT 的一小部分。', unsafe_allow_html=True)
261
 
262
  st.markdown(f'<img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "input_data.png"))}" alt="Image" style="max-width: 100%; width: 100%; height: auto; border-radius: 10px; display: block; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/>', unsafe_allow_html=True)
263
  st.markdown('')
264
+ st.markdown('### 谁创造了 Ethscriptions?')
265
  st.markdown(f'首个 [Ethscription](https://ethscriptions.com/ethscriptions/0) 是在 2016 年创建的,但正式的协议是由 Tom Lehman,又名 <span class="tag"><a href="https://twitter.com/dumbnamenumbers" target="_blank" style="text-decoration: none;"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "Middlemarch.jpg"))}" alt="Image" width="20px" height="20px" style="border-radius: 10px;"/> @Middlemarch</span></a> 开发的。除了比特币的铭文,他还受到了来自 Poly Network 黑客的著名的 “原型 - Ethscription” 的启发,你可以在[这笔交易](https://etherscan.io/tx/0x0ae3d3ce3630b5162484db5f3bdfacdfba33724ffb195ea92a6056beaa169490)中看到它。', unsafe_allow_html=True)
266
  st.markdown(f'- 快来加入 <span class="tag"><a href="https://discord.gg/ethscriptions" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "discord.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> @ethscriptions</span></a>,一起讨论 Ethscriptions 的未来!', unsafe_allow_html=True)
267
  st.markdown(f'- 快来关注 <span class="tag"><a href="https://twitter.com/ethscriptions" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "x.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> @ethscriptions</span></a>,掌握 Ethscriptions 的最新动态!', unsafe_allow_html=True)
268
+ st.markdown('### 如何题写 Ethscriptions?')
269
  st.markdown(f'题写 Ethscriptions 是十分简单的,相当于在发送 ETH 交易时附带一些转账备注,我们就以使用人数最多的 MetaMask 钱包来举例,我们首先打开<span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "0x.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 显示十六进制数据</span>。', unsafe_allow_html=True)
270
  st.markdown(f'1. 打开 <span class="tag"><a href="https://matamask.io" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "metamask.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> Metamask</span></a> 钱包(如果已安装);', unsafe_allow_html=True)
271
  st.markdown(f'2. 在右上角点击打开 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "matamask-more.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 菜单栏</span>;', unsafe_allow_html=True)
 
274
  st.markdown(f'5. 点击打开 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "0x.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 显示十六进制数据</span>。', unsafe_allow_html=True)
275
  st.markdown(f'然后让我们构思一下我们心仪的铭文,我们需要把铭文的文本转换成十六进制形式的数据,在下方文本框输入。', unsafe_allow_html=True)
276
 
277
+ input_ethscriptions_str = st.text_input('默认以 `data:,` 开头,输入需要转换的文本:', key='输入需要转换的文本')
278
  if st.button('转换', key='文本转换到十六进制形式'):
279
  if not input_ethscriptions_str.startswith('data:,'):
280
  input_ethscriptions_str = f'data:,{input_ethscriptions_str}'
 
297
 
298
  st.markdown(f'好,让我们发送一笔交易吧,这笔交易是自己给自己发送 0ETH 的交易。', unsafe_allow_html=True)
299
  st.markdown(f'1. 点击发送 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "send.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 发送交易</span>;', unsafe_allow_html=True)
300
+ st.markdown(f'2. 接收地址填写 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "address.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 自己的地址</span>,这是铭文接收的地址,然后金额填写为 0ETH;', unsafe_allow_html=True)
301
+ st.markdown(f'3. 填写 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "0x.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 十六进制数据</span>,也就是我们刚才复制的文本;', unsafe_allow_html=True)
302
  st.markdown(f'4. 检查确认无误后发送交易。', unsafe_allow_html=True)
303
  st.markdown(f'稍等片刻,我们就可以在 <span class="tag"><a href="https://etherscan.io" target="_blank" style="text-decoration: none;"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "etherscan-logo.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> Etherscan</span></a> 区块浏览器看到成功的交易。', unsafe_allow_html=True)
304
  st.markdown(f'<img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "etherscan_input_data.png"))}" alt="Image" style="max-width: 100%; width: 100%; height: auto; border-radius: 10px; display: block; box-shadow: 3px 3px 10px rgba(0,0,0,0.1);"/>', unsafe_allow_html=True)
305
  st.markdown('')
306
 
307
+ st.markdown('我们可以输入铭文文本查询详细的信息,')
308
+ col1, col2 = st.columns(2)
309
+ ethscriptions_str = col1.text_input('输入完整的铭文文本:', '', label_visibility='collapsed')
310
+ search_information = col2.button('🔍 查询', key='查询信息')
311
+ if search_information:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  if not ethscriptions_str.startswith('data:,'):
313
  ethscriptions_str = f'data:,{ethscriptions_str}'
314
  ethscriptions_all_str = sha256(ethscriptions_str)
315
  ethscriptions_data = check_content_exists(ethscriptions_all_str)
316
  if ethscriptions_data['result']:
317
+ st.markdown(f'###### :green[{ethscriptions_str}] 相关信息如下:')
318
  selected_data = {
319
  '当前拥有者': ethscriptions_data["ethscription"]["current_owner"],
320
  '题写时间': ethscriptions_data["ethscription"]["creation_timestamp"],
321
  '铭文编号': f'#{ethscriptions_data["ethscription"]["ethscription_number"]}',
322
  '铭文完整内容': ethscriptions_data["ethscription"]["content_uri"],
323
  }
324
+ st.json(selected_data)
325
  else:
326
+ st.markdown(f'###### :green[{ethscriptions_str}] 铭文还没被题写!复制下方文本前去题写。')
327
+ st.code(f'{text_to_hex(ethscriptions_str)}', line_numbers=False)
328
+
329
+ st.markdown(f'同时我们也可以前往 <span class="tag"><a href="https://ethscriptions.com/" target="_blank" style="text-decoration: none;"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "ethscriptions_logo_litto.png"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;"/> Ethscriptions</span></a> 官方网站查看我们的铭文。', unsafe_allow_html=True)
330
+ st.markdown(f'如果你觉得上述的方法步骤繁琐,我们建议你使用 Ethscriptions 官方推荐的 [EthScriber](https://ethscriber.xyz/) 还有 [Etherscan IDM](https://etherscan.io/idm),其他题写工具须在你确保安全的情况下使用,因为你不能确定它要发送什么铭文的交易。', unsafe_allow_html=True)
331
+ st.markdown(f'EthPen.com 不仅为你提供优质的工具,还有详尽的教程供你参考。欢迎你前来探索!', unsafe_allow_html=True)
332
+ st.markdown(f'��� 更多功能尽在左上角的 <span class="tag"><img src="data:image/svg+xml;base64,{image_to_base64(os.path.join("img", "greater-than.svg"))}" alt="Image" width="20px" height="20px" style="border-radius: 3px;" /> 菜单栏</span>', unsafe_allow_html=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
+ st.markdown('### Ethscriptions 上的龙头代币是?')
335
  st.markdown(
336
  f'毫无疑问,当之无愧,它必须是 Ethscriptions 上第一个代币 <span class="tag"><a href="https://www.etch.market/market/token?category=token&collectionName=erc-20%20eths" target="_blank" style="text-decoration: none;"><img src="data:image/jpeg;base64,{image_to_base64(os.path.join("img", "eths_logo.png"))}" alt="Image" width="20px" height="20px"/> $eths</span></a>',
337
  unsafe_allow_html=True)