实现 ERC20 和 Ether 转账监听

实现ERC20 和 Ether 转账监听

ERC20 转账监听

  • 所需环境和工具

    1. geth 全节点
    2. web3
  • 对 ERC20 的转账监听有如下两个方案

    1. 将块高作为参数,调用 eth_getLogs 来获取 ERC20 的转账事件
    2. 使用 web3.eth.subscribe("logs") 实时监听 ERC20 转账事件

如下提供第一种方案。

const Web3 = require('web3');
const Decimal = require('decimal.js');
const abiDecoder = require('abi-decoder');

const YOUR_ALCHEMYAPI_API_KEY = 'YOUR_API_KEY';

const EVENT_TRANSFER = '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef';
const ERC20_TRANSFER_EVENT_ABI = [{
    "anonymous": false,
    "inputs": [
        {
            "indexed": true,
            "name": "src",
            "type": "address"
        },
        {
            "indexed": true,
            "name": "dst",
            "type": "address"
        },
        {
            "indexed": false,
            "name": "wad",
            "type": "uint256"
        }
    ],
    "name": "Transfer",
    "type": "event"
}];
abiDecoder.addABI(ERC20_TRANSFER_EVENT_ABI);

const web3 = new Web3(`wss://eth-mainnet.ws.alchemyapi.io/v2/${YOUR_ALCHEMYAPI_API_KEY}`);

/**
 * 取出当前区块上所有的 ERC20 转账事件
 * @param number
 * @returns {Promise.}
 */
async function getErc20TransfersByBlock(number) {
    const number = 1000000;
    const blockLogs = await web3.eth.getPastLogs({
        fromBlock: number,
        toBlock: number,
        address: null,
        topics: [EVENT_TRANSFER]
    });

    const transfers = [];
    for (const log of blockLogs) {
        // todo get erc20 decimals
        const DECIMALS_OF_ERC20 = null;
        const decodeData = abiDecoder.decodeLogs([log])[0];
        const from = decodeData.events[0].value;
        const to = decodeData.events[1].value;
        const raw_value = new Decimal(decodeData.events[2].value);
        const decimal = Decimal.pow(10, DECIMALS_OF_ERC20);
        const value = raw_value.div(decimal);
        console.debug(`from=${from} to=${to} value=${value} contract=${log.address}`);
        transfers.push({from, to, value, contract: log.address});
    }
    return transfers;
}

(async () => {
    while (true) {
        const latest = await this.web3.eth.getBlock('latest', true);
        const transfers = await getErc20TransfersByBlock(latest);
        // ...
    }
})();
  • YOUR_ALCHEMYAPI_API_KEY : https://alchemyapi.io 是一个提供在线 geth 的平台,你可以通过注册获取到 KEY 连接 geth。自建 geth 节点需要支付相当的成本,尤其是 geth归档全节点,硬盘容量需要 6TB 以上(截止 2021年3月)。
  • DECIMALS_OF_ERC20 : ERC20 代币的小数位数,可以自己维护在数据库,也可以实时从链上调用获取。

Ether 转账监听

  • 所需环境和工具
    1. geth 归档全节点
    2. web3

Ether 转账有四类,一种是 external transaction,也就是普通的 Ether 转账。另外一种是 internal transaction,是通过智能合约进行的 Ether 转账。internal transaction 需要通过调用 debug 或者 trace 模块来获取合约执行的详细过程。剩余两类:coinbase 挖矿奖励和 selfdestruct 合约自毁。

特别注意,如果想要在自己的 geth 或者 openethereum 节点上调用 debugtrace 模块,必须将节点设置为 归档全节点 模式,即 syncmode=fullgcmode=archive归档全节点需要至少 6TB 的磁盘空间(截止2021年3月)。

获取 internal transaction 需要归档全节点,可以考虑使用 infura.io 或者 alchemyapi.io ,支付较少的成本即可获取归档全节点的全部能力。

如下是通过 openethereumtrace_transaction 来获取 internal transaction 的例子。

const YOUR_ALCHEMYAPI_API_KEY = 'YOUR_API_KEY';
const web3 = new Web3(`wss://eth-mainnet.ws.alchemyapi.io/v2/${YOUR_ALCHEMYAPI_API_KEY}`);

web3.currentProvider.send({
        method: "trace_transaction",
        params: ['0x42b6ab2be4975708f70575fc7953d11692c84a4a19c5c8eec65c582870a4e85e', {
            disableStack: true,
            disableMemory: true,
            disableStorage: true
        }],
        jsonrpc: "2.0",
        id: "2"
    }, (err, res) => {
        console.info(`${JSON.stringify(res)}`);
    });

返回结果如下(部分),其中 action.value 大于 0 即可认为是 fromto 转账的 Ether 金额。

调用 debug_traceTransaction 前,必须确认 receipt.statustrue

{
  "jsonrpc": "2.0",
  "result": [
    {
      "action": {
        "callType": "call",
        "from": "0xc1563bdf57bdb990c89070aa72cda57fe8d6913d",
        "gas": "0x5f2ad",
        "input": "0x36118b52ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000c1563bdf57bdb990c89070aa72cda57fe8d6913d",
        "to": "0xdcd33426ba191383f1c9b431a342498fdac73488",
        "value": "0x0"
      },
      "blockHash": "0x197f158db6b8263e7d518a4f8f5f43d689e76ca1921882dae75c4fc050b593d6",
      "blockNumber": 11962253,
      "result": {
        "gasUsed": "0x557e2",
        "output": "0x"
      },
      "subtraces": 5,
      "traceAddress": [],
      "transactionHash": "0xffdb1a501fdc51cc308cec9f60cadc6453b221ceec352b804b981da25814f1b9",
      "transactionPosition": 124,
      "type": "call"
    },
    {
      "action": {
        "callType": "staticcall",
        "from": "0xdcd33426ba191383f1c9b431a342498fdac73488",
        "gas": "0x5d382",
        "input": "0x70a08231000000000000000000000000c1563bdf57bdb990c89070aa72cda57fe8d6913d",
        "to": "0x030ba81f1c18d280636f32af80b9aad02cf0854e",
        "value": "0x0"
      },
      "blockHash": "0x197f158db6b8263e7d518a4f8f5f43d689e76ca1921882dae75c4fc050b593d6",
      "blockNumber": 11962253,
      "result": {
        "gasUsed": "0x2a3d",
        "output": "0x0000000000000000000000000000000000000000000000003782db004aad38f2"
      },
      "subtraces": 1,
      "traceAddress": [
        0
      ],
      "transactionHash": "0xffdb1a501fdc51cc308cec9f60cadc6453b221ceec352b804b981da25814f1b9",
      "transactionPosition": 124,
      "type": "call"
    }
  ],
  "id": 0
}

你可能感兴趣的:(区块链)