Sumber daya yang dimuat... Pemuatan...

Mulailah dengan Pengembangan web3 Mudah Berdasarkan Ethereum Menggunakan FMZ

Penulis:FMZ~Lydia, Dibuat: 2023-06-25 09:17:53, Diperbarui: 2024-11-11 22:34:49

Contract name: BoredApeYachtClub

2023-06-13 16:32:57 Info Alamat kontrak: 0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d


The contract with the address ```0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d``` is determined to be ERC721 standard.

In this part, we introduced how to determine ERC721 contracts, so contracts like ERC20, which do not support the ERC165 standard, will have to be identified in another way. Do you know how to check if a contract is ERC20 standard?

## Encoding calldata

What is ```calldata```? By the author's understanding, a simple layman's description here is:

> The "calldata" is the encoding of a function call or parameter in Ethereum, and the "calldata" is encoded according to the ABI (Application Binary Interface) specification of the contract.

For example, we can encode the ```balanceOf``` and ```transfer``` method calls of the ERC20 contract we studied in the previous course, together with the parameters of the calls, into a ```calldata```. In some application scenarios, such as **interaction between contracts**, this scenario will use ```calldata```, and of course there are many other application scenarios that are not listed here.

How to code a smart contract function call to get ```calldata```?

In the FMZ Quant Trading Platform, you can use ```exchange.IO("encode", ...)``` to encode smart contract function calls, the use of exchange.IO("encode", ...) is very simple. The first parameter of the function is the fixed string ```"encode"```; the second parameter is the address of the smart contract; the third parameter is the name of the smart contract method to be encoded; the rest of the parameters are passed to the specific parameter value of the smart contract method to be encoded.

### eth_sendRawTransaction

When we encode a smart contract method call and generate the corresponding ```calldata``` data, if this smart contract method is a Write method (i.e.: write operation), we need to use the generated ```calldata``` data as the data field of the transaction and then use the Ethereum RPC method ```eth_ sendRawTransaction``` to send a request containing the raw data of that transaction to the Ethereum network.

The ```eth_sendRawTransaction``` method has only one parameter, ```data```:

> data: The signed transaction (typically signed with a library, using your private key)

The ```data``` parameter is a transaction data after the signature calculation, and the transaction data structure of Ethereum has the following main fields:

```javascript
{
    "nonce": "0x1",                         // Number of transactions on the account of the sender of the transaction
    "gasPrice": "0x12a05f200",              // Traded Gas price
    "gasLimit": "0x5208",                   // Gas limit for trading
    "to": "0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2",    // Target contract address or recipient address
    "value": "0x4563918244F40000",          // Number of Ethereum transferred
    "data": "0x0123456789ABCDEF",           // Data to send to the contract
}

Bagaimana cara menandatangani transaksi Ethereum?

Dalam FMZ Quant Trading Platform, kami menggunakanEncode()fungsi untuk melakukan perhitungan tanda tangan, contoh spesifik yang kita tulis dalam kursus berikutnya Execute Write method calldata.

Eksekusi Read metode calldata

Untuk pelaksanaancalldatadari metode Read, kita menggunakan metode RPC yang telah dipelajari sebelumnya:eth_callKami menjelaskaneth_callMetode RPC dari Ethereum hanya melakukan demonstrasi dariWritemetode kontrak pintar, dalam bagian ini, kita menggunakancalldatametode untuk menunjukkan eksekusi kontrak pintar Baca panggilan metode.balanceOfmetode kontrak WETH untuk membaca saldo saat ini dari token WETH di dompet.

Kami menggunakan alat debugging untuk menguji pada Ethereum mainnet di:

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function main() {
    // ABI for WETH contracts
    var abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"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":"","type":"address"}],"name":"balanceOf","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":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"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"},{"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 contract address
    var wethAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"

    // Register ABI for WETH contracts
    exchange.IO("abi", wethAddress, abiWETH)

    // The wallet address of the currently configured exchange object
    var walletAddress = exchange.IO("address")

    // Coded WETH contract's deposit method call
    var calldataForDeposit = exchange.IO("encode", wethAddress, "balanceOf(address)", walletAddress)
    Log("calldataForDeposit:", "0x" + calldataForDeposit)

    // Construct the transaction as the first parameter of eth_call
    var transaction = {
        "from" : walletAddress,
        "to" : wethAddress,
        "data" : "0x" + calldataForDeposit,
    }

    // The second parameter of eth_call
    var blockNumber = "latest"

    // Call with eth_call
    var ret = exchange.IO("api", "eth", "eth_call", transaction, blockNumber)
    var wethBalance = exchange.IO("decode", "uint256", ret)   // You can use exchange.IO("decode", ...) function to decode
    Log("wethBalance:", toAmount(wethBalance, 18))            // Converted from wei to WETH units
}

Jalankan di alat debugging:

2023-06-15 11:51:31		Info	wethBalance: 0.015
2023-06-15 11:51:31		Info	calldataForDeposit: 0x70a082310000000000000000000000006b3f11d807809b0b1e5e3243df04a280d9f94bf4

Jika metode kontrak pintar memiliki nilai pengembalian, Anda dapat menggunakanexchange.IO("decode", ...)Anda dapat melihat bahwa melewaticalldatametode adalah sama dengan memanggil kontrak pintarsbalanceOfmetode langsung, mendapatkan saldo WETH 0,015 WETH untuk dompet tes saya.

Eksekusi Tulis metode calldata

Untuk pelaksanaan metode Calldata Write, perlu menggunakan metode RPC:eth_sendRawTransaction.

Mari kita gunakan alat debugging dan uji di Ethereum mainnet di:

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}
function toInnerAmount(s, decimals) {
    return (BigDecimal(s)*BigDecimal(Math.pow(10, decimals))).toFixed(0)
}
function main() {
    // ABI for WETH contracts
    var abiWETH = `[{"constant":true,"inputs":[],"name":"name","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"guy","type":"address"},{"name":"wad","type":"uint256"}],"name":"approve","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":true,"inputs":[],"name":"totalSupply","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"src","type":"address"},{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transferFrom","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[{"name":"wad","type":"uint256"}],"name":"withdraw","outputs":[],"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":"","type":"address"}],"name":"balanceOf","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":false,"inputs":[{"name":"dst","type":"address"},{"name":"wad","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"payable":false,"stateMutability":"nonpayable","type":"function"},{"constant":false,"inputs":[],"name":"deposit","outputs":[],"payable":true,"stateMutability":"payable","type":"function"},{"constant":true,"inputs":[{"name":"","type":"address"},{"name":"","type":"address"}],"name":"allowance","outputs":[{"name":"","type":"uint256"}],"payable":false,"stateMutability":"view","type":"function"},{"payable":true,"stateMutability":"payable","type":"fallback"},{"anonymous":false,"inputs":[{"indexed":true,"name":"src","type":"address"},{"indexed":true,"name":"guy","type":"address"},{"indexed":false,"name":"wad","type":"uint256"}],"name":"Approval","type":"event"},{"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"},{"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 contract address
    var wethAddress = "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"

    // Register ABI for WETH contract
    exchange.IO("abi", wethAddress, abiWETH)

    // The wallet address of the currently configured exchange object
    var walletAddress = exchange.IO("address")

    // Coded WETH contract's deposit method call
    var calldataForDeposit = exchange.IO("encode", wethAddress, "deposit")
    Log("calldataForDeposit:", "0x" + calldataForDeposit)

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

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

    // Call the deposit method to change ETH to WETH, you need to transfer ETH, here we convert 0.01ETH to a hexadecimal value in 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,
    }

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

    // Construct a transaction
    var transaction = {
        "to": wethAddress,
        "value": toAmount("0x" + innerAmount, 0),   // Convert to decimal
        "data": "0x" + calldataForDeposit,
        "gasLimit": toAmount(gasLimit, 0),   // Convert to decimal
        "gasPrice": toAmount(gasPrice, 0),   // Convert to decimal
        "nonce": toAmount(nonce, 0),         // Convert to decimal
        "chainId": 1,                        // Ethereum mainnet Id
    }
    Log("transaction:", transaction)

    // Signature, your key is replaced with your private key
    var signedTx = Encode("signTx", "string", "hex", JSON.stringify(transaction), "hex", "0x" + "your key")
    Log("signedTx:", "0x" + signedTx)

    // Call eth_sendRawTransaction to send a transaction
    var ret = exchange.IO("api", "eth", "eth_sendRawTransaction", "0x" + signedTx)
    return ret 
}

Jalankan di alat debugging:

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

Eksekusivar ret = exchange.IO("api", "eth", "eth_sendRawTransaction", "0x" + signedTx)fungsi dan Hash Transaksi yang dikembalikan adalah:0x2ff585504b0fe59b0122f696e8808abfe2f3ce263448066533f3bb8a4f55e8e6.eth_sendRawTransactioncall mengeksekusi calldata di dalamnya, memanggildepositmetode kontrak WETH untuk menukar 0,005 ETH yang dikirim untuk WETH.

Mendengarkan mempool

Sebelum transaksi pengguna dikemas ke dalam blockchain Ethereum oleh penambang, semua transaksi akan dikumpulkan dalamMempool(transaction memory pool), di mana para penambang juga mencari transaksi dengan biaya tinggi yang akan dikemas terlebih dahulu, untuk memaksimalkan manfaat penambangan.

Beberapa skrip transaksi juga akan mengendusMempoolmisalnya, jika sebuah transaksi diatur dengan slippage pertukaran yang tinggi, transaksi bisa menjadi subyek dari "serangan sandwich" oleh skrip transaksi ini.pending(menunggu, untuk dikemas) transaksi diMempool?

Mendengarkan dengan protokol REST

Gunakan metode RPC yang kita pelajari sebelumnya:eth_getBlockByNumber, tapi kita tidak lulus spesifikblockNumberkali ini, kita menggunakan"pending" tag.

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])
        }
    }
}

Jalankan di alat debugging:

2023-06-18 19:23:05		Info	{"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		Info	{"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		Info	{"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"}
...

Ekstrak satu bagian dari data:

{
	"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"
}

Mendengarkan dengan protokol WebSocket

Dalam FMZ Quant Trading Platform, kami menggunakanDialfungsi untuk membuatWebSocketkoneksi, Anda dapat memeriksaFMZ APIdokumentasi untuk mempelajariDial function.

Kode tes dalam bagian ini berjalan di lingkungan Ethereum mainnet, dan lebih mudah untuk menggunakan FMZ Quant untuk menguji dalam perdagangan langsung karena penggunaan komunikasi protokol WebSocket.

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

SelainnewPendingTransactions, Anda juga bisa berlangganannewHeads, logs.

Menerima data yang didorong olehWebSocketkoneksi:

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

Kemudian pertanyaan lebih lanjuttransactionyang menurutnya:"result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3"Untuk tujuan tertentu.transaction, kita menggunakan metode Ethereum RPCeth_getTransactionByHashuntuk pertanyaan.

var ws = null 

function main () {    
    // {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["xxxxx"]}  , "xxxxx" is the specific message to subscribe to
    var payload = {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]}
    
    // wss://mainnet.infura.io/ws/v3/xxxxx , "xxxxx" is your 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 link infura failed!"
    }
    
    // eth_getTransactionByHash call count
    var getTransactionCounter = 0

    var beginTS = new Date().getTime()

    // Loop to get messages
    while (true) {
        // Receive push messages
        var data = ws.read()
        if (data) {
            var ts = new Date().getTime()

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

            // Check transaction details based on 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("Disconnect WS connection")
    ws.close()
}

Buat live trading untuk menjalankan kode di atas, Anda dapat menerima data didorong oleh koneksi WebSocket, data didorong terus-menerus, kita mengekstrak salah satu dari mereka -transaction:

{
	"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"
}

Data log yang diekstrak (sebagian dihilangkan):

2023-06-18 16:20:07		Info	Disconnect WS connection
2023-06-18 16:20:07		Info	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		Info	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		Info	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		Info	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"}
...

Dekoding Detail Transaksi

Dalam kursus sebelumnya, kami menulis program pemantauan untuk memantau transaksi yang sedang menunggu di Ethereum, mendapatkan hash transaksi yang didorong melalui protokol WebSocket, dan kemudian menanyakan detail transaksi tertentu berdasarkan hash transaksi.

Selanjutnya kita ingin melakukan analisis lebih lanjut dariinputdata lapangan dalam data detail transaksi.inputdata lapangan terlihat seperti kekacauan data hexadecimal, tapi sebenarnya mengkodekan isi transaksi: termasuk fungsi yang dipanggil, dan parameter yang dimasukkan, dll.

Setelah pengujian berulang dan ekstensif, kami menemukan bahwa ketepatan waktu dan jumlah data yang didorong oleh koneksi WebSocket memiliki banyak hubungannya dengan node RPC yang saat ini digunakan, dan data yang didorong yang diterima oleh dua layanan node RPC yang berbeda (misalnya, infura, ALCHEMY) ketika membuat koneksi WebSocket pada saat yang sama tidak persis sama, dan karena skenario saat ini menghasilkan sejumlah besar permintaan, kita masih perlu menggunakan layanan RPC yang lebih stabil dan lebih cepat.eth_getTransactionByHashuntuk query, Anda sering mendapatkan nilai null (diuji di FMZ, node.js).

Kami menggunakanalchemyRPC node kali ini:wss://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN. Node mendukung protokol WebSocket dan REST.

Kami memantaumulticall(uint256,bytes[])metode router kontrak pintar dari Uniswap pertukaran terdesentralisasi, jadi kita perlu menghitung hash tanda tangan fungsi dari metode pertama.

// Take the first 8 characters of the complete hash
// multicall: 0x5ae401dc
var sigHash = "0x" + Encode("keccak256", "string", "hex", "multicall(uint256,bytes[])").slice(0, 8)

Berdasarkan contoh dalam pelajaran sebelumnya, kami telah membuat beberapa modifikasi. Ketika menerima pesan didorong oleh koneksi WebSocket, data terbaru diterima menggunakanvar data = ws.read(-2)metode, danread()parameter fungsi ditetapkan menjadi -2 untuk menunjukkan bahwa data terbaru dikembalikan segera.Transactionyang mengandungmulticallpanggilan, menggunakanif (tx && tx.input.indexOf(sigHash) ! == -1)untuk menentukan filter.

2 fungsi kustom perlu dirancang:

  • calcAllFuncSigHash(): Menghitung hash tanda tangan untuk semua metode berdasarkan ABI.
  • decodeCall()Fungsi dekoding.

Selanjutnya, ketikamulticallpanggilan terdeteksi, operasi dekoding dapat dimulai, dan parameter darimulticallmetode di-decode untuk pertama kalinya:deadlinedandata. deadlineadalah cap waktu yang lebih dipahami, dandataadalah kode laincalldata, jadi Anda masih perlu terus menggunakandecodeCall()fungsi untuk mendekode itu.

Contoh implementasi lengkap:

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"])
                            // Prototype
                            argPrototype.push("tuple" + "(" + protoType.join() + ")")
                        } else {
                            arr.push(inputType)
                            arrName.push(ele["inputs"][j]["name"])
                            // Prototype
                            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 "The decoded args are not equal to the 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" is the specific message of the subscription
    var payload = {"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]}
    
    // Use the alchemy service
    ws = Dial("wss://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN" + "|reconnect=true&payload=" + JSON.stringify(payload))
    if (!ws) {
        throw "websocket link to alchemy failed!"
    }
    
    // eth_getTransactionByHash call count
    var getTransactionCounter = 0
    
    // Start Timestamp
    var beginTS = new Date().getTime()
    
    // Calculate function signature hash
    var sigHash = "0x" + Encode("keccak256", "string", "hex", "multicall(uint256,bytes[])").slice(0, 8)
    Log("sigHash:", sigHash)

    // Loop for messages
    while (true) {
        var msg = ""
        var recv = null
        // Receive pushed messages, use the read parameter -2, and return the latest data immediately
        var data = ws.read(-2)
        if (data && data != "") {
            var ts = new Date().getTime()

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

            // Check transaction details based on 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) {
                    // Decode transaction details
                    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"][

Berkaitan

Lebih banyak