Contract name: BoredApeYachtClub
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
}
Como assinar uma transação Ethereum?
No FMZ Quant Trading Platform, usamos oEncode()
função para executar o cálculo de assinatura, o exemplo específico que escrevemos no curso subsequente
Para a execução docalldata
do método Read, usamos o método RPC aprendido anteriormente:eth_call
Explicamos aeth_call
O método RPC do Ethereum só fez uma demonstração doWrite
método do contrato inteligente, nesta seção, usamos ocalldata
Para demonstrar a execução do contrato inteligente, vamos usar o método de chamada de leitura.balanceOf
método do contrato WETH para ler o saldo atual dos tokens WETH na carteira.
Usamos a ferramenta de depuração para testar na rede principal do Ethereum em:
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
}
Execute na ferramenta de depuração:
2023-06-15 11:51:31 Info wethBalance: 0.015
2023-06-15 11:51:31 Info calldataForDeposit: 0x70a082310000000000000000000000006b3f11d807809b0b1e5e3243df04a280d9f94bf4
Se o método de um contrato inteligente tem um valor de retorno, você pode usar oexchange.IO("decode", ...)
Você pode ver que a passagemcalldata
O método é o mesmo que chamar os contratos inteligentesbalanceOf
método diretamente, obtendo o saldo WETH de 0,015 WETH para a minha carteira de teste.
Para a execução dos dados de chamada do método Write, é necessário utilizar o método RPC:eth_sendRawTransaction
.
Vamos usar a ferramenta de depuração e testá-lo na rede principal do Ethereum em:
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
}
Execute na ferramenta de depuração:
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
Execuçãovar ret = exchange.IO("api", "eth", "eth_sendRawTransaction", "0x" + signedTx)
função e o Hash de transação devolvido é:0x2ff585504b0fe59b0122f696e8808abfe2f3ce263448066533f3bb8a4f55e8e6
. Oeth_sendRawTransaction
call executa os dados de chamada nele, chamando odeposit
método do contrato WETH para trocar os 0,005 ETH enviados por WETH.
Antes de uma transação do usuário ser empacotada no blockchain Ethereum pelos mineradores, todas as transações serão agrupadas emMempool
(pool de memória de transacções), onde
Alguns scripts de transação também vão cheirarMempool
Por exemplo, se uma transação é definida com um alto deslizamento de câmbio, a transação pode ser sujeita a um "ataque de sanduíche" por esses transacções.pending
(pendentes, a pacotar)Mempool
?
Use o método RPC que aprendemos antes:eth_getBlockByNumber
, mas não passamos o específicoblockNumber
Desta vez, usamos o"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])
}
}
}
Execute na ferramenta de depuração:
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"}
...
Extrair um pedaço dos dados:
{
"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"
}
No FMZ Quant Trading Platform, usamos oDial
função para criarWebSocket
ligações, você pode verificar oFMZ API
A documentação necessária para aprender aDial
function.
O código de teste nesta seção é executado no ambiente da rede principal Ethereum, e é mais fácil usar o FMZ Quant para testar na negociação ao vivo devido ao uso da comunicação do protocolo WebSocket.
{"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]}
Além denewPendingTransactions
, também pode subscrevernewHeads
, logs
.
Recepção de dados empurrados porWebSocket
ligações:
{
"jsonrpc": "2.0",
"method": "eth_subscription",
"params": {
"subscription": "0x2c5c087b4aa188e008f4747828ef4e61",
"result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3"
}
}
Então, mais perguntas.transaction
De acordo com o qual:"result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3"
Para um determinadotransaction
, usamos o método Ethereum RPCeth_getTransactionByHash
para fazer uma consulta.
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()
}
Criar uma negociação ao vivo para executar o código acima, você pode receber os dados empurrados pela conexão WebSocket, os dados são empurrados constantemente, extraímos um deles -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"
}
Dados de registo extraídos (omissão parcial):
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"}
...
No curso anterior, escrevemos um programa de monitoramento para monitorar transações pendentes no Ethereum, obter o hash da transação empurrado através do protocolo WebSocket, e depois consultar os detalhes da transação específica com base no hash da transação.
Em seguida, queremos fazer mais análise doinput
dados de campo nos dados de detalhes da transacção.input
dados de campo parece uma confusão de dados hexadecimais, mas ele realmente codifica o conteúdo da transação: incluindo as funções chamadas, e os parâmetros inseridos, etc.
Após testes repetidos e extensos, descobrimos que a puntualidade e a quantidade de dados empurrados pela conexão WebSocket tem muito a ver com o nó RPC atualmente em uso, e os dados empurrados recebidos por dois serviços de nós RPC diferentes (por exemplo, infura, ALCHEMY) ao criar uma conexão WebSocket ao mesmo tempo não são exatamente os mesmos, e como o cenário atual gera um grande número de solicitações, ainda precisamos usar um serviço RPC mais estável e mais rápido. A conexão WebSocket também empurra muitos hashes de transação que estão pendentes há muito tempo, e ao usareth_getTransactionByHash
para fazer uma consulta, você geralmente obtém um valor nulo (testado no FMZ, node.js).
Usamos oalchemy
Nodo RPC desta vez:wss://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN
O nó suporta ambos os protocolos WebSocket e REST.
Monitorizamos omulticall(uint256,bytes[])
método do contrato inteligente do roteador da troca descentralizada Uniswap, então precisamos calcular o hash da assinatura da função do método primeiro.
// Take the first 8 characters of the complete hash
// multicall: 0x5ae401dc
var sigHash = "0x" + Encode("keccak256", "string", "hex", "multicall(uint256,bytes[])").slice(0, 8)
Com base no exemplo na lição anterior, fizemos algumas modificações. Quando recebemos mensagens empurradas pela conexão WebSocket, os dados mais recentes são recebidos usandovar data = ws.read(-2)
O método e oread()
O parâmetro da função é definido como -2 para indicar que os dados mais recentes são devolvidos imediatamente.Transaction
que contém omulticall
chamadas, utilizandoif (tx && tx.input.indexOf(sigHash) ! == -1)
para determinar o filtro.
É necessário conceber duas funções personalizadas:
calcAllFuncSigHash()
: Calcular o hash da assinatura para todos os métodos baseados no ABI.decodeCall()
Função de decodificação.Em seguida, quando omulticall
A chamada é detectada, a operação de decodificação pode ser iniciada, e os parâmetros domulticall
O método é decodificado pela primeira vez:deadline
edata
. deadline
É um selo de data e hora mais compreendido, edata
é outra codificadacalldata
, por isso ainda precisa de continuar a usar odecodeCall()
Função para decodificá-lo.
Exemplo de uma implementação completa:
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"][