Contract name: BoredApeYachtClub
L'adresse du contrat: 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
}
Comment signer une transaction Ethereum?
Dans la plateforme de trading FMZ Quant, nous utilisons leEncode()
fonction pour effectuer le calcul de la signature, l'exemple spécifique que nous écrivons dans le cours suivant
Pour l'exécution de lacalldata
de la méthode de lecture, nous utilisons la méthode RPC apprise précédemment:eth_call
Nous avons expliqué leeth_call
La méthode RPC de l'Ethereum n'a fait qu'une démonstration de laWrite
Dans cette section, nous utilisons la méthode du contrat intelligentcalldata
L'appel de la méthode de lecture est utilisé pour démontrer l'exécution du contrat intelligent.balanceOf
méthode du contrat WETH pour lire le solde actuel des jetons WETH dans le portefeuille.
Nous utilisons l'outil de débogage pour tester sur le réseau Ethereum à:
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
}
Exécutez dans l'outil de débogage:
2023-06-15 11:51:31 Info wethBalance: 0.015
2023-06-15 11:51:31 Info calldataForDeposit: 0x70a082310000000000000000000000006b3f11d807809b0b1e5e3243df04a280d9f94bf4
Si la méthode d'un contrat intelligent a une valeur de retour, vous pouvez utiliser leexchange.IO("decode", ...)
Vous pouvez voir que le passagecalldata
La méthode est la même que d'appeler les contrats intelligentsbalanceOf
la méthode directement, obtenir le solde WETH de 0,015 WETH pour mon portefeuille de test.
Pour l'exécution des données d'appel de la méthode Ecrire, il est nécessaire d'utiliser la méthode RPC:eth_sendRawTransaction
.
Utilisons l'outil de débogage et testons-le sur le réseau Ethereum à:
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
}
Exécutez dans l'outil de débogage:
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
Exécutervar ret = exchange.IO("api", "eth", "eth_sendRawTransaction", "0x" + signedTx)
fonction et le Hash de transaction retourné est:0x2ff585504b0fe59b0122f696e8808abfe2f3ce263448066533f3bb8a4f55e8e6
Leeth_sendRawTransaction
appel exécute les données d'appel, appelant ledeposit
méthode du contrat WETH pour échanger les 0,005 ETH envoyés contre WETH.
Avant qu'une transaction d'un utilisateur ne soit intégrée à la blockchain Ethereum par les mineurs, toutes les transactions seront regroupées dansMempool
L'expérience montre qu'il est possible d'obtenir des gains de prix plus élevés en fonction de la valeur de l'opération (bassin de mémoire de transaction), où les mineurs recherchent également des transactions avec des frais élevés à emballer en premier, afin de maximiser les avantages de l'exploitation minière.
Certains scripts de transaction vont aussi reniflerMempool
Par exemple, si une transaction est réglée avec un glissement de change élevé, la transaction pourrait être soumise à une "attaque sandwich" par ces scripts de transaction.pending
(en attente, à emballer)Mempool
?
Utilisez la méthode RPC que nous avons apprise auparavant:eth_getBlockByNumber
, mais nous ne passons pas le spécifiqueblockNumber
Cette fois, nous utilisons le"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])
}
}
}
Exécutez dans l'outil de débogage:
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"}
...
Extraire une partie des données:
{
"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"
}
Dans la plateforme de trading FMZ Quant, nous utilisons leDial
fonction de créerWebSocket
connexions, vous pouvez vérifier leFMZ API
Les résultats de l'étudeDial
function.
Le code de test dans cette section s'exécute dans l'environnement du mainnet Ethereum, et il est plus facile d'utiliser le FMZ Quant pour tester dans le trading en direct en raison de l'utilisation de la communication par protocole WebSocket.
{"jsonrpc": "2.0", "id": 1, "method": "eth_subscribe", "params": ["newPendingTransactions"]}
En plus denewPendingTransactions
, vous pouvez également vous abonner ànewHeads
, logs
.
Réception des données envoyées parWebSocket
connexions:
{
"jsonrpc": "2.0",
"method": "eth_subscription",
"params": {
"subscription": "0x2c5c087b4aa188e008f4747828ef4e61",
"result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3"
}
}
Puis une autre requêtetransaction
selon lequel:"result": "0x69c4251cecb814e17cfe7a5ee41742a616f9a4d1bbf245c49b186b1006fd14d3"
Pour une cause spécifique.transaction
, nous utilisons la méthode Ethereum RPCeth_getTransactionByHash
Pour demander.
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()
}
Créez un commerce en direct pour exécuter le code ci-dessus, vous pouvez recevoir les données poussées par la connexion WebSocket, les données sont poussées constamment, nous extraire l'un d'entre eux -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"
}
Données de journaux extraites (omission partielle):
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"}
...
Dans le cours précédent, nous avons écrit un programme de surveillance pour surveiller les transactions en attente sur Ethereum, obtenir le hachage de transaction poussé via le protocole WebSocket, puis interroger les détails de transaction spécifiques basés sur le hachage de transaction.
Ensuite, nous voulons faire une analyse plus approfondie duinput
les données de champ dans les données de détail de la transaction.input
Les données de champ ressemblent à un mélange de données hexadécimales, mais elles codent en fait le contenu de la transaction: y compris les fonctions appelées, les paramètres entrés, etc.
Après des tests répétés et approfondis, nous avons constaté que la rapidité et la quantité de données poussées par la connexion WebSocket ont beaucoup à voir avec le nœud RPC actuellement utilisé, et les données poussées reçues par deux différents services de nœuds RPC (par exemple, infura, ALCHEMY) lors de la création d'une connexion WebSocket en même temps ne sont pas exactement les mêmes, et comme le scénario actuel génère un grand nombre de requêtes, nous devons toujours utiliser un service RPC plus stable et plus rapide.eth_getTransactionByHash
Pour effectuer une requête, vous obtenez souvent une valeur nulle (testée sur FMZ, node.js).
Nous utilisons lealchemy
Le nœud RPC cette fois:wss://eth-mainnet.g.alchemy.com/v2/oKmOQKbneVkxgHZfibs-iFhIlIAl6HDN
. Le nœud prend en charge à la fois les protocoles WebSocket et REST.
Nous surveillons lesmulticall(uint256,bytes[])
méthode du contrat intelligent du routeur de l'échange décentralisé Uniswap, nous devons donc d'abord calculer le hachage de la signature de la fonction de la méthode.
// Take the first 8 characters of the complete hash
// multicall: 0x5ae401dc
var sigHash = "0x" + Encode("keccak256", "string", "hex", "multicall(uint256,bytes[])").slice(0, 8)
Sur la base de l'exemple de la leçon précédente, nous avons apporté quelques modifications.var data = ws.read(-2)
La méthode et laread()
Le paramètre de fonction est réglé sur -2 pour indiquer que les dernières données sont renvoyées immédiatement.Transaction
qui contient lemulticall
appeler, en utilisantif (tx && tx.input.indexOf(sigHash) ! == -1)
pour déterminer le filtre.
Deux fonctions personnalisées doivent être conçues:
calcAllFuncSigHash()
: Calculer le hachage de la signature pour toutes les méthodes basées sur ABI.decodeCall()
Fonction de décryptage.Ensuite, lorsque lemulticall
l'appel est détecté, l'opération de décryptage peut commencer, et les paramètres dumulticall
La méthode est décodée pour la première fois:deadline
etdata
. deadline
est un horodatage mieux compris, etdata
est un autre codécalldata
, vous devez donc continuer à utiliser ledecodeCall()
fonction pour le décoder.
Exemple de mise en œuvre complète:
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"][