Contract name: BoredApeYachtClub
En el caso de las empresas que no cumplen con los requisitos de la presente Directiva, la Comisión podrá, en caso de que no cumplan los requisitos de la presente Directiva, adoptar medidas de control.
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
}
¿Cómo firmar una transacción de Ethereum?
En la Plataforma de Comercio Cuántico FMZ, utilizamos elEncode()
función para realizar el cálculo de la firma, el ejemplo específico que escribimos en el curso posterior
Para la ejecución de lacalldata
del método Read, usamos el método RPC aprendido anteriormente:eth_call
Para ejecutarlo.eth_call
El método RPC de Ethereum sólo hizo una demostración de laWrite
En esta sección, utilizamos el método de contrato inteligentecalldata
El método para demostrar la ejecución del contrato inteligente es el método de llamada de lectura.balanceOf
método del contrato WETH para leer el saldo actual de fichas WETH en la billetera.
Usamos la herramienta de depuración para probar en la red principal de Ethereum en:
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
}
Ejecute en la herramienta de depuración:
2023-06-15 11:51:31 Info wethBalance: 0.015
2023-06-15 11:51:31 Info calldataForDeposit: 0x70a082310000000000000000000000006b3f11d807809b0b1e5e3243df04a280d9f94bf4
Si el método de un contrato inteligente tiene un valor de retorno, se puede utilizar elexchange.IO("decode", ...)
Puede ver que el pasocalldata
El método es el mismo que llamar a los contratos inteligentesbalanceOf
método directamente, obteniendo el saldo WETH de 0,015 WETH para mi cartera de prueba.
Para la ejecución de los datos de llamada del método Write, es necesario utilizar el método RPC:eth_sendRawTransaction
.
Usemos la herramienta de depuración y probémosla en la red principal de Ethereum en:
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
}
Ejecute en la herramienta de depuración:
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
Ejecutarvar ret = exchange.IO("api", "eth", "eth_sendRawTransaction", "0x" + signedTx)
la función y el hash de transacción devuelto es:0x2ff585504b0fe59b0122f696e8808abfe2f3ce263448066533f3bb8a4f55e8e6
El.eth_sendRawTransaction
call ejecuta los datos de llamada en él, llamando a ladeposit
método del contrato WETH para intercambiar los 0,005 ETH enviados por WETH.
Antes de que una transacción de usuario sea empaquetada en la cadena de bloques de Ethereum por los mineros, todas las transacciones se agruparán enMempool
En el caso de las transacciones con un precio de gas más alto, es más probable que las transacciones con tarifas más altas sean empaquetadas primero, para maximizar los beneficios de la minería.
Algunos scripts de transacción también olfatearánMempool
Por ejemplo, si una transacción se establece con un alto deslizamiento de cambio, la transacción podría estar sujeta a un "ataque de sándwich" por estos transaccionescripts.pending
(en trámite, en proceso de envasado)Mempool
?
Utilice el método RPC que aprendimos antes:eth_getBlockByNumber
, pero no pasamos el específicoblockNumber
Esta vez, usamos el"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])
}
}
}
Ejecute en la herramienta de depuración:
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"}
...
Extraer una pieza de los datos:
{
"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"
}
En la Plataforma de Comercio Cuántico FMZ, utilizamos elDial
función para crearWebSocket
las conexiones, puede comprobar elFMZ API
Documentación para aprender elDial
function.
El código de prueba en esta sección se ejecuta en el entorno de la red principal de Ethereum, y es más fácil usar el FMZ Quant para probar en el comercio en vivo debido al uso de la comunicación del protocolo WebSocket.
{"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]}
Además denewPendingTransactions
, también puede suscribirse anewHeads
, logs
.
Recibiendo datos enviados porWebSocket
las conexiones:
{
"jsonrpc": "2.0",
"method": "eth_subscription",
"params": {
"subscription": "0x2c5c087b4aa188e008f4747828ef4e61",
"result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3"
}
}
Entonces más consultatransaction
de acuerdo con lo cual:"result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3"
Para un tipo específicotransaction
, usamos el método Ethereum RPCeth_getTransactionByHash
Para hacer una 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()
}
Crear un comercio en vivo para ejecutar el código anterior, se puede recibir los datos empujados por la conexión WebSocket, los datos se empujan constantemente, extraemos uno de ellos -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"
}
Datos del registro extraídos (omisión 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"}
...
En el curso anterior, escribimos un programa de monitoreo para monitorear las transacciones pendientes en Ethereum, obtener el hash de transacción impulsado a través del protocolo WebSocket, y luego consultar los detalles específicos de la transacción basados en el hash de la transacción.
A continuación, queremos hacer más análisis de lainput
Los datos de campo en los datos de detalles de la transacción.input
Los datos de campo se ven como una mezcla de datos hexadecimales, pero en realidad codifican el contenido de la transacción: incluyendo las funciones llamadas, y los parámetros introducidos, etc.
Después de pruebas repetidas y extensas, encontramos que la puntualidad y la cantidad de datos enviados por la conexión WebSocket tiene mucho que ver con el nodo RPC actualmente en uso, y los datos enviados recibidos por dos servicios de nodos RPC diferentes (por ejemplo, infura, ALCHEMY) al crear una conexión WebSocket al mismo tiempo no son exactamente los mismos, y dado que el escenario actual genera una gran cantidad de solicitudes, todavía necesitamos usar un servicio RPC más estable y rápido.eth_getTransactionByHash
Para hacer una consulta, a menudo se obtiene un valor nulo (probado en FMZ, node.js).
Usamos elalchemy
Nodo RPC esta vez:wss://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN
El nodo admite tanto los protocolos WebSocket como REST.
Nosotros monitorizamos elmulticall(uint256,bytes[])
método del contrato inteligente del router del intercambio descentralizado Uniswap, por lo que necesitamos calcular el hash de la firma de la función del método primero.
// Take the first 8 characters of the complete hash
// multicall: 0x5ae401dc
var sigHash = "0x" + Encode("keccak256", "string", "hex", "multicall(uint256,bytes[])").slice(0, 8)
Basándonos en el ejemplo de la lección anterior, hemos hecho algunas modificaciones. Cuando se reciben mensajes impulsados por la conexión WebSocket, los datos más recientes se reciben utilizandovar data = ws.read(-2)
El método y laread()
El parámetro de la función se establece en -2 para indicar que los últimos datos se devuelven inmediatamente.Transaction
que contiene elmulticall
llamadas, utilizandoif (tx && tx.input.indexOf(sigHash) ! == -1)
para determinar el filtro.
Hay que diseñar 2 funciones personalizadas:
calcAllFuncSigHash()
: Calcular el hash de la firma para todos los métodos basados en ABI.decodeCall()
Función de decodificación.A continuación, cuando elmulticall
Cuando se detecta la llamada, puede iniciarse la operación de decodificación y los parámetros de lamulticall
el método se decodifica por primera vez:deadline
ydata
. deadline
es una marca de tiempo que se entiende mejor, ydata
es otro codificadocalldata
, por lo que aún debe seguir usando eldecodeCall()
función para descifrarlo.
Ejemplo de una implementación 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"][