实现 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.<Array>}
 */
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 转账监听

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
}
  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
要使用JS来转账ERC20的USDT,需要使用相应的Web3库。以下是一个简单的示例代码: ```javascript const Web3 = require('web3'); const EthereumTx = require('ethereumjs-tx').Transaction; const web3 = new Web3(new Web3.providers.HttpProvider('https://mainnet.infura.io/v3/<YOUR_INFURA_PROJECT_ID>')); // 以下是需要设置的参数 const privateKey = '<YOUR_PRIVATE_KEY>'; const toAddress = '<RECEIVER_ADDRESS>'; const amount = '<AMOUNT_IN_WEI>'; const contractAddress = '0xdac17f958d2ee523a2206206994597c13d831ec7'; const decimals = 6; const gasPrice = '1'; const gasLimit = '100000'; // 获取USDT合约对象 const usdtContract = new web3.eth.Contract([ { "constant": true, "inputs": [], "name": "name", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": false, "inputs": [ { "name": "_to", "type": "address" }, { "name": "_value", "type": "uint256" } ], "name": "transfer", "outputs": [ { "name": "", "type": "bool" } ], "payable": false, "stateMutability": "nonpayable", "type": "function" }, { "constant": true, "inputs": [], "name": "decimals", "outputs": [ { "name": "", "type": "uint8" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "totalSupply", "outputs": [ { "name": "", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [], "name": "symbol", "outputs": [ { "name": "", "type": "string" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "constant": true, "inputs": [ { "name": "_owner", "type": "address" } ], "name": "balanceOf", "outputs": [ { "name": "balance", "type": "uint256" } ], "payable": false, "stateMutability": "view", "type": "function" }, { "anonymous": false, "inputs": [ { "indexed": true, "name": "from", "type": "address" }, { "indexed": true, "name": "to", "type": "address" }, { "indexed": false, "name": "value", "type": "uint256" } ], "name": "Transfer", "type": "event" } ], contractAddress); // 获取nonce值 web3.eth.getTransactionCount(web3.eth.accounts.privateKeyToAccount(privateKey).address, 'pending').then(nonce => { // 构造交易对象 const txParams = { from: web3.eth.accounts.privateKeyToAccount(privateKey).address, nonce: web3.utils.toHex(nonce), gasPrice: web3.utils.toHex(gasPrice * 1e9), gasLimit: web3.utils.toHex(gasLimit), to: contractAddress, value: '0x0', data: usdtContract.methods.transfer(toAddress, amount).encodeABI() }; // 签名交易 const tx = new EthereumTx(txParams, {chain: 'mainnet'}); tx.sign(Buffer.from(privateKey, 'hex')); // 发送交易 const serializedTx = tx.serialize(); web3.eth.sendSignedTransaction('0x' + serializedTx.toString('hex')) .on('transactionHash', txHash => { console.log(`Transaction hash: ${txHash}`); }) .on('receipt', receipt => { console.log(`Transaction receipt: ${JSON.stringify(receipt, null, 2)}`); }) .on('error', error => { console.error(`Transaction error: ${error}`); }); }); ``` 在上述代码中,需要设置以下参数: - `privateKey`: 发送USDT的账户的私钥 - `toAddress`: 接收USDT的账户的地址 - `amount`: 发送的USDT数量,单位为wei - `contractAddress`: USDT合约地址 - `decimals`: USDT的小数位数 - `gasPrice`: 交易的gas价格,单位为Gwei - `gasLimit`: 交易的gas限制 该示例代码使用Infura提供的节点来与以太坊网络进行交互,也可以使用自己的节点。注意,发送USDT需要支付一定的手续费,需要确保发送账户有足够的ETH用于支付手续费。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值