The resource loading... loading...

Easy introduction to web3 development based on Ethereum with FMZ

Author: Inventors quantify - small dreams, Created: 2023-03-28 13:32:48, Updated: 2024-11-11 22:28:24

":true,“name”:“dst”,“type”:“address”},{“indexed”:false,“name”:“wad”,“type”:“uint256”}],“name”:“Transfer”,“type”:“event”},{“anonymous”:false,“inputs”:[{“indexed”:true,“name”:“dst”,“type”:“address”},{“indexed”:false,“name”:“wad”,“type”:“uint256”}],“name”:“Deposit”,“type”:“event”},{“anonymous”:false,“inputs”:[{“indexed”:true,“name”:“src”,“type”:“address”},{“indexed”:false,“name”:“wad”,“type”:“uint256”}],“name”:“Withdrawal”,“type”:“event”}]`

// WETH合约地址
var wethAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"

// 注册WETH合约的ABI
exchange.IO("abi", wethAddress, abiWETH)

// 当前配置的交易所对象的钱包地址
var walletAddress = exchange.IO("address")

// 编码WETH合约的deposit方法调用
var calldataForDeposit = exchange.IO("encode", wethAddress, "deposit")
Log("calldataForDeposit:", "0x" + calldataForDeposit)

// 获取nonce
var nonce = exchange.IO("api", "eth", "eth_getTransactionCount", walletAddress, "pending")

// 获取gasPrice
var gasPrice = exchange.IO("api", "eth", "eth_gasPrice")

// 调用deposit方法把ETH换为WETH,需要转账ETH,这里把0.01ETH转换为以wei为单位的十六进制数值
var innerAmount = BigInt(Number(toInnerAmount(0.005, 18))).toString(16)

// The transaction call object:
var obj = {
    "from" : walletAddress,
    "to"  : wethAddress,
    "gasPrice" : gasPrice,
    "value" : "0x" + innerAmount,
    "data" : "0x" + calldataForDeposit,
}

// 计算gasLimit
var gasLimit = exchange.IO("api", "eth", "eth_estimateGas", obj)

// 构造交易
var transaction = {
    "to": wethAddress,
    "value": toAmount("0x" + innerAmount, 0),   // 转换为10进制
    "data": "0x" + calldataForDeposit,
    "gasLimit": toAmount(gasLimit, 0),   // 转换为10进制
    "gasPrice": toAmount(gasPrice, 0),   // 转换为10进制
    "nonce": toAmount(nonce, 0),         // 转换为10进制
    "chainId": 1,                        // 以太坊主网Id
}
Log("transaction:", transaction)

// 签名,your key 替换为你的私钥
var signedTx = Encode("signTx", "string", "hex", JSON.stringify(transaction), "hex", "0x" + "your key")
Log("signedTx:", "0x" + signedTx)

// 调用eth_sendRawTransaction发送交易
var ret = exchange.IO("api", "eth", "eth_sendRawTransaction", "0x" + signedTx)
return ret 

}


调试工具中运行:

```run
2023-06-15 09:58:50		信息	signedTx: 0xf86f4f8504202067888...
2023-06-15 09:58:50		信息	transaction: {"to":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","value":5000000000000000,"data":"0xd0e30db0","gasLimit":27938,"gasPrice":17718863752,"nonce":79,"chainId":1}
2023-06-15 09:58:50		信息	calldataForDeposit: 0xd0e30db0

Executionvar ret = exchange.IO("api", "eth", "eth_sendRawTransaction", "0x" + signedTx)The function that returns the Transaction Hash is:0x2ff585504b0fe59b0122f696e8808abfe2f3ce263448066533f3bb8a4f55e8e6This one.eth_sendRawTransactionThe call executes the calldata in it, calls the WETH contractdepositThe method converts the 0.005 ETH sent to WETH.

Listening to the mempool

Before users' transactions on Ethereum were packed into the Ethereum blockchain by miners, all exchanges were grouped together in a single blockchain.MempoolIn the (transaction memory pool), aluminum miner's pumps are also looking for high-cost transaction prioritization packaging to maximize mining profits.

Some of the transaction scripts are also correct.MempoolSniffing, hoping to find some profitable trades. For example, if a transaction is set too high a swap slippoint, the transaction may be executed by these transaction scripts.MempoolThe insidependingWhat about the deal?

Listening with REST protocol

Using the RPC methodology we've learned before:eth_getBlockByNumberI'm not going to tell you what I'm talking about.blockNumberWe use"pending"This is a very sad story.

function main() {
    var data = exchange.IO("api", "eth", "eth_getBlockByNumber", "pending", true)
    if (Array.isArray(data.transactions)) {
        for (var i = 0; i < data.transactions.length; i++) {
            Log(data.transactions[i])
        }
    }
}

The debugger tool runs on:

2023-06-18 19:23:05		信息	{"blockNumber":"0x10b2027","type":"0x2","accessList":[],"blockHash":"0xf833ed36435c53d63bd7109bb1e85383075534410c14573881bf26d912f46a89","from":"0xd50521974d62f1fa34b8e81cb742ccf6147d05ff","gasPrice":"0x32ea2db37","hash":"0xf8f10f8f473c340b021298feb48d0affe529e8737a309c4cc1902e8989ef0914","input":"0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001","v":"0x0","value":"0x0","maxFeePerGas":"0x48a413364","maxPriorityFeePerGas":"0x5f5e100","nonce":"0x8","r":"0x8c1cc36f43b02c9e9e454153588cc9d38757f1da69ec49d3cfdda74ab69e06a8","s":"0x2f3dd3e5ddf9e5d42c128a8e900026aca7568fa83c68cf332e1328066ee8d03a","transactionIndex":"0x3a","chainId":"0x1","gas":"0x1142d","to":"0x8c3c0274c33f263f0a55d129cfc8eaa3667a9e8b"}
2023-06-18 19:23:05		信息	{"input":"0x646174613a2c7b2270223a226572632d3230222c226f70223a226d696e74222c227469636b223a2265746873222c226964223a223139323732222c22616d74223a2231303030227d","nonce":"0x1d","blockHash":"0xf833ed36435c53d63bd7109bb1e85383075534410c14573881bf26d912f46a89","from":"0xe7fa86855af674837cea1b58f88b5352543ca27b","gas":"0x81cc","gasPrice":"0x32ea2db37","to":"0xe7fa86855af674837cea1b58f88b5352543ca27b","chainId":"0x1","transactionIndex":"0x39","type":"0x2","value":"0x0","accessList":[],"blockNumber":"0x10b2027","hash":"0x55702f5d14736fc9d0c58fdac2d2052a602db171c46b5e1fa9ff6af5c277f9a2","maxFeePerGas":"0x48a413364","maxPriorityFeePerGas":"0x5f5e100","r":"0x5a703d389d23b51adf8ef0f55db8876e7392636797b68a4be6afe73e76d7e1f2","s":"0x4b4bb11257c4434a0acc2672357f8793476e4bfdf98bc30d2389ce335e7de64e","v":"0x1"}
2023-06-18 19:23:05		信息	{"gas":"0x186a0","nonce":"0x46533","r":"0xfeea052a4ac2283ca058a657a806ba0916d8e7d52d2a577f150c40eb1dfbec65","s":"0x5bf0089a3c060ba787b67a205b44e1065a0d11d132b41737ab9adf0f55066811","transactionIndex":"0x38","value":"0x78f0975742c400","blockHash":"0xf833ed36435c53d63bd7109bb1e85383075534410c14573881bf26d912f46a89","chainId":"0x1","hash":"0x56bdf1b38e23db66e8d1c4014d1e9f690a9217d8a0232489210325fc69e25cf9","v":"0x25","input":"0x","type":"0x0","blockNumber":"0x10b2027","gasPrice":"0x4a817c800","from":"0x97b9d2102a9a65a26e1ee82d59e42d1b73b68689","to":"0xcb513e99c020e9d15a6eafef873fef5d9f078221"}
...

One of the data points:

{
	"blockNumber": "0x10b2027",
	"type": "0x2",
	"accessList": [],
	"blockHash": "0xf833ed36435c53d63bd7109bb1e85383075534410c14573881bf26d912f46a89",
	"from": "0xd50521974d62f1fa34b8e81cb742ccf6147d05ff",
	"gasPrice": "0x32ea2db37",
	"hash": "0xf8f10f8f473c340b021298feb48d0affe529e8737a309c4cc1902e8989ef0914",
	"input": "0xa22cb4650000000000000000000000001e0049783f008a0085193e00003d00cd54003c710000000000000000000000000000000000000000000000000000000000000001",
	"v": "0x0",
	"value": "0x0",
	"maxFeePerGas": "0x48a413364",
	"maxPriorityFeePerGas": "0x5f5e100",
	"nonce": "0x8",
	"r": "0x8c1cc36f43b02c9e9e454153588cc9d38757f1da69ec49d3cfdda74ab69e06a8",
	"s": "0x2f3dd3e5ddf9e5d42c128a8e900026aca7568fa83c68cf332e1328066ee8d03a",
	"transactionIndex": "0x3a",
	"chainId": "0x1",
	"gas": "0x1142d",
	"to": "0x8c3c0274c33f263f0a55d129cfc8eaa3667a9e8b"
}

Listening with the WebSocket protocol

Inventors quantify the trading platforms we use.DialFunctions to createWebSocketLink, you can seeFMZ APIThe documentationDialThe function ≠ ∞

The test code in this chapter runs in an Ethereum-based environment, which makes it easier to quantify disk testing with the inventor because of the use of WebSocket protocol communication. The Websocket protocol subscription message is:

{"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]}

Except fornewPendingTransactionsYou can also subscribe.newHeadslogs

ReceivedWebSocketThe link pushes data to:

{
	"jsonrpc": "2.0",
	"method": "eth_subscription",
	"params": {
		"subscription": "0x2c5c087b4aa188e008f4747828ef4e61",
		"result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3"
	}
}

And then based on:"result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3", further inquiriestransaction‒ For a specifictransactionWe use the Ethereum RPC method.eth_getTransactionByHashI'm here to ask you a question.

var ws = null 

function main () {    
    // {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["xxxxx"]}  , "xxxxx" 是订阅的具体消息
    var payload = {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]}
    
    // wss://mainnet.infura.io/ws/v3/xxxxx , "xxxxx" 是你的infura key
    var infuraKey = "your key"

    ws = Dial("wss://mainnet.infura.io/ws/v3/" + infuraKey + "|reconnect=true&payload=" + JSON.stringify(payload))
    if (!ws) {
        throw "websocket链接infura失败!"
    }
    
    // eth_getTransactionByHash 调用计数
    var getTransactionCounter = 0

    var beginTS = new Date().getTime()

    // 循环获取消息
    while (true) {
        // 接收推送的消息
        var data = ws.read()
        if (data) {
            var ts = new Date().getTime()

            if (ts - beginTS >= 1000) {
                getTransactionCounter = 0
                beginTS = ts 
            }

            // 根据txHash查询交易详情
            if (ts - beginTS < 1000 && getTransactionCounter >= 100) {
                Sleep(1000)
                getTransactionCounter = 0
                beginTS = ts 
            }
            
            var obj = JSON.parse(data)
            if (obj["params"] && obj["params"]["result"]) {
                var transcationInfo = exchange.IO("api", "eth", "eth_getTransactionByHash", obj["params"]["result"])
                Log(obj["params"]["result"], "transcationInfo:", transcationInfo)
            }
            
            getTransactionCounter++
        }

        LogStatus(_D())
    }
}

function onexit() {
    Log("断开WS连接")
    ws.close()
}

Creating a hard drive to run above the code that can receive the data pushed by the WebSocket connection, the data is constantly pushed, we extract one of thesetransaction

{
	"maxPriorityFeePerGas": "0x5f5e100",
	"nonce": "0x1a9",
	"accessList": [],
	"blockNumber": "0x10b1c9f",
	"from": "0x5888700be02f52c8adf85890886ef84a6b8a7829",
	"blockHash": "0x92c3d77ea218cdc0967ab74b6005bb393b92355047f206c7e2d59d41828e7fa9",
	"chainId": "0x1",
	"gasPrice": "0x34fdbf43d",
	"s": "0x7d86ae29a786a61b9e74a7a9e2cc4b39b7913aa3d4c3816ccb07528fed82048a",
	"to": "0xfc2068c3d47b575a60f6a4a7bf60dea0ac368e01",
	"type": "0x2",
	"v": "0x1",
	"value": "0x0",
	"gas": "0x1aad3",
	"hash": "0x2c77c0704aefbb26db460cbb71efdb488df968ad53d2c2b3f1e1172056b40b22",
	"input": "0x42842e0e0000000000000000000000005888700be02f52c8adf85890886ef84a6b8a7829000000000000000000000000d2d07e4d1bb0f40ac3e4aa7cc3ad05d348bfd2c3000000000000000000000000000000000000000000000000000000000000180b",
	"maxFeePerGas": "0x4712d1273",
	"r": "0x8ec58f95f6d9729a6eee075e6976658b6c5346cbc90eb68ac361a40af073b10e",
	"transactionIndex": "0xc1"
}

The log data for the selected (partly omitted) logs:

2023-06-18 16:20:07		信息	断开WS连接
2023-06-18 16:20:07		信息	0xba07ca903f9eafbfa7d494bb26197713034b9ca2dd3c19bc0898af3f35b59343 transcationInfo: {"accessList":[],"from":"0xe2977d60182da068dfd78693f96362ee7a2e9644","nonce":"0xf","value":"0x0","blockHash":"0x92c3d77ea218cdc0967ab74b6005bb393b92355047f206c7e2d59d41828e7fa9","blockNumber":"0x10b1c9f","chainId":"0x1","hash":"0xba07ca903f9eafbfa7d494bb26197713034b9ca2dd3c19bc0898af3f35b59343","maxFeePerGas":"0x530c30b70","r":"0xf28bfdf372a5401a2e00675c6ebe8d5e73f2c955db44b1aa56240b9197d6cbc7","type":"0x2","v":"0x0","gas":"0x21079","gasPrice":"0x367b3783d","input":"0x657bb1130000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001e000000000000000000000000000000000000000000000000000000000000002200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000001e0300000000000000000000000033c6eec1723b12c46732f7ab41398de45641fa42000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000041976bd7d021a5b94cbba72b291093b50a0ecf21d1c6cd8193fbfcd685c4723ce068feb249bdcace58c28eb3b6cc647e8c839b0826c84f8dfe4c31d57d1ac1f0111b00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000648ebef50000000000000000000000000000000000000000000000000000000000000000","maxPriorityFeePerGas":"0x1dcd6500","s":"0x71d51246bb60e792f963a3c75c46fd8f557921ce6face7224c944e1768a76ca","to":"0x0b51eb9d0e54c562fedc07ceba453f05b70c4b79","transactionIndex":"0x40"}
2023-06-18 16:20:07		信息	0x2c77c0704aefbb26db460cbb71efdb488df968ad53d2c2b3f1e1172056b40b22 transcationInfo: {"maxPriorityFeePerGas":"0x5f5e100","nonce":"0x1a9","accessList":[],"blockNumber":"0x10b1c9f","from":"0x5888700be02f52c8adf85890886ef84a6b8a7829","blockHash":"0x92c3d77ea218cdc0967ab74b6005bb393b92355047f206c7e2d59d41828e7fa9","chainId":"0x1","gasPrice":"0x34fdbf43d","s":"0x7d86ae29a786a61b9e74a7a9e2cc4b39b7913aa3d4c3816ccb07528fed82048a","to":"0xfc2068c3d47b575a60f6a4a7bf60dea0ac368e01","type":"0x2","v":"0x1","value":"0x0","gas":"0x1aad3","hash":"0x2c77c0704aefbb26db460cbb71efdb488df968ad53d2c2b3f1e1172056b40b22","input":"0x42842e0e0000000000000000000000005888700be02f52c8adf85890886ef84a6b8a7829000000000000000000000000d2d07e4d1bb0f40ac3e4aa7cc3ad05d348bfd2c3000000000000000000000000000000000000000000000000000000000000180b","maxFeePerGas":"0x4712d1273","r":"0x8ec58f95f6d9729a6eee075e6976658b6c5346cbc90eb68ac361a40af073b10e","transactionIndex":"0xc1"}
2023-06-18 16:20:07		信息	0xbc42d5db10e5cb2e888c76005c522cb2474a0c0a7325feb867b618f69ff26f2a transcationInfo: {"accessList":[],"blockNumber":"0x10b1c9f","gas":"0x1cc12b","hash":"0xbc42d5db10e5cb2e888c76005c522cb2474a0c0a7325feb867b618f69ff26f2a","maxFeePerGas":"0x6ab262e5c","value":"0x0","v":"0x1","chainId":"0x1","from":"0xc1b634853cb333d3ad8663715b08f41a3aec47cc","input":"0x8f111f3c000000000000000000000000000000000000000000000000000000000003b83700000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000e0fa2000000000000000000000000e64a54e2533fd126c2e452c5fab544d80e2e4eb50000000000000000000000000000000000000000000000000000000004c6ff1c0000000000000000000000000000000000000000000000000000000004c70029000000000000000000000000000000000000000000000000000000000001822d005b1979341221e80ed20b20d832de88a8a4b535fe9990a90c165f3c95ad085ab9445c0a998c70edff76f1c2de3f4263d7e4fe3c3fb73fe7dcfbdede92371842fb883267f5408c8aaf08ba2f6c22463f19da98183d2302735615460d7380d6f9ff5e764e75bcaca9a93946cf644cd4d4448f314c4cf60cd0353f085aa0562d70e16a510b8bc4c2a09b5e7fafcd43f07dc1b5dd1782962af8f6fff7a6965bfc127e11501a72c64913d58e624333f9ec51687c7cb1bb4a9850541f1e03b2790ed4ee508052910dfe22542d900548d5243ca238811427491d49e98cf269ccab5b1724f0f9698120e406c00910c4090c0e84e0400e2706822d2a001a3964a0ca8101700a547342c2c1fff8934a988416f020a0c98f0909c7f529875f8443914e10b58145c79d38914d1fafbc9ee57ebcb377e4ac1cd252bdebe3c59e8e917fea7dbc7bf66dfc1846482a858645b95555b3ecc9ab4f9e2b0e3e78d68379b009e606a1cefe675670a5eabd5f5a2efa5d77a1084288480c98d01c70a3d8c6b854496e2a966dc9051b13b872b7c6c2c5d82676fd8e82c680514333db21db2006d23f42074021de7e61c54d88b01824d40f03d1505eb6ec6d0cb7ccd38deb821517a5e63d0e89f6bf0385f109c81ea36dd00e7a903a100290f5b47a940ed146ae9338ff8bc17a2b5bc457614d0831e743e485c0de84636b034400bf6bd192ff723045cc170e109aabf273dc9de19c9987038515b6613249f471f9ddeb31331cc1643902212d20241c417532ad7e4a9ac742b4b5f68e1019795cf9386dcf36037502c13ff51f50a2202b2c1cac1c0b38a21ec798deff778c9a6b679d16d0521d2df89c439f4f8f9425ed378f4194d03d00
2023-06-18 16:20:06		信息	0xff0945c3d682a37e18ee433d56c8bedbb93d9ac368af968ed8d53b655575e8e5 transcationInfo: {"gas":"0x5208","s":"0x63572e1fa060841b939cea0849154e55781fe0efcbdfe5ce6979b44ce0980e4a","transactionIndex":"0xa7","value":"0x113e9d515e400","blockHash":"0x92c3d77ea218cdc0967ab74b6005bb393b92355047f206c7e2d59d41828e7fa9","hash":"0xff0945c3d682a37e18ee433d56c8bedbb93d9ac368af968ed8d53b655575e8e5","nonce":"0x2","r":"0x698fe26331ad39ba89c4d30985b707792ea4ab09b25205727f8fac2a6120b54a","gasPrice":"0x35458af00","from":"0x228d93af92d03184c07aa9e39b3d2d61b666686d","input":"0x","to":"0x0246177b98a5e42835cdcfaac1c274d3e6c39486","v":"0x26","blockNumber":"0x10b1c9f","type":"0x0","chainId":"0x1"}
...

Details of the transaction

In the previous lesson, we wrote a monitoring program for unresolved transactions on Ethereum that obtained pushed transaction hashes through the WebSocket protocol and then queried specific transaction details based on the transaction hash.

Next, we're going to look at the transaction details.inputThe field data is further analyzed.inputThe field data appears to be a clutter of hexadecimal data that actually encodes the contents of the transaction: including the function to be called, as well as the parameters to be entered.

After repeated, extensive testing, it was found that the timeliness and number of RPC nodes currently in use are closely related to the number of WebSocket connections. Two different RPC node services (e.g. infura, ALCHEMY) create WebSocket connections at the same time, and the received push data is not exactly the same.eth_getTransactionByHash去查询时往往得到一个空值(在FMZ上测试、node.js测试)。

This time we usealchemyThe RPC nodes:wss://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDNThis node supports both the WebSocket protocol and the REST protocol.

We monitor the routing of smart contracts on the Uniswap decentralized exchange.multicall(uint256,bytes[])So first we need to compute the hash of the function signature of this method.

// 取完整哈希值的前8个字符
// multicall: 0x5ae401dc
var sigHash = "0x" + Encode("keccak256", "string", "hex", "multicall(uint256,bytes[])").slice(0, 8)

Based on the example in the previous section, we have made some modifications.var data = ws.read(-2)How to get the latest dataread()The function parameter is set to -2, which means that it immediately returns the latest data.multicallCalledTransaction, useif (tx && tx.input.indexOf(sigHash) !== -1)I'm not going to say that.

You need to design two custom functions:

  • calcAllFuncSigHash()This is the first time I have seen a hash of the ABI-based algorithm.
  • decodeCall()It's not a problem.

The next thing you know,multicallWhen you call, you can start the decryption operation, the first time you decrypt.multicallParameters of the method:deadlineanddatadeadlineI'm not sure how to explain it, but it's a time capsule.dataIt's also a coded calldata, so it needs to continue to be used.decodeCall()The function decodes.

Here are some examples of complete implementation:

var ws = null 
var arrLog = []

const ABI_Route = '[{"inputs":[{"internalType":"address","name":"_factoryV2","type":"address"},{"internalType":"address","name":"factoryV3","type":"address"},{"internalType":"address","name":"_positionManager","type":"address"},{"internalType":"address","name":"_WETH9","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"WETH9","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMax","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"}],"name":"approveZeroThenMaxMinusOne","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"data","type":"bytes"}],"name":"callPositionManager","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"paths","type":"bytes[]"},{"internalType":"uint128[]","name":"amounts","type":"uint128[]"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"uint24","name":"maximumTickDivergence","type":"uint24"},{"internalType":"uint32","name":"secondsAgo","type":"uint32"}],"name":"checkOracleSlippage","outputs":[],"stateMutability":"view","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactInputParams","name":"params","type":"tuple"}],"name":"exactInput","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMinimum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactInputSingleParams","name":"params","type":"tuple"}],"name":"exactInputSingle","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"bytes","name":"path","type":"bytes"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"}],"internalType":"struct IV3SwapRouter.ExactOutputParams","name":"params","type":"tuple"}],"name":"exactOutput","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"tokenIn","type":"address"},{"internalType":"address","name":"tokenOut","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMaximum","type":"uint256"},{"internalType":"uint160","name":"sqrtPriceLimitX96","type":"uint160"}],"internalType":"struct IV3SwapRouter.ExactOutputSingleParams","name":"params","type":"tuple"}],"name":"exactOutputSingle","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"factory","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"factoryV2","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"getApprovalType","outputs":[{"internalType":"enum IApproveAndCall.ApprovalType","name":"","type":"uint8"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint256","name":"tokenId","type":"uint256"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"}],"internalType":"struct IApproveAndCall.IncreaseLiquidityParams","name":"params","type":"tuple"}],"name":"increaseLiquidity","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"components":[{"internalType":"address","name":"token0","type":"address"},{"internalType":"address","name":"token1","type":"address"},{"internalType":"uint24","name":"fee","type":"uint24"},{"internalType":"int24","name":"tickLower","type":"int24"},{"internalType":"int24","name":"tickUpper","type":"int24"},{"internalType":"uint256","name":"amount0Min","type":"uint256"},{"internalType":"uint256","name":"amount1Min","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"internalType":"struct IApproveAndCall.MintParams","name":"params","type":"tuple"}],"name":"mint","outputs":[{"internalType":"bytes","name":"result","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"previousBlockhash","type":"bytes32"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes[]","name":"data","type":"bytes[]"}],"name":"multicall","outputs":[{"internalType":"bytes[]","name":"results","type":"bytes[]"}],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"positionManager","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"pull","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"refundETH","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermit","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowed","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"uint256","name":"expiry","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitAllowedIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"selfPermitIfNecessary","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"},{"internalType":"uint256","name":"amountOutMin","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapExactTokensForTokens","outputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountOut","type":"uint256"},{"internalType":"uint256","name":"amountInMax","type":"uint256"},{"internalType":"address[]","name":"path","type":"address[]"},{"internalType":"address","name":"to","type":"address"}],"name":"swapTokensForExactTokens","outputs":[{"internalType":"uint256","name":"amountIn","type":"uint256"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"sweepToken","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"sweepTokenWithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"int256","name":"amount0Delta","type":"int256"},{"internalType":"int256","name":"amount1Delta","type":"int256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"uniswapV3SwapCallback","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"}],"name":"unwrapWETH9","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amountMinimum","type":"uint256"},{"internalType":"uint256","name":"feeBips","type":"uint256"},{"internalType":"address","name":"feeRecipient","type":"address"}],"name":"unwrapWETH9WithFee","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"value","type":"uint256"}],"name":"wrapETH","outputs":[],"stateMutability":"payable","type":"function"},{"stateMutability":"payable","type":"receive"}]'

function calcAllFuncSigHash(jsonABI) {
    var mapSigHash = {}
    for (var i in jsonABI) {
        var ele = jsonABI[i]
        if (typeof(ele["name"]) != "undefined") {
            if (ele["inputs"]) {
                var funcName = ele["name"]
                if (ele["inputs"].length == 0) {
                    var methodId = "0x" + Encode("keccak256", "string", "hex", funcName + "()").slice(0, 8)
                    mapSigHash[methodId] = {"argsTypeList": [], "argsNameList": [], "funcName": funcName}
                } else {
                    var arr = []
                    var arrName = []
                    var argPrototype = []
                    for (var j in ele["inputs"]) {
                        var inputType = ele["inputs"][j]["type"]
                        if (inputType == "tuple") {                            
                            var components = ele["inputs"][j]["components"]
                            var tupleType = []
                            var protoType = []
                            for (var componentsIdx = 0; componentsIdx < components.length; componentsIdx++) {
                                tupleType.push(components[componentsIdx]["type"])
                                protoType.push(components[componentsIdx]["name"] + " " + components[componentsIdx]["type"])
                            }
                            arr.push("(" + tupleType.join() + ")")
                            arrName.push(ele["inputs"][j]["name"])
                            // 原型
                            argPrototype.push("tuple" + "(" + protoType.join() + ")")
                        } else {
                            arr.push(inputType)
                            arrName.push(ele["inputs"][j]["name"])
                            // 原型
                            argPrototype.push(inputType)
                        }                        
                    }
                    var functionSignature = funcName + "(" + arr.join() + ")"
                    var methodId = "0x" + Encode("keccak256", "string", "hex", functionSignature).slice(0, 8)
                    mapSigHash[methodId] = {"argsTypeList": arr, "argsNameList": arrName, "funcName": funcName, "argPrototype": argPrototype}
                }
            }
        }
    }
    return mapSigHash
}

function decodeCall(input, abi) {
    var mapSigHash = calcAllFuncSigHash(JSON.parse(abi))
    var methodId = input.slice(0, 10)
    var data = input.slice(10)
    
    var decodedArgs = {}
    var infoMethod = mapSigHash[methodId]
    if (typeof(infoMethod) == "undefined") {
        return [methodId, mapSigHash]
    }
    
    var arr = []
    for (var i = 0; i < infoMethod["argsTypeList"].length; i++) {
        if (infoMethod["argsTypeList"][i].startsWith("(")) {
            arr.push(infoMethod["argPrototype"][i])
        } else {
            arr.push(infoMethod["argsTypeList"][i])
        }
    }
    
    if (arr.length == 0) {
        return {"funcName": infoMethod["funcName"], "args": decodedArgs}
    }

    var args = exchange.IO("decode", arr.join(), data)

    if (!Array.isArray(args)) {
        args = [args]
    }

    if (args.length != infoMethod["argsNameList"].length) {
        Log("args:", args)
        Log("infoMethod:", infoMethod)
        throw "解码后的args与argsNameList不等"
    }

    for (var i = 0; i < infoMethod["argsNameList"].length; i++) {
        var key = infoMethod["argsNameList"][i]
        var value = args[i]
        decodedArgs[key] = value
    }

    return {"funcName": infoMethod["funcName"], "args": decodedArgs}
}

function main () {
    // {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["xxxxx"]}  , "xxxxx" 是订阅的具体消息
    var payload = {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]}
    
    // 使用alchemy服务
    ws = Dial("wss://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN" + "|reconnect=true&payload=" + JSON.stringify(payload))
    if (!ws) {
        throw "websocket链接alchemy失败!"
    }
    
    // eth_getTransactionByHash 调用计数
    var getTransactionCounter = 0
    
    // 起始时间戳
    var beginTS = new Date().getTime()
    
    // 计算函数签名哈希
    var sigHash = "0x" + Encode("keccak256", "string", "hex", "multicall(uint256,bytes[])").slice(0, 8)
    Log("sigHash:", sigHash)

    // 循环获取消息
    while (true) {
        var msg = ""
        var recv = null
        // 接收推送的消息,使用read参数-2,立即返回最新数据
        var data = ws.read(-2)
        if (data && data != "") {
            var ts = new Date().getTime()

            if (ts - beginTS >= 1000) {
                getTransactionCounter = 0
                beginTS = ts 
            }

            // 根据txHash查询交易详情
            if (ts - beginTS < 1000 && getTransactionCounter >= 100) {
                Sleep(1000)
                getTransactionCounter = 0
                beginTS = ts 
            }
            
            var obj = JSON.parse(data)
            if (obj["params"] && obj["params"]["result"]) {
                var txHash = obj["params"]["result"]
                var tx = exchange.IO("api", "eth", "eth_getTransactionByHash", txHash)
                
                if (tx && tx.input.indexOf(sigHash) !== -1) {
                    // 解码交易详情
                    arrLog = []
                    var decodedInput = decodeCall(tx.input, ABI_Route)

                    // Log("----------------", txHash, "/", decodedInput["funcName"], "----------------", "#FF0000")
                    arrLog.push("----------------" + txHash + "/" + decodedInput["funcName"] + "----------------" + "#FF0000")
                    arrLog.push(tx.from + " -> " + tx.to)

                    for (var i = 0; i < decodedInput["args"]["data"].length; i++) {
                        var calldata = "0x" + decodedInput["args"]["data"][i]
                        var decodedCalldata = decodeCall(calldata, ABI_Route)

                        // Log("----------------", decodedCalldata["funcName"], "----------------", "#FF0000")
                        arrLog.push("----------------" + decodedCalldata["funcName"] + "----------------" + "#FF0000")

                        for (var key in decodedCalldata["args"]) {
                            // Log(key, decodedCalldata["args"][key])
                            arrLog.push(key + ": " + JSON.stringify(decodedCalldata["args"][key]))
                        }
                    }

                    // 输出日志
                    for (var logIdx = arrLog.length - 1; logIdx >= 0; logIdx--) {
                        Log(arrLog[logIdx])
                    }
                }
                
                getTransactionCounter++
            }
            recv = obj
        } else if (data == null) {
            msg = "缓冲区队列空了,时间:" + _D()
        }
        
        LogStatus(_D(), ", msg:", msg, ", recv:", recv)
    }
}

function onexit() {
    Log("断开WS连接")
    ws.close()
}

function onerror() {
    Log("断开WS连接")
    ws.close()

    for (var logIdx = arrLog.length - 1; logIdx >= 0; logIdx--) {        
        Log(arrLog[logIdx])
    }
}

Create a test run on a hard disk:

2023-06-20 17:01:00		信息	----------------0x5288a7bd6e0f57162ca763df722de73793e542734d7d2b7af5755664e2e67910/multicall----------------
2023-06-20 17:01:00		信息	0x851b594033d57c98af753bcb3a7d0237a615de32 -> 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45
2023-06-20 17:01:00		信息	----------------exactInputSingle----------------
2023-06-20 17:01:00		信息	params: {"tokenOut":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2","fee":"10000","recipient":"0x0000000000000000000000000000000000000002","amountIn":"8952087000296027130940868","amountOutMinimum":"41638694112306829","sqrtPriceLimitX96":"0","tokenIn":"0xe1283567345349942acdfad3692924a1b16cf3cc"}
2023-06-20 17:01:00		信息	----------------unwrapWETH9----------------
2023-06-20 17:01:00		信息	amountMinimum: "41638694112306829"
2023-06-20 17:01:00		信息	recipient: "0x851b594033d57c98af753bcb3a7d0237a615de32"
2023-06-20 16:59:03		信息	----------------0x55e0c4a38a17d3aa6e8f558a66c77e9defa9f8f6e347536363ac1b921de9aaf3/multicall----------------
2023-06-20 16:59:03		信息	0x27457ada2dd725c7d0f28e1737bdd0bf583c0f0b -> 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45
2023-06-20 16:59:03		信息	----------------swapExactTokensForTokens----------------
2023-06-20 16:59:03		信息	amountIn: "816769666850161"
2023-06-20 16:59:03		信息	amountOutMin: "40404501509302321"
2023-06-20 16:59:03		信息	path: ["0x7863e06bca47ded821fcb53ab788eeb371243eda","0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"]
2023-06-20 16:59:03		信息	to: "0x27457ada2dd725c7d0f28e1737bdd0bf583c0f0b"
2023-06-20 16:58:25		信息	sigHash: 0x5ae401dc

The screenshot:

img

You can see that the transaction hash is0x5288a7bd6e0f57162ca763df722de73793e542734d7d2b7af5755664e2e67910Input data is the data that contains a call to a transaction.multicallThe method is called. The transaction is sent to: 0x851b594033d57c98af753bcb3a7d0237a615de32 -> 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45.0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45Uniswap's routing contract address.

img

SolvedmulticallPacking calls for a contractexactInputSingleandunwrapWETH9This is the first time that the project has been implemented in the country.

----------------exactInputSingle----------------
params: {
    "tokenOut":"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
    "fee":"10000",
    "recipient":"0x0000000000000000000000000000000000000002",
    "amountIn":"8952087000296027130940868",
    "amountOutMinimum":"41638694112306829",
    "sqrtPriceLimitX96":"0",
    "tokenIn":"0xe1283567345349942acdfad3692924a1b16cf3cc"
}

----------------unwrapWETH9----------------
amountMinimum: "41638694112306829"
recipient: "0x851b594033d57c98af753bcb3a7d0237a615de32"

Interested students can modify, expand, monitor more transactions, and parse operations on these chains based on this example.


More