Spaces:
Sleeping
Sleeping
Ethscriptions
commited on
Commit
·
c18992d
1
Parent(s):
9c1300a
Add application file
Browse files- .DS_Store +0 -0
- data/.DS_Store +0 -0
- data/1.txt +0 -0
- img/.DS_Store +0 -0
- img/alipay_qrcode.png +0 -0
- img/pushover_logo.jpg +0 -0
- logo/.DS_Store +0 -0
- logo/favicon.png +0 -0
- logo/icon-512.png +0 -0
- logo/icon-57.png +0 -0
- logo/icon-60.png +0 -0
- logo/icon-60@3x.png +0 -0
- logo/icon-hdpi.png +0 -0
- logo/icon-ldpi.png +0 -0
- logo/icon-mdpi.png +0 -0
- logo/icon-xhdpi.png +0 -0
- logo/icon-xxhdpi.png +0 -0
- logo/splash-1125x2436.png +0 -0
- logo/splash-1242x2208.png +0 -0
- logo/splash-640x1136.png +0 -0
- pages/.DS_Store +0 -0
- pages/1_🔍_批量查询铭文状态.py +185 -29
- pages/2_🆔_ 批量题写域名铭文.py +9 -2
- pages/3_🪙_ 批量题写代币铭文.py +9 -2
- pages/4_💹️_铭文数据分析.py +1195 -43
- pages/5_🏫_教程中心.py +11 -3
- pages/6_📢_推送通知服务.py +316 -4
- pages/7_ℹ️️_EthPen.com 简介.py +97 -0
- pages/7_ℹ️️_关于 EthPen.com.py +0 -50
- requirements.txt +2 -107
- 🖊️EthPen-以太之笔.py +37 -128
.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
|
7 |
-
|
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.
|
|
|
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 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
|
232 |
else:
|
233 |
-
|
234 |
-
|
235 |
-
|
|
|
|
|
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 |
-
|
238 |
-
|
239 |
-
|
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.
|
|
|
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.
|
|
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
17 |
|
18 |
# 获取当前文件目录
|
19 |
current_dir = os.path.dirname(__file__)
|
20 |
parent_dir = os.path.dirname(current_dir)
|
|
|
21 |
# 构造数据库文件路径
|
22 |
-
|
23 |
-
|
24 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'])
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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'
|
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 |
-
#
|
153 |
-
|
154 |
-
|
|
|
|
|
|
|
|
|
|
|
155 |
|
156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 |
-
|
205 |
-
|
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['
|
227 |
if thread_start_state_value == '0':
|
228 |
-
print('
|
229 |
-
config['
|
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
9 |
# Streamlit app layout
|
10 |
st.set_page_config(page_title="EthPen - 教程中心", page_icon="🏫", layout='centered', initial_sidebar_state='auto')
|
11 |
-
st.
|
|
|
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
|
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.
|
|
|
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 |
-
|
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 |
-
|
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 =
|
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='
|
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'<
|
248 |
unsafe_allow_html=True)
|
|
|
|
|
249 |
|
250 |
-
st.markdown(f'
|
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('
|
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('
|
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('默认以
|
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
|
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
|
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(
|
302 |
-
|
303 |
-
|
304 |
-
|
305 |
-
|
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 |
-
|
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 |
-
|
354 |
else:
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
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('
|
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)
|