跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像AI 生图工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
Python大前端

基于Python Flask的Web3应用开发

使用 Python Flask 和 web3.py 构建 Web3 应用的完整流程。涵盖 Web3 核心概念、环境搭建(Ganache/Hardhat)、项目结构设计、后端 API 实现(余额查询、地址验证)、前端页面交互以及部署注意事项。提供了详细的代码示例,包括 Flask 路由、Web3 连接配置、HTML/CSS/JS 模板及扩展功能(ERC20 代币、交易历史)。适合希望入门区块链后端开发的开发者参考。

栈溢出发布于 2026/4/5更新于 2026/7/2150 浏览

基于 Python Flask 的 Web3 应用开发

第一部分:Web3 概述

核心要点总结
1. Web3 的核心概念
  • 数据所有权:用户真正拥有自己的数据和资产
  • 去中心化:无需中介即可完成价值转移
  • 可组合性:应用之间可以无缝交互
2. 技术架构
前端 (HTML/JS) → Flask 后端 → web3.py → 以太坊节点 (Infura)
3. 关键技术栈
  • Flask: 轻量级 Web 框架
  • web3.py: Python 的以太坊交互库
  • Infura/Alchemy: 区块链节点服务

进阶开发建议

1. 安全注意事项
# 1. 输入验证增强
from eth_utils import is_checksum_address

def validate_address(address):
    if not is_checksum_address(address):
        # 进一步处理或返回错误
        pass

# 2. 环境变量安全
# 不要将私钥硬编码在代码中
PRIVATE_KEY = os.getenv('WALLET_PRIVATE_KEY')
2. 智能合约交互示例
from web3 import Web3
from web3.contract import Contract

# 加载合约
contract_address = "0xContractAddress"
with open('contract_abi.json') as f:
    contract_abi = json.load(f)
contract = w3.eth.contract(address=contract_address, abi=contract_abi)

# 读取合约数据
balance = contract.functions.balanceOf(address).call()

# 发送交易(需要私钥签名)
nonce = w3.eth.get_transaction_count(my_address)
transaction = contract.functions.transfer(
    to_address,
    amount
).build_transaction({
    : ,
    : ,
    : w3.to_wei(, ),
    : nonce,
})

目录

  1. 基于 Python Flask 的 Web3 应用开发
  2. 第一部分:Web3 概述
  3. 核心要点总结
  4. 1. Web3 的核心概念
  5. 2. 技术架构
  6. 3. 关键技术栈
  7. 进阶开发建议
  8. 1. 安全注意事项
  9. 1. 输入验证增强
  10. 2. 环境变量安全
  11. 不要将私钥硬编码在代码中
  12. 2. 智能合约交互示例
  13. 加载合约
  14. 读取合约数据
  15. 发送交易(需要私钥签名)
  16. 3. 添加更多功能
  17. 1. 获取交易记录
  18. 2. 获取代币余额
  19. 3. 发送交易
  20. 4. 项目结构优化
  21. 5. 部署注意事项
  22. 使用缓存
  23. 6. 测试建议
  24. 使用 web3.py 的测试功能
  25. 学习路径建议
  26. 第二部分:完整可运行项目设置
  27. 1. 环境准备与安装
  28. 1.1 系统要求
  29. 1.2 安装 Python 依赖
  30. 创建项目目录
  31. 创建虚拟环境(推荐)
  32. 激活虚拟环境
  33. Windows: venv\Scripts\activate
  34. Mac/Linux: source venv/bin/activate
  35. 安装依赖
  36. 1.3 安装以太坊本地测试环境(可选,推荐)
  37. 1. 安装 Ganache CLI(命令行版)
  38. 或安装 Ganache 桌面版:https://trufflesuite.com/ganache/
  39. 2. 启动 Ganache
  40. 1. 初始化 Hardhat 项目
  41. 2. 创建 Hardhat 项目
  42. 选择 "Create a JavaScript project"
  43. 3. 启动本地节点
  44. 2. 项目结构详细说明
  45. 3. 详细配置文件
  46. 3.1 .env.example 文件
  47. 以太坊节点配置
  48. 使用 Infura(主网)
  49. 使用 Alchemy(备选)
  50. 本地 Ganache 节点(开发用)
  51. 应用配置
  52. 钱包配置(测试用,不要用于主网)
  53. 可选:Etherscan API(用于查询交易)
  54. 3.2 requirements.txt 文件
  55. 3.3 config.py 配置文件
  56. 创建配置实例
  57. 4. 完整后端代码
  58. 4.1 app.py - 主应用文件
  59. 添加项目根目录到 Python 路径
  60. 禁用未签名交易的自动签名警告
  61. 初始化 Web3 连接
  62. 全局 Web3 实例
  63. 工具函数
  64. 路由定义
  65. 错误处理
  66. 5. 前端页面详细实现
  67. 5.1 templates/base.html - 基础模板
  68. 5.2 templates/index.html - 主页面
  69. 6. 运行应用
  70. 6.1 启动本地以太坊节点(Ganache)
  71. 打开一个新的终端窗口
  72. 6.2 配置环境变量
  73. 复制环境变量示例文件
  74. 编辑 .env 文件(可选,如果使用 Ganache 可以不填 INFURA_URL)
  75. 如果你有 Infura 账号,可以填写 INFURA_URL
  76. 否则留空,系统会自动使用本地 Ganache 节点
  77. 6.3 启动 Flask 应用
  78. 在项目根目录运行
  79. 6.4 测试应用
  80. 7. 扩展功能示例
  81. 7.1 添加 ERC20 代币余额查询
  82. ERC20 ABI (简化版)
  83. 常用 ERC20 代币地址
  84. 7.2 添加交易历史查询
  85. 8. 常见问题解决
  86. 问题 1:无法连接到节点
  87. 问题 2:Python 包安装失败
  88. 升级 pip
  89. 使用国内镜像源
  90. 或使用 conda
  91. 问题 3:地址格式验证失败
  92. 问题 4:余额显示为 0
  93. 9. 学习资源
  94. 10. 下一步建议
  • 免费图片AI生成工具免费生成了解详情
'chainId'
1
'gas'
200000
'gasPrice'
'50'
'gwei'
'nonce'
3. 添加更多功能
# 1. 获取交易记录
@app.route('/get_transactions')
def get_transactions():
    address = request.args.get('address')
    # 使用 Etherscan API 或节点查询

# 2. 获取代币余额
@app.route('/get_token_balance')
def get_token_balance():
    # 查询 ERC20 代币余额

# 3. 发送交易
@app.route('/send_transaction', methods=['POST'])
def send_transaction():
    # 实现交易发送功能(需安全处理私钥)
4. 项目结构优化
flask-web3-demo/
├── app.py              # 主应用文件
├── config.py           # 配置文件
├── requirements.txt    # 依赖包
├── .env                # 环境变量
├── static/             # 静态文件
│   ├── css/
│   └── js/
├── templates/          # 模板文件
│   ├── base.html
│   ├── index.html
│   └── dashboard.html
└── blueprints/         # 蓝图模块
    ├── auth.py         # 认证模块
    ├── wallet.py       # 钱包功能
    └── contracts.py    # 合约交互
5. 部署注意事项
  1. 安全性:
    • 使用 HTTPS
    • 保护私钥和 API 密钥
    • 实现速率限制
    • 添加输入验证和防注入

错误处理增强:

@app.errorhandler(500)
def internal_error(error):
    return jsonify({"error": "Internal server error", "message": "Please try again later"}), 500

性能优化:

# 使用缓存
from flask_caching import Cache

cache = Cache(config={'CACHE_TYPE': 'simple'})

@app.route('/get_balance')
@cache.cached(timeout=30)  # 缓存 30 秒
def get_balance():
    # 查询余额
6. 测试建议
# 使用 web3.py 的测试功能
from web3 import EthereumTesterProvider

def test_balance_query():
    w3 = Web3(EthereumTesterProvider())
    # 测试余额查询逻辑

学习路径建议

  1. 基础掌握:
    • Flask 基础
    • web3.py 官方文档
    • 以太坊基础概念
  2. 进阶学习:
    • 智能合约开发 (Solidity)
    • 前端集成 (web3.js/ethers.js)
    • 安全最佳实践
  3. 项目实践:
    • NFT 市场
    • DeFi 仪表板
    • DAO 投票系统
    • 多链钱包

这个示例项目虽然简单,但为你展示了 Web3 开发的核心流程。随着 Web3 生态的发展,Python 在区块链数据分析、后端服务和自动化工具方面都有很大应用空间。


第二部分:完整可运行项目设置

1. 环境准备与安装
1.1 系统要求
  • Python 3.8+
  • 操作系统:Windows/Mac/Linux
  • 至少 2GB 可用内存
1.2 安装 Python 依赖
# 创建项目目录
mkdir flask-web3-demo
cd flask-web3-demo

# 创建虚拟环境(推荐)
python -m venv venv

# 激活虚拟环境
# Windows: venv\Scripts\activate
# Mac/Linux: source venv/bin/activate

# 安装依赖
pip install flask web3 python-dotenv requests
1.3 安装以太坊本地测试环境(可选,推荐)

方案 A:使用 Ganache(快速本地测试链)

# 1. 安装 Ganache CLI(命令行版)
npm install -g ganache
# 或安装 Ganache 桌面版:https://trufflesuite.com/ganache/

# 2. 启动 Ganache
ganache --server.port 8545 --wallet.totalAccounts 10 --wallet.defaultBalance 100

Ganache 会在 http://localhost:8545 启动一个本地以太坊节点,提供 10 个测试账户,每个账户 100ETH。

方案 B:使用 Hardhat(更接近主网环境)

# 1. 初始化 Hardhat 项目
mkdir hardhat-test
cd hardhat-test
npm init -y
npm install --save-dev hardhat

# 2. 创建 Hardhat 项目
npx hardhat init
# 选择 "Create a JavaScript project"

# 3. 启动本地节点
npx hardhat node
2. 项目结构详细说明
flask-web3-demo/
├── app.py              # 主应用文件
├── config.py           # 配置文件
├── requirements.txt    # 依赖包列表
├── .env                # 环境变量(不要提交到 Git)
├── .env.example        # 环境变量示例
├── static/             # 静态文件
│   ├── css/
│   │   └── style.css
│   └── js/
│       └── app.js
├── templates/          # HTML 模板
│   ├── base.html
│   ├── index.html
│   └── error.html
├── contracts/          # 智能合约(可选)
│   └── SimpleToken.sol
├── tests/              # 测试文件
│   └── test_app.py
└── utils/              # 工具函数
    └── web3_utils.py
3. 详细配置文件
3.1 .env.example 文件
# 以太坊节点配置
# 使用 Infura(主网)
INFURA_URL=https://mainnet.infura.io/v3/YOUR_INFURA_PROJECT_ID

# 使用 Alchemy(备选)
ALCHEMY_URL=https://eth-mainnet.g.alchemy.com/v2/YOUR_ALCHEMY_API_KEY

# 本地 Ganache 节点(开发用)
GANACHE_URL=http://localhost:8545

# 应用配置
FLASK_ENV=development
FLASK_DEBUG=true
SECRET_KEY=your-secret-key-here

# 钱包配置(测试用,不要用于主网)
TEST_PRIVATE_KEY=your-test-private-key-here
TEST_ADDRESS=0xYourTestAddressHere

# 可选:Etherscan API(用于查询交易)
ETHERSCAN_API_KEY=YourEtherscanAPIKey
3.2 requirements.txt 文件
Flask==2.3.3
web3==6.10.0
python-dotenv==1.0.0
requests==2.31.0
eth-account==0.9.0
eth-typing==3.5.0
eth-utils==2.3.0
cryptography==41.0.7
3.3 config.py 配置文件
import os
from dotenv import load_dotenv

load_dotenv()

class Config:
    """基础配置"""
    SECRET_KEY = os.getenv('SECRET_KEY', 'dev-secret-key')
    FLASK_ENV = os.getenv('FLASK_ENV', 'development')
    DEBUG = os.getenv('FLASK_DEBUG', 'true').lower() == 'true'

    # Web3 配置
    INFURA_URL = os.getenv('INFURA_URL')
    ALCHEMY_URL = os.getenv('ALCHEMY_URL')
    GANACHE_URL = os.getenv('GANACHE_URL', 'http://localhost:8545')

    # 优先使用本地节点,然后使用免费节点
    def get_web3_provider(self):
        """获取 Web3 节点提供者"""
        if self.GANACHE_URL:
            return self.GANACHE_URL
        elif self.ALCHEMY_URL:
            return self.ALCHEMY_URL
        elif self.INFURA_URL:
            return self.INFURA_URL
        else:
            # 备用公共节点(可能不稳定)
            return "https://rpc.ankr.com/eth"

    # 网络 ID
    CHAIN_ID = 1  # 以太坊主网

    # 测试钱包配置
    TEST_PRIVATE_KEY = os.getenv('TEST_PRIVATE_KEY')
    TEST_ADDRESS = os.getenv('TEST_ADDRESS')

    # API Keys
    ETHERSCAN_API_KEY = os.getenv('ETHERSCAN_API_KEY')

    @property
    def is_local_node(self):
        """检查是否使用本地节点"""
        return 'localhost' in self.get_web3_provider() or '127.0.0.1' in self.get_web3_provider()

# 创建配置实例
config = Config()
4. 完整后端代码
4.1 app.py - 主应用文件
from flask import Flask, render_template, request, jsonify, session
from web3 import Web3
from web3.exceptions import InvalidAddress
from eth_account import Account
import json
import os
import sys

# 添加项目根目录到 Python 路径
sys.path.append(os.path.dirname(os.path.abspath(__file__)))
from config import config

# 禁用未签名交易的自动签名警告
import warnings
warnings.filterwarnings("ignore")

app = Flask(__name__)
app.config['SECRET_KEY'] = config.SECRET_KEY

# 初始化 Web3 连接
def init_web3():
    """初始化 Web3 连接"""
    provider_url = config.get_web3_provider()
    print(f"正在连接 Web3 节点:{provider_url}")
    # 创建 Web3 实例
    w3 = Web3(Web3.HTTPProvider(provider_url))
    # 测试连接
    try:
        if w3.is_connected():
            print("✅ 成功连接到以太坊节点")
            print(f"  网络 ID: {w3.eth.chain_id}")
            print(f"  最新区块:{w3.eth.block_number}")
            print(f"  节点:{provider_url}")
        else:
            print("❌ 连接失败,请检查节点配置")
    except Exception as e:
        print(f"❌ 连接错误:{e}")
    return w3

# 全局 Web3 实例
w3 = init_web3()

# 工具函数
def validate_ethereum_address(address):
    """验证以太坊地址格式"""
    if not address:
        return False, "地址不能为空"
    # 检查长度
    if len(address) != 42:
        return False, "地址长度应为 42 个字符"
    # 检查前缀
    if not address.startswith('0x'):
        return False, "地址应以 0x 开头"
    # 检查十六进制字符
    try:
        # web3.py 的地址验证
        if w3.is_address(address):
            return True, "地址格式正确"
        else:
            return False, "无效的以太坊地址"
    except Exception as e:
        return False, f"地址验证错误:{str(e)}"

def get_eth_balance(address):
    """获取 ETH 余额"""
    try:
        # 验证地址
        is_valid, message = validate_ethereum_address(address)
        if not is_valid:
            return None, message
        
        # 获取余额(单位:Wei)
        balance_wei = w3.eth.get_balance(address)
        # 转换为 ETH
        balance_eth = w3.from_wei(balance_wei, 'ether')
        # 格式化显示
        if balance_eth == 0:
            balance_str = "0 ETH"
        elif balance_eth < 0.0001:
            balance_str = f"{balance_eth:.8f} ETH"
        else:
            balance_str = f"{balance_eth:.4f} ETH"
        
        return {
            'address': address,
            'balance_wei': str(balance_wei),
            'balance_eth': str(balance_eth),
            'balance_display': balance_str,
            'network': w3.eth.chain_id,
            'block_number': w3.eth.block_number
        }, None
    except Exception as e:
        return None, f"查询余额失败:{str(e)}"

def create_test_account():
    """创建测试账户(仅用于开发)"""
    if config.is_local_node:
        try:
            # 获取 Ganache 的测试账户
            accounts = w3.eth.accounts
            if accounts:
                # 获取第一个账户的私钥(仅 Ganache 可用)
                # 注意:实际开发中不要在生产环境使用这种方法
                if hasattr(w3.provider, 'ethereum_tester'):
                    # 如果是测试环境
                    return {
                        'address': accounts[0],
                        'private_key': '测试环境不显示私钥',
                        'balance': w3.from_wei(w3.eth.get_balance(accounts[0]), 'ether')
                    }
                return {
                    'address': accounts[0],
                    'private_key': '请查看 Ganache 界面获取私钥',
                    'balance': w3.from_wei(w3.eth.get_balance(accounts[0]), 'ether')
                }
        except:
            pass
    # 如果没有本地节点,创建一个新账户
    account = Account.create()
    return {
        'address': account.address,
        'private_key': account.key.hex(),
        'balance': '0'
    }

# 路由定义
@app.route('/')
def index():
    """主页"""
    # 获取网络状态
    try:
        network_info = {
            'is_connected': w3.is_connected(),
            'chain_id': w3.eth.chain_id,
            'block_number': w3.eth.block_number,
            'gas_price': w3.from_wei(w3.eth.gas_price, 'gwei') if w3.is_connected() else 0,
            'is_local': config.is_local_node
        }
    except:
        network_info = {
            'is_connected': False,
            'chain_id': '未知',
            'block_number': 0,
            'gas_price': 0,
            'is_local': config.is_local_node
        }
    return render_template('index.html', network=network_info, default_address=config.TEST_ADDRESS)

@app.route('/api/balance', methods=['POST'])
def api_get_balance():
    """API:获取余额"""
    data = request.get_json()
    if not data or 'address' not in data:
        return jsonify({'success': False, 'error': '请提供以太坊地址', 'code': 'MISSING_ADDRESS'}), 400
    
    address = data['address'].strip()
    result, error = get_eth_balance(address)
    if error:
        return jsonify({'success': False, 'error': error, 'code': 'BALANCE_ERROR'}), 400
    return jsonify({'success': True, 'data': result})

@app.route('/api/network-status')
def api_network_status():
    """API:获取网络状态"""
    try:
        return jsonify({
            'success': True,
            'data': {
                'is_connected': w3.is_connected(),
                'chain_id': w3.eth.chain_id,
                'block_number': w3.eth.block_number,
                'gas_price_gwei': float(w3.from_wei(w3.eth.gas_price, 'gwei')),
                'node_url': config.get_web3_provider(),
                'is_local': config.is_local_node
            }
        })
    except Exception as e:
        return jsonify({'success': False, 'error': str(e)}), 500

@app.route('/api/test-account')
def api_test_account():
    """API:获取测试账户(仅开发环境)"""
    if not config.is_local_node and config.FLASK_ENV == 'production':
        return jsonify({'success': False, 'error': '此功能仅在开发环境可用'}), 403
    account = create_test_account()
    return jsonify({'success': True, 'data': account})

@app.route('/api/validate-address', methods=['POST'])
def api_validate_address():
    """API:验证地址格式"""
    data = request.get_json()
    address = data.get('address', '').strip()
    is_valid, message = validate_ethereum_address(address)
    return jsonify({
        'success': is_valid,
        'is_valid': is_valid,
        'message': message,
        'address': address
    })

@app.route('/api/sample-addresses')
def api_sample_addresses():
    """API:获取示例地址"""
    # 一些知名的以太坊地址(只读,用于测试)
    samples = [
        {'address': '0x0000000000000000000000000000000000000000', 'name': '零地址', 'description': '以太坊零地址,常用于代币销毁'},
        {'address': '0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B', 'name': 'Vitalik Buterin', 'description': '以太坊创始人之一'},
        {'address': '0x28C6c06298d514Db089934071355E5743bf21d60', 'name': 'Binance 14', 'description': '币安热钱包地址'}
    ]
    # 如果是本地环境,添加 Ganache 测试地址
    if config.is_local_node and w3.is_connected():
        try:
            accounts = w3.eth.accounts[:3]  # 前 3 个测试账户
            for i, addr in enumerate(accounts):
                samples.insert(0, {
                    'address': addr,
                    'name': f'测试账户 {i+1}',
                    'description': 'Ganache 本地测试账户'
                })
        except:
            pass
    return jsonify({'success': True, 'data': samples})

@app.route('/health')
def health_check():
    """健康检查端点"""
    return jsonify({
        'status': 'healthy',
        'web3_connected': w3.is_connected(),
        'flask_env': config.FLASK_ENV,
        'version': '1.0.0'
    })

# 错误处理
@app.errorhandler(404)
def not_found(error):
    return jsonify({'success': False, 'error': '资源未找到'}), 404

@app.errorhandler(500)
def internal_error(error):
    return jsonify({'success': False, 'error': '服务器内部错误'}), 500

if __name__ == '__main__':
    # 检查 Web3 连接
    if not w3.is_connected():
        print("⚠️ 警告:Web3 连接失败,部分功能可能不可用")
        print("  请检查:")
        print("  1. Ganache 是否启动(http://localhost:8545)")
        print("  2. 或配置 Infura/Alchemy 节点")
    
    # 运行应用
    print(f"\n🚀 启动 Flask Web3 应用...")
    print(f"  环境:{config.FLASK_ENV}")
    print(f"  调试模式:{config.DEBUG}")
    print(f"  节点:{config.get_web3_provider()}")
    print(f"  本地节点:{config.is_local_node}")
    print(f"\n🌐 请在浏览器中访问:")
    print(f" http://127.0.0.1:5000")
    print(f"\n📊 健康检查:")
    print(f" http://127.0.0.1:5000/health")
    print(f"\n🔄 网络状态:")
    print(f" http://127.0.0.1:5000/api/network-status")
    
    app.run(host='0.0.0.0', port=5000, debug=config.DEBUG, threaded=True)
5. 前端页面详细实现
5.1 templates/base.html - 基础模板
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Flask Web3 应用{% endblock %}</title>
    <!-- Bootstrap 5 CSS -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    <!-- Font Awesome -->
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <!-- 自定义样式 -->
    <style>
        :root {
            --primary-color: #6366f1;
            --success-color: #10b981;
            --danger-color: #ef4444;
            --warning-color: #f59e0b;
        }
        body {
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
        }
        .card {
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0,0,0,0.1);
            border: none;
            overflow: hidden;
        }
        .gradient-header {
            background: linear-gradient(90deg, var(--primary-color), #8b5cf6);
            color: white;
            padding: 2rem;
            margin-bottom: 2rem;
        }
        .btn-web3 {
            background: linear-gradient(90deg, var(--primary-color), #8b5cf6);
            color: white;
            border: none;
            padding: 0.75rem 1.5rem;
            border-radius: 10px;
            font-weight: 600;
            transition: all 0.3s;
        }
        .btn-web3:hover {
            transform: translateY(-2px);
            box-shadow: 0 5px 15px rgba(99, 102, 241, 0.4);
        }
        .eth-balance {
            font-size: 2.5rem;
            font-weight: bold;
            color: var(--success-color);
            text-shadow: 0 2px 4px rgba(0,0,0,0.1);
        }
        .address-box {
            background: #f8f9fa;
            border-radius: 10px;
            padding: 1rem;
            font-family: 'Courier New', monospace;
            word-break: break-all;
        }
        .network-badge {
            padding: 0.5rem 1rem;
            border-radius: 20px;
            font-weight: 600;
            font-size: 0.85rem;
        }
        .network-connected {
            background-color: rgba(16, 185, 129, 0.1);
            color: var(--success-color);
        }
        .network-disconnected {
            background-color: rgba(239, 68, 68, 0.1);
            color: var(--danger-color);
        }
        .loading-spinner {
            display: none;
            text-align: center;
            padding: 2rem;
        }
        .spinner {
            width: 40px;
            height: 40px;
            border: 4px solid #f3f3f3;
            border-top: 4px solid var(--primary-color);
            border-radius: 50%;
            animation: spin 1s linear infinite;
            margin: 0 auto;
        }
        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }
        .toast {
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 1050;
            min-width: 250px;
        }
        .sample-address {
            cursor: pointer;
            transition: all 0.2s;
        }
        .sample-address:hover {
            background-color: #f0f0f0;
            transform: translateX(5px);
        }
    </style>
    {% block extra_css %}{% endblock %}
</head>
<body>
    <div class="container py-5">
        <div class="row justify-content-center">
            <div class="col-lg-10 col-xl-8">
                {% block content %}{% endblock %}
            </div>
        </div>
    </div>
    <!-- Bootstrap JS Bundle -->
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
    <!-- Axios -->
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
    <!-- 自定义 JavaScript -->
    <script>
        // 显示消息
        function showMessage(message, type = 'info') {
            const toastContainer = document.getElementById('toast-container');
            if (!toastContainer) return;
            const toast = document.createElement('div');
            toast.className = `toast align-items-center text-bg-${type} border-0`;
            toast.setAttribute('role', 'alert');
            toast.setAttribute('aria-live', 'assertive');
            toast.setAttribute('aria-atomic', 'true');
            toast.innerHTML = `
                <div>
                    <div>
                        <i class="me-2">${type === 'success' ? 'check-circle' : type === 'error' ? 'exclamation-circle' : 'info-circle'}</i> ${message}
                    </div>
                    <button type="button" class="btn-close btn-close-white" data-bs-dismiss="toast" aria-label="Close"></button>
                </div>
            `;
            toastContainer.appendChild(toast);
            const bsToast = new bootstrap.Toast(toast);
            bsToast.show();
            // 自动移除
            toast.addEventListener('hidden.bs.toast', function () {
                toast.();
            });
        }

        
         () {
             container = .(containerId);
             (container) {
                container.. = ;
            }
        }

        
         () {
             container = .(containerId);
             (container) {
                container.. = ;
            }
        }

        
         () {
             (!address || address. < )  address;
             address.(, ) +  + address.(address. - );
        }

        
         () {
            navigator..(text).( {
                (, );
            }).( {
                .(, err);
                (, );
            });
        }

        
        .(,  () {
            
             (!.()) {
                 toastContainer = .();
                toastContainer. = ;
                toastContainer. = ;
                ..(toastContainer);
            }
        });
    </script>
    {% block extra_js %}{% endblock %}
</body>
</html>
5.2 templates/index.html - 主页面
{% extends "base.html" %}
{% block title %}Web3 以太坊余额查询器{% endblock %}
{% block content %}
<div class="card">
    <!-- 头部 -->
    <div class="gradient-header text-center">
        <h1 class="display-5 fw-bold"><i class="fas fa-coins me-2"></i>Web3 余额查询器</h1>
        <p class="lead mb-0">查询任何以太坊地址的 ETH 余额,支持主网和测试网络</p>
        <!-- 网络状态 -->
        <div class="mt-4">
            <span id="network-status" class="network-badge network-disconnected"><i class="fas fa-circle me-1"></i>连接中...</span>
            <span id="block-number" class="badge bg-secondary ms-2">区块:<span id="current-block">-</span></span>
            <span id="gas-price" class="badge bg-info ms-2">Gas: <span id="current-gas">-</span> Gwei</span>
        </div>
    </div>
    <div class="card-body p-4">
        <!-- 查询表单 -->
        <div class="row">
            <div class="col-md-8">
                <div class="mb-3">
                    <label for="addressInput" class="form-label fw-bold"><i class="fas fa-wallet me-1"></i>以太坊地址</label>
                    <input type="text" class="form-control form-control-lg" id="addressInput" placeholder="例如:0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B" value="{{ default_address or '' }}">
                    <div class="form-text">请输入完整的以太坊地址(0x 开头,42 个字符)</div>
                </div>
            </div>
            <div class="col-md-4 d-flex align-items-end">
                <button onclick="queryBalance()" class="btn btn-web3 w-100 py-3"><i class="fas fa-search me-2"></i>查询余额</button>
            </div>
        </div>
        <!-- 验证状态 -->
        <div id="validationResult" class="mb-3" style="display: none;">
            <span id="validationIcon" class="me-2"></span><span id="validationMessage"></span>
        </div>
        <!-- 加载动画 -->
        <div id="loadingSpinner" class="loading-spinner">
            <div class="spinner"></div>
            <p class="mt-3 text-muted">正在查询区块链数据...</p>
        </div>
        <!-- 结果显示 -->
        <div id="resultContainer" class="mt-4" style="display: none;">
            <div class="card border-success">
                <div class="card-header bg-success text-white"><i class="fas fa-check-circle me-2"></i>查询结果</div>
                <div class="card-body">
                    <div class="row">
                        <div class="col-md-6">
                            <h6 class="text-muted">地址</h6>
                            <div class="address-box mb-3">
                                <span id="resultAddress"></span>
                                <button onclick="copyAddress()" class="btn btn-sm btn-outline-secondary float-end"><i class="fas fa-copy"></i></button>
                            </div>
                        </div>
                        <div class="col-md-6">
                            <h6 class="text-muted">网络</h6>
                            <div class="mb-3">
                                <span id="resultNetwork" class="badge bg-primary"></span>
                                <span id="resultChainId" class="badge bg-secondary ms-1"></span>
                            </div>
                        </div>
                    </div>
                    <div class="text-center py-4">
                        <h6 class="text-muted mb-2">ETH 余额</h6>
                        <div class="eth-balance" id="resultBalance">0 ETH</div>
                        <small class="text-muted" id="resultWei"></small>
                    </div>
                    <div class="row mt-4">
                        <div class="col-md-6">
                            <h6 class="text-muted">区块高度</h6>
                            <p class="h5" id="resultBlock">-</p>
                        </div>
                        <div class="col-md-6">
                            <h6 class="text-muted">更新时间</h6>
                            <p class="h5" id="resultTimestamp">-</p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        <!-- 示例地址 -->
        <div class="mt-5">
            <h5 class="mb-3"><i class="fas fa-list me-2"></i>示例地址</h5>
            <div id="sampleAddresses" class="list-group">
                <!-- 动态加载示例地址 -->
            </div>
        </div>
        <!-- 测试账户(仅本地环境) -->
        {% if network.is_local %}
        <div class="mt-5">
            <div class="card border-warning">
                <div class="card-header bg-warning"><i class="fas fa-flask me-2"></i>开发环境工具</div>
                <div class="card-body">
                    <p class="card-text">您正在使用本地测试网络(Ganache)。这里有一些测试工具:</p>
                    <div class="row">
                        <div class="col-md-6">
                            <button onclick="getTestAccount()" class="btn btn-outline-warning w-100 mb-2"><i class="fas fa-user-plus me-2"></i>获取测试账户</button>
                        </div>
                        <div class="col-md-6">
                            <button onclick="testConnection()" class="btn btn-outline-info w-100 mb-2"><i class="fas fa-plug me-2"></i>测试连接</button>
                        </div>
                    </div>
                    <div id="testAccountInfo" class="mt-3" style="display: none;">
                        <div class="alert alert-info">
                            <h6>测试账户信息</h6>
                            <p class="mb-1"><strong>地址:</strong><span id="testAddress"></span></p>
                            <p class="mb-1"><strong>私钥:</strong><span id="testPrivateKey"></span></p>
                            <p class="mb-0"><strong>余额:</strong><span id="testBalance"></span></p>
                        </div>
                    </div>
                </div>
            </div>
        </div>
        {% endif %}
        <!-- 说明 -->
        <div class="mt-5">
            <div class="card">
                <div class="card-header"><i class="fas fa-info-circle me-2"></i>说明</div>
                <div class="card-body">
                    <ul>
                        <li>此工具可以查询任何以太坊地址的 ETH 余额</li>
                        <li>数据直接从区块链节点获取,实时更新</li>
                        <li>支持以太坊主网、测试网和本地开发网络</li>
                        <li>不存储任何用户数据,所有查询均为匿名</li>
                        <li>如需查询 ERC20 代币余额,请使用专业的区块链浏览器</li>
                    </ul>
                </div>
            </div>
        </div>
    </div>
</div>
{% endblock %}
{% block extra_js %}
<script>
// 全局状态
let currentAddress = '';

// 页面加载完成
document.addEventListener('DOMContentLoaded', function () {
    // 初始化网络状态
    updateNetworkStatus();
    // 加载示例地址
    loadSampleAddresses();
    // 实时验证地址输入
    const addressInput = document.getElementById('addressInput');
    addressInput.addEventListener('input', function () {
        validateAddressInput(this.value);
    });
    // 按回车键查询
    addressInput.addEventListener('keypress', function (e) {
        if (e.key === 'Enter') {
            queryBalance();
        }
    });
    // 如果有默认地址,自动验证
    if (addressInput.value) {
        validateAddressInput(addressInput.value);
    }
});

// 更新网络状态
async function updateNetworkStatus() {
    try {
        const response = await axios.get('/api/network-status');
        if (response.data.) {
             data = response..;
            
             statusBadge = .();
            statusBadge. = data. ?  : ;
            statusBadge. = data. ?  : ;
            
             (data.) {
                .(). = data.;
            }
            
             (data.) {
                .(). = data..();
            }
            
             (data.) {
                statusBadge. += ;
            }
        }
    }  (error) {
        .(, error);
    }
    
    (updateNetworkStatus, );
}


  () {
     validationResult = .();
     validationIcon = .();
     validationMessage = .();
     (!address || address.() === ) {
        validationResult.. = ;
         ;
    }
     {
         response =  axios.(, { : address });
         data = response.;
        validationResult.. = ;
         (data.) {
            validationResult. = ;
            validationIcon. = ;
            validationMessage. = ;
             ;
        }  {
            validationResult. = ;
            validationIcon. = ;
            validationMessage. =  + data.;
             ;
        }
    }  (error) {
        .(, error);
         ;
    }
}


  () {
     address = .()..();
     (!address) {
        (, );
        ;
    }
    
     isValid =  (address);
     (!isValid) {
        (, );
        ;
    }
    currentAddress = address;
    
    ();
    .().. = ;
     {
         response =  axios.(, { : address });
         (response..) {
            (response..);
            (, );
        }  {
            ( + response.., );
        }
    }  (error) {
        .(, error);
         errorMsg = error.?.?. || error.;
        ( + errorMsg, );
    }  {
        ();
    }
}


 () {
     resultContainer = .();
    
    .(). = data.;
    
    .(). = data.;
    .(). = data. + ;
    
     networkName = (data.);
    .(). = networkName;
    .(). =  + data.;
    
    .(). = data.;
    
     now =  ();
    .(). = now.() +  + now.();
    
    resultContainer.. = ;
    
    resultContainer.({ : , :  });
}


 () {
     networks = {
        : ,
        : ,
        : ,
        : ,
        : ,
        : ,
        : 
    };
     networks[chainId] || ;
}


  () {
     {
         response =  axios.();
         (response..) {
             container = .();
            container. = ;
            response...( {
                 addressItem = .();
                addressItem. = ;
                addressItem. = ;
                addressItem. = ;
                addressItem.(,  () {
                    .(). = item.;
                    (item.);
                    
                    
                });
                container.(addressItem);
            });
        }
    }  (error) {
        .(, error);
    }
}


 () {
     (currentAddress) {
        (currentAddress);
    }  {
        (, );
    }
}


  () {
     {
         response =  axios.();
         (response..) {
             account = response..;
             container = .();
            .(). = account.;
            .(). = account.;
            .(). = account. + ;
            container.. = ;
            
            .(). = account.;
            (account.);
            (, );
        }  {
            (response.., );
        }
    }  (error) {
        .(, error);
        (, );
    }
}


  () {
     {
         response =  axios.();
         (response.. && response...) {
            (, );
        }  {
            (, );
        }
    }  (error) {
        ( + error., );
    }
}
</script>
{% endblock %}
6. 运行应用
6.1 启动本地以太坊节点(Ganache)
# 打开一个新的终端窗口
ganache --server.port 8545 --wallet.totalAccounts 10 --wallet.defaultBalance 100

输出应该类似:

Ganache CLI v6.12.2 (ganache-core: 2.13.2)
Available Accounts ==================
(0) 0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1 (100 ETH)
(1) 0xFFcf8FDEE72ac11b5c542428B35EEF5769C409f0 (100 ETH)
...
Private Keys ==================
(0) 0x4f3edf983ac636a65a842ce7c78d9aa706d3b113bce9c46f30d7d21715b23b1d
...
Listening on 127.0.0.1:8545
6.2 配置环境变量
# 复制环境变量示例文件
cp .env.example .env

# 编辑 .env 文件(可选,如果使用 Ganache 可以不填 INFURA_URL)
# 如果你有 Infura 账号,可以填写 INFURA_URL
# 否则留空,系统会自动使用本地 Ganache 节点
6.3 启动 Flask 应用
# 在项目根目录运行
python app.py

输出应该类似:

正在连接 Web3 节点:http://localhost:8545
✅ 成功连接到以太坊节点
网络 ID: 1337
最新区块:0
节点:http://localhost:8545
🚀 启动 Flask Web3 应用...
环境:development
调试模式:True
节点:http://localhost:8545
本地节点:True
🌐 请在浏览器中访问:
 http://127.0.0.1:5000
📊 健康检查:
 http://127.0.0.1:5000/health
🔄 网络状态:
 http://127.0.0.1:5000/api/network-status
* Serving Flask app 'app'
* Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment.
* Running on all addresses (0.0.0.0)
* Running on http://127.0.0.1:5000
* Running on http://192.168.x.x:5000
6.4 测试应用
  1. 打开浏览器访问 http://127.0.0.1:5000
  2. 在地址输入框中输入一个以太坊地址,例如:
    • Ganache 测试地址:0x90F8bf6A479f320ead074411a4B0e7944Ea8c9C1
    • 以太坊创始人地址:0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B
  3. 点击"查询余额"按钮
  4. 查看查询结果
7. 扩展功能示例
7.1 添加 ERC20 代币余额查询

创建 utils/web3_utils.py:

from web3 import Web3
from eth_abi import decode_abi
import json

# ERC20 ABI (简化版)
ERC20_ABI = json.loads('''
[
    {
        "constant": true,
        "inputs": [{"name": "_owner", "type": "address"}],
        "name": "balanceOf",
        "outputs": [{"name": "balance", "type": "uint256"}],
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "decimals",
        "outputs": [{"name": "", "type": "uint8"}],
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "symbol",
        "outputs": [{"name": "", "type": "string"}],
        "type": "function"
    },
    {
        "constant": true,
        "inputs": [],
        "name": "name",
        "outputs": [{"name": "", "type": "string"}],
        "type": "function"
    }
]
''')

def get_erc20_balance(w3, token_address, wallet_address):
    """获取 ERC20 代币余额"""
    try:
        # 创建合约实例
        contract = w3.eth.contract(
            address=Web3.to_checksum_address(token_address),
            abi=ERC20_ABI
        )
        # 获取余额
        balance = contract.functions.balanceOf(
            Web3.to_checksum_address(wallet_address)
        ).call()
        # 获取代币信息
        decimals = contract.functions.decimals().call()
        symbol = contract.functions.symbol().call()
        name = contract.functions.name().call()
        # 计算实际余额
        actual_balance = balance / (10 ** decimals)
        return {
            'success': True,
            'balance': str(actual_balance),
            'balance_raw': str(balance),
            'symbol': symbol,
            'name': name,
            'decimals': decimals,
            'token_address': token_address
        }
    except Exception as e:
        return {'success': False, 'error': str(e)}

# 常用 ERC20 代币地址
COMMON_TOKENS = {
    'USDT': '0xdAC17F958D2ee523a2206206994597C13D831ec7',
    'USDC': '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48',
    'DAI': '0x6B175474E89094C44Da98b954EedeAC495271d0F',
    'LINK': '0x514910771AF9Ca656af840dff83E8264EcF986CA',
    'UNI': '0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984',
    'AAVE': '0x7Fc66500c84A76Ad7e9c93437bFc5Ac33E2DDaE9'
}
7.2 添加交易历史查询

在 app.py 中添加:

@app.route('/api/transactions', methods=['POST'])
def api_get_transactions():
    """获取交易历史(使用 Etherscan API)"""
    data = request.get_json()
    address = data.get('address', '').strip()
    if not address:
        return jsonify({'success': False, 'error': '请提供地址'}), 400
    
    # 验证地址
    is_valid, message = validate_ethereum_address(address)
    if not is_valid:
        return jsonify({'success': False, 'error': message}), 400
    
    # 如果有 Etherscan API 密钥,可以调用其 API
    if config.ETHERSCAN_API_KEY:
        try:
            import requests
            url = f"https://api.etherscan.io/api"
            params = {
                'module': 'account',
                'action': 'txlist',
                'address': address,
                'startblock': 0,
                'endblock': 99999999,
                'sort': 'desc',
                'apikey': config.ETHERSCAN_API_KEY
            }
            response = requests.get(url, params=params)
            data = response.json()
            if data['status'] == '1':
                # 处理交易数据
                transactions = data['result'][:10]  # 只返回最近 10 笔
                return jsonify({
                    'success': True,
                    'data': {
                        'address': address,
                        'transaction_count': len(transactions),
                        'transactions': transactions
                    }
                })
            else:
                return jsonify({'success': False, 'error': data.get('message', '获取交易失败')}), 500
        except Exception as e:
            return jsonify({'success': False, 'error': f'获取交易历史失败:{str(e)}'}), 500
    else:
        return jsonify({'success': False, 'error': '未配置 Etherscan API 密钥'}), 501
8. 常见问题解决
问题 1:无法连接到节点

症状:显示"连接失败"或"查询失败"
解决方案:

  1. 检查 Ganache 是否运行:访问 http://localhost:8545
  2. 检查端口是否被占用:netstat -ano | findstr :8545
  3. 重启 Ganache:ganache --server.port 8545
问题 2:Python 包安装失败

症状:pip install 报错
解决方案:

# 升级 pip
python -m pip install --upgrade pip

# 使用国内镜像源
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple flask web3

# 或使用 conda
conda install -c conda-forge web3
问题 3:地址格式验证失败

症状:总是提示"无效地址"
解决方案:

  1. 确保地址以 0x 开头
  2. 确保地址长度为 42 个字符
  3. 使用 Checksum 地址(区分大小写)
问题 4:余额显示为 0

症状:查询真实地址显示 0 ETH
解决方案:

  1. 检查是否连接到正确的网络(主网 vs 测试网)
  2. 确认地址是否正确
  3. 尝试查询示例地址
9. 学习资源
  1. 官方文档:
    • Flask 官方文档:https://flask.palletsprojects.com/
    • web3.py 文档:https://web3py.readthedocs.io/
    • 以太坊官方:https://ethereum.org/zh/developers/
  2. 测试资源:
    • 以太坊测试网水龙头:https://faucet.paradigm.xyz/
    • Goerli 测试网:https://goerli-faucet.pk910.de/
    • Sepolia 测试网:https://sepoliafaucet.com/
  3. 开发工具:
    • Remix IDE:https://remix.ethereum.org/
    • Hardhat:https://hardhat.org/
    • Truffle Suite:https://trufflesuite.com/
10. 下一步建议
  1. 添加更多功能:
    • ERC20 代币余额查询
    • NFT 持有查询
    • 交易发送功能
    • 智能合约交互
  2. 改进安全性:
    • 添加 API 限流
    • 实现 JWT 认证
    • 添加 CORS 配置
    • 使用环境变量管理敏感信息
  3. 部署到生产环境:
    • 使用 Gunicorn 或 uWSGI
    • 配置 Nginx 反向代理
    • 使用 HTTPS
    • 配置数据库存储用户数据

这个完整示例提供了从环境配置到实际运行的所有步骤。你可以基于此项目进一步开发更复杂的 Web3 应用,如 DeFi 仪表板、NFT 市场或 DAO 投票系统。

remove
// 显示加载状态
function
showLoading
containerId
const
document
getElementById
if
style
display
'block'
// 隐藏加载状态
function
hideLoading
containerId
const
document
getElementById
if
style
display
'none'
// 格式化地址
function
formatAddress
address
if
length
10
return
return
substring
0
6
'...'
substring
length
4
// 复制到剪贴板
function
copyToClipboard
text
clipboard
writeText
then
() =>
showMessage
'已复制到剪贴板'
'success'
catch
err =>
console
error
'复制失败:'
showMessage
'复制失败'
'error'
// 页面加载完成
document
addEventListener
'DOMContentLoaded'
function
// 创建 Toast 容器
if
document
getElementById
'toast-container'
const
document
createElement
'div'
id
'toast-container'
className
'toast-container position-fixed top-0 end-0 p-3'
document
body
appendChild
success
const
data
data
// 更新状态指示器
const
document
getElementById
'network-status'
className
is_connected
'network-badge network-connected'
'network-badge network-disconnected'
innerHTML
is_connected
'<i class="fas fa-circle"></i>已连接'
'<i class="fas fa-circle"></i>未连接'
// 更新区块信息
if
block_number
document
getElementById
'current-block'
textContent
block_number
// 更新 Gas 价格
if
gas_price_gwei
document
getElementById
'current-gas'
textContent
gas_price_gwei
toFixed
1
// 如果是本地网络,显示标记
if
is_local
innerHTML
' (本地)'
catch
console
error
'获取网络状态失败:'
// 每 10 秒更新一次
setTimeout
10000
// 验证地址输入
async
function
validateAddressInput
address
const
document
getElementById
'validationResult'
const
document
getElementById
'validationIcon'
const
document
getElementById
'validationMessage'
if
trim
''
style
display
'none'
return
false
try
const
await
post
'/api/validate-address'
address
const
data
style
display
'block'
if
is_valid
className
'alert alert-success'
innerHTML
'<i class="fas fa-check"></i>'
textContent
'✓ 地址格式正确'
return
true
else
className
'alert alert-danger'
innerHTML
'<i class="fas fa-times"></i>'
textContent
'✗ '
message
return
false
catch
console
error
'验证地址失败:'
return
false
// 查询余额
async
function
queryBalance
const
document
getElementById
'addressInput'
value
trim
if
showMessage
'请输入以太坊地址'
'warning'
return
// 验证地址格式
const
await
validateAddressInput
if
showMessage
'地址格式不正确,请检查后重试'
'error'
return
// 显示加载动画
showLoading
'loadingSpinner'
document
getElementById
'resultContainer'
style
display
'none'
try
const
await
post
'/api/balance'
address
if
data
success
displayResult
data
data
showMessage
'查询成功'
'success'
else
showMessage
'查询失败:'
data
error
'error'
catch
console
error
'查询错误:'
const
response
data
error
message
showMessage
'查询失败:'
'error'
finally
hideLoading
'loadingSpinner'
// 显示查询结果
function
displayResult
data
const
document
getElementById
'resultContainer'
// 更新地址
document
getElementById
'resultAddress'
textContent
address
// 更新余额
document
getElementById
'resultBalance'
textContent
balance_display
document
getElementById
'resultWei'
textContent
balance_wei
' Wei'
// 更新网络信息
const
getNetworkName
network
document
getElementById
'resultNetwork'
textContent
document
getElementById
'resultChainId'
textContent
'Chain ID: '
network
// 更新区块信息
document
getElementById
'resultBlock'
textContent
block_number
// 更新时间戳
const
new
Date
document
getElementById
'resultTimestamp'
textContent
toLocaleTimeString
' '
toLocaleDateString
// 显示结果
style
display
'block'
// 滚动到结果
scrollIntoView
behavior
'smooth'
block
'nearest'
// 获取网络名称
function
getNetworkName
chainId
const
1
'以太坊主网'
3
'Ropsten 测试网'
4
'Rinkeby 测试网'
5
'Goerli 测试网'
42
'Kovan 测试网'
1337
'Ganache 本地'
5777
'Ganache 本地'
return
`未知网络 (${chainId})`
// 加载示例地址
async
function
loadSampleAddresses
try
const
await
get
'/api/sample-addresses'
if
data
success
const
document
getElementById
'sampleAddresses'
innerHTML
''
data
data
forEach
item =>
const
document
createElement
'a'
className
'list-group-item list-group-item-action sample-address'
href
'javascript:void(0)'
innerHTML
` <div> <div> <h6>${item.name}</h6> <small>${formatAddress(item.address)}</small> </div> <small><i class="fas fa-link"></i></small> </div> <p>${item.description}</p> `
addEventListener
'click'
function
document
getElementById
'addressInput'
value
address
validateAddressInput
address
// 自动查询(可选)
// queryBalance();
appendChild
catch
console
error
'加载示例地址失败:'
// 复制地址
function
copyAddress
if
copyToClipboard
else
showMessage
'没有可复制的地址'
'warning'
// 获取测试账户(仅开发环境)
async
function
getTestAccount
try
const
await
get
'/api/test-account'
if
data
success
const
data
data
const
document
getElementById
'testAccountInfo'
document
getElementById
'testAddress'
textContent
address
document
getElementById
'testPrivateKey'
textContent
private_key
document
getElementById
'testBalance'
textContent
balance
' ETH'
style
display
'block'
// 自动填充地址
document
getElementById
'addressInput'
value
address
validateAddressInput
address
showMessage
'测试账户已获取'
'success'
else
showMessage
data
error
'error'
catch
console
error
'获取测试账户失败:'
showMessage
'获取测试账户失败'
'error'
// 测试连接
async
function
testConnection
try
const
await
get
'/api/network-status'
if
data
success
data
data
is_connected
showMessage
'✅ 连接正常!节点运行中'
'success'
else
showMessage
'❌ 连接失败,请检查节点配置'
'error'
catch
showMessage
'❌ 连接测试失败:'
message
'error'
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 免费图片视频在线生成30秒,将你的创意变成现实开始设计
  • X/Twitter免费视频下载器免登陆无限额度免费视频解析下载了解详情
  • 100+免费在线小游戏爽一把
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 通义万相 2.1 多模态生成技术解析与核心优势
  • DGX Spark 部署 vLLM 与 Open WebUI 运行 Qwen3-Coder-Next-FP8(CUDA 13.0)
  • DGX Spark 部署 vLLM + Open WebUI 运行 Qwen3-Coder-Next-FP8 (CUDA 13.0)
  • Android Handler 与 Binder 底层原理深度解析
  • 植物大战僵尸网页版前端页面结构与样式解析
  • Web 基础:动静态 Web、URL 与 HTTP 协议
  • Coze Skills + OpenClaw 实战:构建 AI 智能体自动化工作流
  • FlowMiner: 基于流关联挖掘的加密流量分类模型
  • Photoshop 中集成 Stable Diffusion 的 5 个核心技巧
  • Google Gemini 国内使用指南与多模态功能解析
  • Stable Diffusion 绘画实战:云端部署与提示词技巧
  • Linux 线程概念详解
  • Webhook 核心原理及 Langflow 自动化工作流实践
  • 鸿蒙系统若不再兼容安卓,对行业生态有何影响?
  • Kiro AI 助手完整使用指南
  • Clawith 企业级多智能体框架深度解析
  • 31 岁转行软件测试:一位 34 岁从业者的经历与感悟
  • AI 绘画报错:模型文件缺失与配置修复
  • 无线联邦学习:保护隐私下的 AI 协同进化
  • JavaAI 实战指南:老项目重构与全栈开发效率提升

相关免费在线工具

  • curl 转代码

    解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online