监控以太坊交易记录,监控以太坊代币交易;
一般都是监控代币转账记录;
erc20标准的转账
//转账方法
function transfer(address to, uint256 value) public returns (bool);
function transferFrom(address from, address to, uint256 value) public returns (bool);
//转账事件,以上两个交易成功都会发送该事件
event Transfer(address indexed from, address indexed to, uint256 value);
let transaction = await web3.eth.getTransaction(pHash).catch(Common.thenCatch);
if (tr.to === null) {
//创建智能合约,
}else if(tr.input.length >= 10){
//调用合约方法
//再匹配是否调用了某方法
if (input.startsWith('0xa9059cbb')) {
//transfer(address,uint256)
}else if(input.startsWith('0x23b872dd'){
//transferFrom
}
}else{
//转账以太坊
}
如果疑惑0xa9059cbb是什么,可参考链接 以太坊实战【地址监控三】
或者参考 理解以太坊时间与日志
web3.sha3("transfer(address,uint256)")
推荐一个通过abi解析input的库 https://lab.miguelmota.com/ethereum-input-data-decoder/example/
这个方式会比较简单,但是链接节点需要通过ws的方式。ws好像比较耗资源..链接数多了节点会卡;
使用方法:
1)合约事件订阅
参考 http://cw.hubwiz.com/card/c/web3.js-1.0/1/4/13/
let web3 = AppConfig.getWeb3();
let contract = new web3.eth.Contract(AppConfig.getErc20Abi(),addr);
let events = new Array();
//contract.events.Transfer({fromBlock:0,filter:{to:to}},function (error,event) {
contract.events.Transfer({fromBlock:0,filter:{from:from}},function (error,event) {
//
console.log(event);
}).on('data',function (log) {
console.log(log);
}).on('changed',function (log) {
console.error('changed-----');
console.log(log);
}).on('error',function (log) {
console.error('error-----');
console.log(log);
});
2)直接使用web3.eth订阅事件
参考http://cw.hubwiz.com/card/c/web3.js-1.0/1/3/6/
let obj = {
// address:'替换'
topics: ['替换']
};
let web3 = ConfigInit.getWeb3();
subscription = web3.eth.subscribe('logs', obj, function (err, result) {
console.log(err);
console.log(result);
}).on("data", function (log) {
console.error('data-----');
console.log(log);
}).on("changed", function (log) {
console.error('changed-----');
console.log(log);
}).on('error', function (log) {
console.error('error-----');
console.log(log);
});
具体查看 https://etherscan.io/apis
使用访问限制,看提示是每秒最多5个请求..具体看实测。。那个MyApiKey也可以不注册使用
使用例子
//测试网
https://api-rinkeby.etherscan.io/api?module=account&action=tokentx&contractaddress=0x00000000&startblock=4936100&endblock=4936200&sort=asc
//主网
https://api.etherscan.io/api?module=account&action=tokentx&contractaddress=0x00000000&startblock=4936100&endblock=4936200&sort=asc
//自行替换里面的合约地址和start,end块号
该方法是无意中发现的..之前再群里总问别人,除了上述三种方式,还有没有其他的...没人答....
然后还去找go-ethereum 看有没有方案,忘了后面怎么发现了getPastLogs
先说下为啥不用上面三种
第一种方式:扫to;会有一种地方会漏掉。 比如批量转代币(不懂的可以查看 https://blog.csdn.net/zgf1991/article/details/90756619),或者其他合约内部转账
使用批量合约转币,to是批量合约工具的地址,调用的方法也不是transfer(address,uint256);未知的,合约开发者随意取名,不过内部还是掉的transfer
第二种方式:ws是因为我这边没有测试节点,主节点怕用了ws卡或搞坏环境;
第三种方式:etherscan-apis;不稳定..万一请求过快/过多。被限制或被检测是爬虫; 就GG了。群里有个小伙用这个不是很稳定,还出404~~
好了。正题。。
目前我使用getPastLogs挺好用的, 每隔45-60s调用一次,自行保存最后一次的块号;
如使用发现问题..请告知一声
核心代码如下
MonitorTokensTxLogsUtils.scanBlockTokenTx = async function (startBlock, endBlock) {
let arr = await MongodbOptions.getAllContract().catch(function (e) {
log4js.error('查询mongodb合约列表出错 ' + e.message);
});
if (arr === undefined) {
//查询出错
return null;
}
if (arr.length === 0) {
log4js.info('未查询到mongodb合约数据');
} else {
let map = new Map();
for (let i = 0; i < arr.length; i++) {
map.set(arr[i].address, arr[i].name);
}
await getLogs(map, startBlock, endBlock);
}
return true;
};
/**
* 扫区块,监控入账
* 只有交易成功的才会进入到这里,交易失败的,没有
* @param tokenMap map(addr=>name);存入时需转成小写! 先从数据库查询
* @param startBlock
* @param endBlock
*/
async function getLogs(tokenMap, startBlock, endBlock) {
let web3 = ConfigInit.getWeb3();
let datas = await web3.eth.getPastLogs({fromBlock: startBlock, toBlock: endBlock, topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef']}).catch(function (e) {
log4js.error('查询交易记录出错:startBlock: ' + startBlock + ' -> endBlock: ' + endBlock);
return null;
});
console.log('查询到' + datas.length + '条数据');
for (let i = 0; i < datas.length; i++) {
let item = datas[i];
let addr = item.address.toLocaleLowerCase();
console.log(addr);
if (tokenMap.has(addr)) {
let to = '0x' + item.topics[2].substring(26);//32字节,20是地址,还有12是补0,加上0x;12*2 = 24 + 2 = 26;
to = to.toLocaleLowerCase();
let isAddrExist = await MongodbOptions.checkToAddr(to).catch(Common.thenCatch);
if (isAddrExist) {
let tx = item.transactionHash.toLocaleLowerCase();
let logIndex = item.logIndex;
//确认交易是否已经存在;
let txExist = await checkTxExist(tx, logIndex);
if (txExist) {
log4js.info('交易已经存在 ' + tx + ' ' + logIndex + ' ' + to);
continue;
}
let blockHash = item.blockHash;
let blockHeight = item.blockNumber;
let value = web3.utils.fromWei(item.data);
let blockTimestamp = await getBlockTime(blockHeight);
reportContract(tx, blockHash, blockHeight, to, value, endBlock, blockTimestamp, logIndex, tokenMap.get(addr));
}
}
}
return true;
}
如果疑问 topics: ['0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef'] 是什么;
借鉴下 理解以太坊事件与日志 文中的代码
> web3.sha3("Transfer(address,address,uint256)")
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
topics 内容结构如何解析,可以先自己测试获取成功,去看实际返回值分析;
参考批量日志 https://ropsten.etherscan.io/tx/0x630b868e41ecfbd97273371ed33242439230355d9110095b814ae3029b88d829#eventlog
该记录不是我的...网上抠的~~~
说下我目前的了解:
返回的datas是所有发送了Transfer(address,address,uint256)事件的交易记录;主要都是ERC20代币
topics[0]是事件方法名,topics[1]是发送者(from) ,topics[2]是接收者(to)
data是转账金额;logIndex是当前交易hash中的日志id,可以当做当前的唯一标识(是当前块还是当前hash,需要确认下,忘了...)
其他的自己琢磨吧
当前使用web3版本是 "web3": "^1.0.0-beta.55"
如果是java,php自己找找这个方法,应该都有