{"content":{"title":"使用 Viem 实时监听最新区块和 USDT 转账流水","body":"要求：\r\n\r\n1、监听新区块，打印区块高度和区块哈稀值\r\n\r\n如`20329474 (0x662022f0...cc0dea26)`\r\n\r\n2、实时采集并打印最新 USDT Token（0xdac17f958d2ee523a2206206994597c13d831ec7） Transfer 流水\r\n如`在 20329474 区块 0xe52ff...7ddcf 交易中从 0x65eDc7E1...5518AeF12 转账 50 USDT 到 0xa2D30559...30348f472`\r\n\r\n使用 Viem 编写 TypeScript 脚本来监听最新区块和 USDT 转账流水，并使用 React 在网页中展示结果。以下是详细的实现步骤：\r\n\r\n### 步骤 1: 创建项目文件夹和初始化项目\r\n\r\n在你的终端中运行以下命令：\r\n\r\n```\r\nmkdir viem_USDTTransfer\r\ncd viem_USDTTransfer\r\nnpm init -y\r\n```\r\n\r\n### 步骤 2: 安装依赖\r\n\r\n安装 Viem 以及 TypeScript 和 React 等必要的依赖：\r\n\r\n```\r\npnpm install viem react react-dom @types/react @types/react-dom\r\n\r\nnpm install --save-dev typescript ts-node @types/node @types/react @types/react-dom --legacy-peer-deps\r\n```\r\n\r\n### 步骤 3: 创建 TypeScript 配置文件\r\n\r\n在项目根目录下创建 `tsconfig.json` 文件，内容如下：\r\n\r\n```\r\n{\r\n  \"compilerOptions\": {\r\n    \"target\": \"ES2020\", // 更新为 ES2020\r\n    \"module\": \"commonjs\",\r\n    \"strict\": true,\r\n    \"esModuleInterop\": true,\r\n    \"skipLibCheck\": true,\r\n    \"forceConsistentCasingInFileNames\": true,\r\n    \"outDir\": \"./dist\",\r\n    \"jsx\": \"react\",\r\n    \"jsxFactory\": \"React.createElement\",\r\n    \"jsxFragmentFactory\": \"React.Fragment\"\r\n  },\r\n  \"include\": [\"src/**/*.tsx\", \"src/**/*.ts\"],\r\n  \"exclude\": [\"node_modules\", \"**/*.spec.ts\"]\r\n}\r\n\r\n```\r\n\r\n### 步骤 4: 编写 TypeScript 脚本\r\n\r\n在 `viem_USDTTransfer` 文件夹中创建 `src` 文件夹，然后在 `src` 文件夹中创建 `index.tsx` 文件，内容如下：\r\n\r\n```\r\nimport React, { useState, useEffect } from 'react';\r\nimport ReactDOM from 'react-dom';\r\nimport { createPublicClient, http, parseAbiItem, formatUnits } from 'viem';\r\nimport { mainnet } from 'viem/chains';\r\n\r\n// USDT 合约地址\r\nconst USDT_CONTRACT_ADDRESS = '0xdac17f958d2ee523a2206206994597c13d831ec7';\r\n\r\n// Transfer 事件的 ABI 格式\r\nconst TRANSFER_EVENT_ABI = parseAbiItem('event Transfer(address indexed from, address indexed to, uint256 value)');\r\n\r\nconst App = () => {\r\n    const [blockHeight, setBlockHeight] = useState<number | null>(null);\r\n    const [blockHash, setBlockHash] = useState<string | null>(null);\r\n    const [transfers, setTransfers] = useState<any[]>([]);\r\n\r\n    useEffect(() => {\r\n        const client = createPublicClient({\r\n            chain: mainnet,\r\n            transport: http('https://rpc.flashbots.net'),\r\n        });\r\n\r\n        const fetchBlockData = async () => {\r\n            const latestBlock = await client.getBlock({ blockTag: 'latest' });\r\n            setBlockHeight(Number(latestBlock.number));\r\n            setBlockHash(latestBlock.hash);\r\n        };\r\n\r\n        const subscribeToEvents = () => {\r\n            client.watchBlockNumber({\r\n                onBlockNumber: async (blockNumber) => {\r\n                    if (blockNumber === undefined) {\r\n                        setBlockHeight(null);\r\n                        return;\r\n                    }\r\n\r\n                    const safeBlockNumber = blockNumber !== undefined ? BigInt(blockNumber) : 0n; // 提供默认值\r\n                    console.log(safeBlockNumber, \"Safe Block Number\");\r\n                    setBlockHeight(Number(safeBlockNumber));\r\n                    \r\n                    const fromBlock = safeBlockNumber - 100n;\r\n                    const toBlock = safeBlockNumber;\r\n\r\n                    const logs = await client.getLogs({\r\n                        address: USDT_CONTRACT_ADDRESS,\r\n                        event: TRANSFER_EVENT_ABI,\r\n                        fromBlock,\r\n                        toBlock,\r\n                    });\r\n\r\n                    console.log(logs, \"Logs\");\r\n\r\n                    const newTransfers = logs.map(log => {\r\n                        const { from, to, value } = log.args || {};\r\n                        console.log(log, \"Log\");\r\n                        return {\r\n                            blockNumber: log.blockNumber.toString(), // 转换为字符串\r\n                            transactionHash: log.transactionHash,\r\n                            from,\r\n                            to,\r\n                            value: value ? Number(formatUnits(value, 6)).toFixed(5) : '0.00000' // 处理 value 可能为 undefined 的情况\r\n                        };\r\n                    });\r\n                    console.log(newTransfers, \"New Transfers\");\r\n                    setTransfers(newTransfers);\r\n                },\r\n            });\r\n        };\r\n\r\n        fetchBlockData();\r\n        subscribeToEvents();\r\n    }, []);\r\n\r\n    return (\r\n        <div>\r\n            <h1>最新区块信息</h1>\r\n            <p>区块高度: {blockHeight}</p>\r\n            <p>区块哈希值: {blockHash}</p>\r\n            <h2>最新 USDT 转账记录</h2>\r\n            {transfers.map((transfer, index) => (\r\n                <div key={index}>\r\n                    <p>在 {transfer.blockNumber} 区块 {transfer.transactionHash} 交易中从 {transfer.from} 转账 {transfer.value} USDT 到 {transfer.to}</p>\r\n                </div>\r\n            ))}\r\n        </div>\r\n    );\r\n};\r\n\r\nReactDOM.render(<App />, document.getElementById('root'));\r\n\r\n```\r\n\r\n### 步骤 5: 设置 HTML 模板\r\n\r\n在项目根目录下创建 `public` 文件夹，然后在 `public` 文件夹中创建 `index.html` 文件，内容如下：\r\n\r\n```\r\n<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>USDT Transfer</title>\r\n</head>\r\n<body>\r\n    <div id=\"root\"></div>\r\n    <script src=\"index.js\"></script>\r\n</body>\r\n</html>\r\n\r\n```\r\n\r\n### 步骤 6: 编译并运行项目\r\n\r\n安装\r\n\r\n```\r\nnpm install webpack webpack-cli ts-loader --save-dev\r\n```\r\n\r\n在项目根目录下创建 `webpack.config.js` 文件，用于配置 Webpack：\r\n\r\n```\r\nconst path = require('path');\r\nconst HtmlWebpackPlugin = require('html-webpack-plugin');\r\n\r\nmodule.exports = {\r\n    entry: './src/index.tsx',\r\n    output: {\r\n        path: path.resolve(__dirname, 'dist'),\r\n        filename: 'index.js',\r\n    },\r\n    resolve: {\r\n        extensions: ['.tsx', '.ts', '.js'],\r\n    },\r\n    module: {\r\n        rules: [\r\n            {\r\n                test: /\\.tsx?$/,\r\n                use: 'ts-loader',\r\n                exclude: /node_modules/,\r\n            },\r\n        ],\r\n    },\r\n    devServer: {\r\n        static: {\r\n            directory: path.join(__dirname, 'public'),\r\n        },\r\n        compress: true,\r\n        port: 9000,\r\n    },\r\n    plugins: [\r\n        new HtmlWebpackPlugin({\r\n            template: './public/index.html',\r\n        }),\r\n    ],\r\n};\r\n\r\n```\r\n\r\n然后运行以下命令来编译并启动开发服务器：\r\n\r\n```\r\nnpx webpack serve\r\n```\r\n\r\n打开浏览器访问 `http://localhost:9000`，你应该能够看到最新的区块高度和 USDT 转账记录。\r\n\r\n\r\n![image-20240718211830725.png](https://img.learnblockchain.cn/attachments/2024/07/0T4QMxIm669916a1f100e.png)"},"author":{"user":"https://learnblockchain.cn/people/2184","address":"0x531247BbA4d32ED9D870bc3aBe71A2B9ce911e69"},"history":"bafkreifuph2ae7vbgzpqz27g2pt7t6ilanr2pwvxkbdza653n5x635fj6u","timestamp":1721308846,"version":1}