所需环境和工具
对 ERC20 的转账监听有如下两个方案
eth_getLogs
来获取 ERC20 的转账事件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 转账有四类,一种是 external transaction,也就是普通的 Ether 转账。另外一种是 internal transaction,是通过智能合约进行的 Ether 转账。internal transaction 需要通过调用 debug
或者 trace
模块来获取合约执行的详细过程。剩余两类:coinbase 挖矿奖励和 selfdestruct 合约自毁。
特别注意,如果想要在自己的
geth
或者openethereum
节点上调用debug
、trace
模块,必须将节点设置为 归档全节点 模式,即syncmode=full
和gcmode=archive
。归档全节点需要至少 6TB 的磁盘空间(截止2021年3月)。
获取 internal transaction 需要归档全节点,可以考虑使用 infura.io 或者 alchemyapi.io ,支付较少的成本即可获取归档全节点的全部能力。
如下是通过 openethereum
的 trace_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 即可认为是 from
到 to
转账的 Ether 金额。
调用
debug_traceTransaction
前,必须确认receipt.status
为true
{
"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
}