Spaces:
Sleeping
Sleeping
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)
|