avatar of 发明者量化-小小梦 发明者量化-小小梦
フォロー ダイレクトメッセージ
4
フォロー
1282
フォロワー

FMZを使用してEthereumベースのWeb3開発を簡単に開始する

作成日:: 2023-03-28 13:32:48, 更新日:: 2024-11-11 22:28:24
comments   0
hits   3885

831ec7”, “1INCH”: “0x111111111117dC0aa78b770fA6A738034120C302”, “USDC”: “0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48”, “DAI”: “0x6b175474e89094c44da98b954eedeac495271d0f”, } for (let name in tokenAddressMap) { ex.addToken(name, tokenAddressMap[name]) }


  特定の取引ペアの交換プール価格を取得して印刷したい場合は、次のようにします。`ex`オブジェクトのメンバー関数`getPrice()`次のように書くことができます。

  ```javascript
    Log(ex.getPrice('ETH_USDT'))
    Log(ex.getPrice('1INCH_USDT'))

償還操作を実行する場合は、exオブジェクトのメンバー関数swapToken()交換を実行します:

    // swap 0.01 ETH to USDT
    Log(ex.swapToken('ETH', 0.01, 'USDT'))
    let usdtBalance = ex.balanceOf('USDT')
    Log("balance of USDT", usdtBalance)

    // swap USDT to DAI then DAI to ETH
    Log(ex.swapToken('USDT', usdtBalance, 'DAI,ETH'))

イベントを取得する

この章では、Inventor Quantitative Trading Platform を使用してスマート コントラクトによってリリースされたイベントを読み取る方法を学習します。スマート コントラクトによってリリースされたイベントは、Ethereum 仮想マシンのログに保存されます。

eth_getLogs

スマートコントラクトによってリリースされたイベントを照会するには、EthereumのRPCメソッドを使用する必要があります。eth_getLogs、オンチェーン ログ データを取得します。Ethereum RPC ノードを呼び出す方法については、前回のコースですでに説明しました。 例えば、WETH契約イベントはFMZを使用してコード化できるデバッグツールテストでは、交換オブジェクトによって構成されたRPCノードは、Ethereumのメインネットワークノードであり、eth_getLogsメソッドを使用する際に3つのパラメータを指定した。fromBlocktoBlockaddressでは、fromBlock および toBlock パラメータを使用して、クエリをブロック内のデータに制限します。

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function toInnerAmount(n, decimals) {
    return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}

function main() {
    // getBlockNumber
    var blockNumber = exchange.IO("api", "eth", "eth_blockNumber")
    Log("blockNumber:", blockNumber)

    // get logs
    var fromBlock = "0x" + (toAmount(blockNumber, 0) - 1).toString(16)
    var toBlock = "0x" + toAmount(blockNumber, 0).toString(16)
    var params = {
        "fromBlock" : fromBlock,
        "toBlock" : toBlock,
        "address" : "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"   // WETH合约的地址
    }
    var logs = exchange.IO("api", "eth", "eth_getLogs", params)

    // 由于数据量比较大,如果使用Log函数打印,数据会被截断。使用return将完整数据返回在页面「函数结果」编辑框中
    return logs   
}

ログデータを取得します。データが大きいため、一部の内容を省略します。

[{
	"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
	"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
	"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
	"transactionIndex": "0x0",
	"removed": false,
	"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
	"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
	"blockNumber": "0x109b1cc",
	"logIndex": "0x0"
}, {
	"data": "0x00000000000000000000000000000000000000000000000008ea20cdea027c00",
	"logIndex": "0x5",
	"topics": ["0xe1fffcc4923d04b559f4d29a8bfc6cda04eb5b0d3c460751c2402c5c5cc9109c", "0x0000000000000000000000007a250d5630b4cf539739df2c5dacb4c659f2488d"],
	"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
	"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
	"blockNumber": "0x109b1cc",
	"removed": false,
	"transactionHash": "0xace3afa02e8af5d1ef6fc1635fbdf7bee37624547937ea5272c23968dd034c09",
	"transactionIndex": "0x1"
},

...

{
	"blockNumber": "0x109b1cd",
	"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
	"data": "0x00000000000000000000000000000000000000000000000002c053531ab8a000",
	"logIndex": "0xd3",
	"removed": false,
	"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000001111111254eeb25477b68fb85ed929f73a960582", "0x000000000000000000000000252ba9b5916171dbdadd2cec7f91875a006955d0"],
	"transactionHash": "0x3012b82891f85b077cfe1c12cb9722b93c696ef2c37d67981ccddcc9c3396aca",
	"transactionIndex": "0x8d",
	"blockHash": "0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9"
}, {
	"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],
	"transactionIndex": "0x91",
	"logIndex": "0xdb",
	"removed": false,
	"blockNumber": "0x109b1cd",
	"data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
	"transactionHash": "0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048",
	"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
	"blockHash": "0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9"
}, {
	"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
	"blockHash": "0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9",
	"blockNumber": "0x109b1cd",
	"logIndex": "0xde",
	"removed": false,
	"topics": ["0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],
	"data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
	"transactionHash": "0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048",
	"transactionIndex": "0x91"
}]

ログデータにはさまざまなイベントがあることがわかります。TransferイベントではこれらのデータをTransferイベントは除外されます。

ログを取得する

イーサリアムのログは2つの部分に分かれています: 1. トピックtopics2. データdata

  • テーマtopics によるeth_getLogs章のテストのコード実行結果を例に挙げます。topicsフィールドのデータは次のとおりです。
  "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],  

これtopics(トピック) フィールドの値は、イベントを記述する配列構造です。配列の長さは 4 を超えないことが規定されており、最初の要素はイベントの署名ハッシュ値です。 Inventor定量取引プラットフォームでは、Encodeこの関数は、次のコードを使用して署名ハッシュ値を計算できます。

  function main() {
      var eventFunction = "Transfer(address,address,uint256)"
      var eventHash = Encode("keccak256", "string", "hex", eventFunction)
      Log("eventHash:", "0x" + eventHash)
      // eventHash: 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef
  }

計算されたTransfer(address,address,uint256)keccak256ハッシュ値(16進数エンコード)は0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef

topicsフィールドの値は配列構造であり、2 番目の要素と 3 番目の要素は次のとおりです。

  • 送信先住所from

  • 受取住所to

  • データdata

dataフィールドのデータは次のとおりです。

  "data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",

イベント内の一部のパラメータ(スマートコントラクトのSolidityコードでインデックス宣言されていないパラメータ)は、data一部。

このデータを解析する0x0000000000000000000000000000000000000000000000000164f2434262e1cc

  function toAmount(s, decimals) {
      return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
  }

  function main() {
      var value = "0x0000000000000000000000000000000000000000000000000164f2434262e1cc"
      Log(toAmount(value, 0) / 1e18)  // 0.10047146239950075
  }

データ: 0.10047146239950075、dataデータは振込金額に対応します。


上記の内容を説明して実践したら準備完了です。ログの取得を開始できます。

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function toInnerAmount(n, decimals) {
    return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}

function main() {
    // getBlockNumber
    var blockNumber = exchange.IO("api", "eth", "eth_blockNumber")
    Log("blockNumber:", blockNumber)

    // get logs
    var fromBlock = "0x" + (toAmount(blockNumber, 0) - 1).toString(16)
    var toBlock = "0x" + toAmount(blockNumber, 0).toString(16)
    var params = {
        "fromBlock" : fromBlock,
        "toBlock" : toBlock,
        "address" : "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
    }
    var logs = exchange.IO("api", "eth", "eth_getLogs", params)

    // 遍历logs
    var eventFunction = "Transfer(address,address,uint256)"
    var eventHash = "0x" + Encode("keccak256", "string", "hex", eventFunction)
    Log("eventHash:", eventHash)

    var counter = 0
    for (var i = logs.length - 1; i >= 0 && counter < 10; i--) {
        if (logs[i].topics[0] == eventHash) {
            Log("Event Transfer, data:", toAmount(logs[i].data, 0) / 1e18, ", blockNumber:", toAmount(logs[i].blockNumber, 0), ", transactionHash:", logs[i].transactionHash,
              ", log:", logs[i])
            counter++
        }
    }
}

存在するhttps://etherscan.io/クエリについて:

FMZを使用してEthereumベースのWeb3開発を簡単に開始する

FMZ デバッグ ツールでテスト コードを実行した結果:

FMZを使用してEthereumベースのWeb3開発を簡単に開始する

検索のニーズに応じて解析することもできるfromtoフィールドのデータ、例:

function main() {
    var from = "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c"
    var address = "0x" + exchange.IO("encodePacked", "address", from)
    Log("address:", address)
}

操作結果:

address: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c

契約イベントを監視する

なぜならデバッグツールコードのテストは短時間しかできず、内容はコード実行後にのみ出力されます。リアルタイムでログを表示・出力することは不可能です。このセクションでは、Inventor Quantitative Trading Platform を使用して、テスト用の実際のアカウントを作成します。

ここではイーサリアムメインネットを使って監視してみましょうUSDTこの通貨契約Transfer(address,address,uint256)イベントでは、前回の授業で学んだことを基に、スマート コントラクト イベントを継続的に監視する例を設計して記述しました。

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function toInnerAmount(n, decimals) {
    return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}

function addEventListener(contractAddress, event, callBack) {
    var self = {}
    self.eventHash = "0x" + Encode("keccak256", "string", "hex", event)
    self.contractAddress = contractAddress
    self.latestBlockNumber = 0
    self.fromBlockNumber = 0
    self.firstBlockNumber = 0
    /* TODO: test
    self.isFirst = true 
    */ 

    self.getBlockNumber = function() {
        var maxTry = 10
        for (var i = 0; i < maxTry; i++) {
            var ret = exchange.IO("api", "eth", "eth_blockNumber")
            if (ret) {
                return toAmount(ret, 0)
            }
            Sleep(5000)
        }
        throw "getBlockNumber failed"
    }

    self.run = function() {
        var currBlockNumber = self.getBlockNumber()
        var fromBlock = "0x" + self.fromBlockNumber.toString(16)
        var toBlock = "0x" + currBlockNumber.toString(16)
        var params = {
            "fromBlock" : fromBlock, 
            "toBlock" : toBlock, 
            "address" : self.contractAddress, 
            "topics" : [self.eventHash]
        }
        // Log("fromBlockNumber:", self.fromBlockNumber, ", currBlockNumber:", currBlockNumber, "#FF0000")
        
        var logs = exchange.IO("api", "eth", "eth_getLogs", params)
        if (!logs) {
            return 
        }

        for (var i = 0; i < logs.length; i++) {
            if (toAmount(logs[i].blockNumber, 0) > self.latestBlockNumber) {
                /* TODO: test
                if (self.isFirst) {
                    self.firstBlockNumber = toAmount(logs[i].blockNumber, 0)
                    Log("firstBlockNumber:", self.firstBlockNumber)
                    self.isFirst = false 
                }
                */

                callBack(logs[i])
            }
        }

        self.latestBlockNumber = currBlockNumber
        self.fromBlockNumber = self.latestBlockNumber - 1
    }

    self.latestBlockNumber = self.getBlockNumber()
    self.fromBlockNumber = self.latestBlockNumber - 1

    return self
}

var listener = null 
function main() {
    var event = "Transfer(address,address,uint256)"
    var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"
    var decimals = exchange.IO("api", contractAddress, "decimals")
    Log(exchange.IO("api", contractAddress, "name"), " decimals:", decimals)

    listener = addEventListener(contractAddress, event, function(log) {        
        var fromAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[1])
        var toAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[2])
        Log("Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0))
        
        /* TODO: test
        arrLog.push(log)
        */
    })

    while (true) {
        listener.run()
        Sleep(5000)
    }
}

/* TODO: test
var arrLog = []
function onexit() {
    Log("结束运行,验证记录")
    var firstBlockNumber = listener.firstBlockNumber
    var endBlockNumber = listener.latestBlockNumber

    Log("getLogs, from:", firstBlockNumber, " -> to:", endBlockNumber)
    var fromBlock = "0x" + (firstBlockNumber).toString(16)
    var toBlock = "0x" + (endBlockNumber).toString(16)
    var params = {
        "fromBlock" : fromBlock,
        "toBlock" : toBlock,
        "topics" : ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"],
        "address" : "0xdac17f958d2ee523a2206206994597c13d831ec7"
    }
    var logs = exchange.IO("api", "eth", "eth_getLogs", params)

    Log("arrLog:", arrLog.length)
    Log("logs:", logs.length)

    if (arrLog.length != logs.length) {
        Log("长度不等!")
        return 
    }
    
    for (var i = 0; i < arrLog.length; i++) {
        Log("判断blockNumber:", logs[i].blockNumber == arrLog[i].blockNumber, ",判断from:", logs[i].topics[1] == arrLog[i].topics[1], 
            "判断to:", logs[i].topics[2] == arrLog[i].topics[2])
    }
}
*/

実際のディスク操作:

FMZを使用してEthereumベースのWeb3開発を簡単に開始する

実行結果については、検証部分(TODO:テスト)もコード内に記述します。簡単な検証の後、USDT 契約が継続的に監視されていることがわかります。Transferイベントが記録され、データが記録されます。このデータを一度取得したイベント データと比較すると、データが一貫していることがわかります。

FMZを使用してEthereumベースのWeb3開発を簡単に開始する

イベントフィルタリング

前回のレッスン「契約イベントの監視」に基づいて、監視プロセスにフィルターを追加して、指定されたアドレスへの転送と指定されたアドレスからの転送を監視することで、これを拡張します。スマートコントラクトがログを作成する(つまりイベントをリリースする)と、ログデータはtopics最大 4 つのメッセージが含まれます。そこで私たちはフィルタリングルールを設計し、[[A1, A2, ...An], null, [C1], D]例えば。

1、[A1, A2, ...An]対応するtopics[0]位置データ。 2、null対応するtopics[1]位置データ。 3、[C1]対応するtopics[2]位置データ。 4、D対応するtopics[3]位置データ。

  • ケース構造の要素が設定されている場合nullフィルタリングされていないことを示します。例:null対応するtopics[1]、任意の値が一致します。
  • ケース構造内の要素が単一の値を設定する場合、位置が一致する必要があることを示します。例:[C1]対応するtopics[2]またはD対応するtopics[3]一致しないログは除外されます。
  • 条件構造の要素が配列である場合、配列内の少なくとも1つの要素が一致する必要があることを意味します。たとえば、[A1, A2, ...An]対応するtopics[0][A1, A2, ...An]いずれか1つとtopics[0]一致が見つかった場合、ログはフィルタリングされません。

取引所でのUSDTの送金を監視する

Binanceとの間の送金を監視するUSDT取引:

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function toInnerAmount(n, decimals) {
    return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}

function addEventListener(contractAddress, event, callBack) {
    var self = {}
    self.eventHash = "0x" + Encode("keccak256", "string", "hex", event)
    self.contractAddress = contractAddress
    self.latestBlockNumber = 0
    self.fromBlockNumber = 0
    self.firstBlockNumber = 0
    self.filters = []
    
    self.setFilter = function(filterCondition) {
        if (filterCondition.length > 4) {
            throw "filterCondition error"
        }

        self.filters.push(filterCondition)
        Log("设置过滤条件:", filterCondition)
    }

    self.getTokenBalanceOfWallet = function(walletAddress, tokenAddress, tokenDecimals) {
        var balance = exchange.IO("api", tokenAddress, "balanceOf", walletAddress)
        if (balance) {
            return toAmount(balance, tokenDecimals)
        }
        return null
    }

    self.getBlockNumber = function() {
        var maxTry = 10
        for (var i = 0; i < maxTry; i++) {
            var ret = exchange.IO("api", "eth", "eth_blockNumber")
            if (ret) {
                return toAmount(ret, 0)
            }
            Sleep(5000)
        }
        throw "getBlockNumber failed"
    }

    self.run = function() {
        var currBlockNumber = self.getBlockNumber()
        var fromBlock = "0x" + self.fromBlockNumber.toString(16)
        var toBlock = "0x" + currBlockNumber.toString(16)
        var params = {
            "fromBlock" : fromBlock, 
            "toBlock" : toBlock, 
            "address" : self.contractAddress, 
            "topics" : [self.eventHash]
        }
        
        var logs = exchange.IO("api", "eth", "eth_getLogs", params)
        if (!logs) {
            return 
        }

        for (var i = 0; i < logs.length; i++) {
            if (toAmount(logs[i].blockNumber, 0) > self.latestBlockNumber) {
                // 检查过滤条件,设置了过滤条件则执行判断
                if (self.filters.length != 0) {
                    // 初始过滤标记
                    var isFilter = true 
                    // 遍历过滤条件设置
                    for (var j = 0; j < self.filters.length; j++) {
                        // 取一个过滤设置,例如:[[A1, A2, ...An], null, [C1], D]
                        var cond = self.filters[j]

                        // 遍历这个过滤设置
                        var final = true
                        for (var topicsIndex = 0; topicsIndex < cond.length; topicsIndex++) {
                            // 拿到这个过滤设置中的某一个条件,如果是第一个条件:即要和topics[0]对比的数据
                            var condValue = cond[topicsIndex]

                            // 日志中的数据
                            if (topicsIndex > logs[i].topics.length - 1) {
                                continue 
                            }
                            var topicsEleValue = logs[i].topics[topicsIndex]
                            // 如果是Transfer事件,需要处理from和to
                            if (logs[i].topics[0] == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") {
                                if (topicsIndex == 1 || topicsIndex == 2) {
                                    topicsEleValue = "0x" + exchange.IO("encodePacked", "address", topicsEleValue)
                                }
                            }

                            // 如果condValue类型是数组,表示该位置的对比条件有多个,多个条件对比是逻辑或关系
                            if (Array.isArray(condValue) && condValue.length > 1) {
                                // 判断 condValue[0] == topicsEleValue || condValue[1] == topicsEleValue
                                final = final && condValue.some(element => element === topicsEleValue)
                            }else if (condValue === null) {
                                final = final && true
                            } else {
                                final = final && (condValue === topicsEleValue)
                            }
                        }
                        
                        if (final) {
                            isFilter = false 
                        }
                    }
                    
                    if (isFilter) {
                        continue
                    }
                }
                callBack(logs[i])
            }
        }

        self.latestBlockNumber = currBlockNumber
        self.fromBlockNumber = self.latestBlockNumber - 1
    }

    self.latestBlockNumber = self.getBlockNumber()
    self.fromBlockNumber = self.latestBlockNumber - 1

    return self
}

var listener = null 
function main() {
    // 初始清理日志
    LogReset(1)
    LogProfitReset()

    var event = "Transfer(address,address,uint256)"                          // 监听事件
    var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"       // USDT合约地址
    var decimals = exchange.IO("api", contractAddress, "decimals")           // 获取USDT token的精度信息
    var accountBinanceAddress = "0x28C6c06298d514Db089934071355E5743bf21d60" // Binance 热钱包地址
    accountBinanceAddress = accountBinanceAddress.toLowerCase()              // 地址处理为小写
    Log(exchange.IO("api", contractAddress, "name"), " decimals:", decimals)

    // 创建监听对象
    listener = addEventListener(contractAddress, event, function(log) {
        var fromAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[1])
        var toAddress = "0x" + exchange.IO("encodePacked", "address", log.topics[2])
        if (fromAddress == accountBinanceAddress) {
            Log("币安转出 - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#CD32CD")
        } else if (toAddress == accountBinanceAddress) {
            Log("转入币安 - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#FF0000")
        }        
    })

    // 设置事件过滤
    listener.setFilter([null, accountBinanceAddress, null])   // Binance -> USDT
    listener.setFilter([null, null, accountBinanceAddress])   // USDT -> Binance
    
    var preBalance = 0
    while (true) {
        listener.run()
        var balance = listener.getTokenBalanceOfWallet(accountBinanceAddress, contractAddress, decimals)
        if (balance) {
            var direction = ""
            if (preBalance != 0 && preBalance > balance) {
                direction = " ↓ " + (preBalance - balance) + "#CD32CD"
            } else if (preBalance != 0 && preBalance < balance) {
                direction = " ↑ " + (balance - preBalance) + "#FF0000"
            }
            Log("币安钱包地址:", accountBinanceAddress, " 余额:", balance, direction)
            LogProfit(balance, "&")   // 只画图,不打印日志
            preBalance = balance
        }
        LogStatus(_D(), "币安钱包地址:", accountBinanceAddress, ", 余额:", balance)
        Sleep(5000 * 3)
    }
}

上記のコードは実際のディスクで実行されます。

FMZを使用してEthereumベースのWeb3開発を簡単に開始する

FMZを使用してEthereumベースのWeb3開発を簡単に開始する

このレッスンでは、イベント フィルターの設計方法を紹介しました。そして、Binance Exchangeのホットウォレットを監視するために使用しましたUSDT貿易。このサンプル プログラムを変更および拡張して、関心のあるイベントをリッスンすることができます。smart moneyどのような新しい取引が行われましたか?NFT大物たちはどんな新しいプロジェクトに取り組んでいるのか、など。

単位変換

多くのイーサリアム関連の計算では、値がJavaScript言語の最大安全整数。したがって、Inventor Quantitative Trading Platform で大量の数値を処理するには、いくつかの方法が必要です。これらの方法は以前のコースで使用しましたが、詳細には説明していませんでした。この章では、この点について詳しく説明します。

印刷JavaScript言語で定義されている最大の安全な整数:

function main() {
    Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)
}

操作結果:

Number.MAX_SAFE_INTEGER: 9007199254740991

BigInt

イーサリアムで定義されている最小単位は1wei、意味1Gwei等しい1000000000 wei1Gwei実は、イーサリアム関連の計算ではそれほど大きな数字ではありません。一部のデータはこれよりはるかに大きいです。すると、非常に大きな値を持つこれらのデータは簡単に超過する可能性がありますNumber.MAX_SAFE_INTEGER: 9007199254740991

Inventor Quantitative Trading Platformでは、プラットフォームのBigIntこれらの非常に大きな整数データを表すためにオブジェクトが使用されます。コンストラクタの使用BigInt()構築するBigInt物体。数値または16進数値文字列をパラメータとして使用して構築できますBigInt物体。使用BigInt物体toString()このメソッドは、オブジェクトによって表されるデータを文字列として出力します。

BigIntオブジェクトでサポートされる操作:

  • 加算演算:+
  • 減算:-
  • 乗算:*
  • 除算演算:/
  • モジュロ演算:%
  • 電源操作:**

次のコード例を参照してください。

function main() {
    // 1Gwei的十进制表示
    var oneGwei = 1000000000

    // 1Gwei的十进制转换为十六进制表示
    var oneGweiForHex = "0x" + oneGwei.toString(16)

    Log("oneGwei : ", oneGwei)
    Log("oneGweiForHex : ", oneGweiForHex)

    // 构造BigInt对象
    Log("1Gwei / 1Gwei : ", (BigInt(oneGwei) / BigInt(oneGweiForHex)).toString(10))
    Log("1Gwei * 1Gwei : ", (BigInt(oneGwei) * BigInt(oneGweiForHex)).toString(10))
    Log("1Gwei - 1Gwei : ", (BigInt(oneGwei) - BigInt(oneGweiForHex)).toString(10))
    Log("1Gwei + 1Gwei : ", (BigInt(oneGwei) + BigInt(oneGweiForHex)).toString(10))
    Log("(1Gwei + 1) % 1Gwei : ", (BigInt(oneGwei + 1) % BigInt(oneGweiForHex)).toString(10))
    Log("1Gwei ** 2 : ", (BigInt(oneGwei) ** BigInt(2)).toString(10))
    Log("100 的平方根 : ", (BigInt(100) ** BigFloat(0.5)).toString(10))

    Log("Number.MAX_SAFE_INTEGER : ", BigInt(Number.MAX_SAFE_INTEGER).toString(10))
    Log("Number.MAX_SAFE_INTEGER * 2 : ", (BigInt(Number.MAX_SAFE_INTEGER) * BigInt("2")).toString(10))
}

デバッグツールテスト:

2023-06-08 11:39:50		信息	Number.MAX_SAFE_INTEGER * 2 : 18014398509481982
2023-06-08 11:39:50		信息	Number.MAX_SAFE_INTEGER : 9007199254740991
2023-06-08 11:39:50		信息	100 的平方根 : 10
2023-06-08 11:39:50		信息	1Gwei ** 2 : 1000000000000000000
2023-06-08 11:39:50		信息	(1Gwei + 1) % 1Gwei : 1
2023-06-08 11:39:50		信息	1Gwei + 1Gwei : 2000000000
2023-06-08 11:39:50		信息	1Gwei - 1Gwei : 0
2023-06-08 11:39:50		信息	1Gwei * 1Gwei : 1000000000000000000
2023-06-08 11:39:50		信息	1Gwei / 1Gwei : 1
2023-06-08 11:39:50		信息	oneGweiForHex : 0x3b9aca00
2023-06-08 11:39:50		信息	oneGwei : 1000000000

BigFloat

BigFloatオブジェクトとBigIntオブジェクトも同様に使用され、より大きな値の浮動小数点数を表すために使用されます。また、加算、減算、乗算、除算の演算もサポートしています。 BigFloatオブジェクトのサポートtoFixed()方法。

次のコード例を参照してください。

function main() {
    var pi = 3.14
    var oneGwei = "1000000000"
    var oneGweiForHex = "0x3b9aca00"

    Log("pi + oneGwei : ", (BigFloat(pi) + BigFloat(oneGwei)).toFixed(2))
    Log("pi - oneGweiForHex : ", (BigFloat(pi) - BigFloat(oneGweiForHex)).toFixed(2))
    Log("pi * 2.0 : ", (BigFloat(pi) * BigFloat(2.0)).toFixed(2))
    Log("pi / 2.0 : ", (BigFloat(pi) / BigFloat(2.0)).toFixed(2))
}

デバッグツールテスト:

2023-06-08 13:56:44		信息	pi / 2.0 : 1.57
2023-06-08 13:56:44		信息	pi * 2.0 : 6.28
2023-06-08 13:56:44		信息	pi - oneGweiForHex : -999999996.86
2023-06-08 13:56:44		信息	pi + oneGwei : 1000000003.14

BigDecimal

BigDecimalこのオブジェクトは整数値と浮動小数点値と互換性があり、BigIntオブジェクト、BigFloatオブジェクトの初期化では、加算、減算、乗算、除算の演算もサポートされます。

次のコード例を参照してください。

function main() {
    var pi = 3.1415
    var oneGwei = 1000000000
    var oneGweiForHex = "0x3b9aca00"

    Log("pi : ", BigDecimal(pi).toFixed(2))
    Log("oneGwei : ", BigDecimal(oneGwei).toString())
    Log("oneGweiForHex : ", BigDecimal(BigInt(oneGweiForHex)).toString())

    Log("BigInt(oneGwei) : ", BigDecimal(BigInt(oneGwei)).toString())    
    Log("BigFloat(pi) : ", BigDecimal(BigFloat(pi)).toFixed(4))

    Log("oneGwei + pi : ", (BigDecimal(oneGwei) + BigDecimal(pi)).toString())
    Log("oneGwei - pi : ", (BigDecimal(oneGwei) - BigDecimal(pi)).toString())
    Log("2.0 * pi : ", (BigDecimal(2.0) * BigDecimal(pi)).toString())
    Log("pi / pi : ", (BigDecimal(pi) / BigDecimal(pi)).toString())
}

デバッガーで実行します:

2023-06-08 14:52:53		信息	pi / pi : 1
2023-06-08 14:52:53		信息	2.0 * pi : 6.283
2023-06-08 14:52:53		信息	oneGwei - pi : 999999996.8585
2023-06-08 14:52:53		信息	oneGwei + pi : 1000000003.1415
2023-06-08 14:52:53		信息	BigFloat(pi) : 3.1415
2023-06-08 14:52:53		信息	BigInt(oneGwei) : 1e+9
2023-06-08 14:52:53		信息	oneGweiForHex : 1e+9
2023-06-08 14:52:53		信息	oneGwei : 1e+9
2023-06-08 14:52:53		信息	pi : 3.14

単位変換

次の 2 つの機能:toAmount()toInnerAmount()これら 2 つの関数は、以前のコースで何度も使用しました。これらは主に、データの精度変換に使用されます。

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function toInnerAmount(n, decimals) {
    return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}

toAmount()この関数は変数を変換するs精度パラメータに応じてdecimals(縮小)変換を実行します。実際、web3 の開発では、チェーン上で 16 進数データを処理する必要がある場合がよくあります。 スマート コントラクトなど、これまでのコースでこれによく遭遇します。Transfer(address,address,uint256)イベントではdataフィールドデータ:

{
	"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000",
	"topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000006b75d8af000000e20b7a7ddf000ba900b4009a80", "0x000000000000000000000000bcb095c1f9c3dc02e834976706c87dee5d0f1fb6"],
	"transactionHash": "0x27f9bf5abe3148169b4b85a83e1de32bd50eb81ecc52e5af006157d93353e4c4",
	"transactionIndex": "0x0",
	"removed": false,
	"address": "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
	"blockHash": "0x847be24a7b159c292bda030a011dfec89487b70e71eed486969b032d6ef04bad",
	"blockNumber": "0x109b1cc",
	"logIndex": "0x0"
}

データ処理"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000"、今回はtoAmount()関数。この処理設計はdataフィールド データを読み取り可能な数値に変換します。

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function main() {
    var data = "0x00000000000000000000000000000000000000000000000001c1a55000000000"
    Log(toAmount(data, 18))  // 打印出 0.12656402755905127
}

私たちが知っている1ETHトークンは1e18 weiもし私たちがwei単位のデータ126564027559051260それをETHトークンの数に変換するにはどうすればいいですか? 使用toAmount(, 18)関数は簡単に変換できます。toInnerAmount()機能はtoAmount()関数の逆操作(精度、増幅に応じて)この 2 つの関数を使用すると、データを変換するのに非常に便利です。

JavaScript言語における整数値の安全な範囲はNumber.MAX_SAFE_INTEGER次の例は、データ変換におけるより隠れた問題を示しています。

function toAmount(s, decimals) {
    return Number((BigDecimal(BigInt(s)) / BigDecimal(Math.pow(10, decimals))).toString())
}

function toInnerAmount(n, decimals) {
    return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}

function main() {
    var amount = 0.01
    var innerAmount = Number(toInnerAmount(amount, 18))       

    Log("Number.MAX_SAFE_INTEGER:", Number.MAX_SAFE_INTEGER)  // 9007199254740991
    Log("innerAmount:", innerAmount)                          // 10000000000000000

    Log("typeof(innerAmount):", typeof(innerAmount), ", innerAmount:", innerAmount)
    
    // 十进制数值 10000000000000000 -> 十六进制数值 0x2386f26fc10000
    Log("转换", innerAmount, "为十六进制:", innerAmount.toString(16))
    Log("转换", BigInt(10000000000000000).toString(10), "为十六进制:", BigInt(10000000000000000).toString(16))
    
    Log("0x" + BigInt(10000000000000000).toString(16), "转换为10进制:", toAmount("0x" + BigInt(10000000000000000).toString(16), 0))
}

デバッガーで実行できます:

2023-06-15 16:21:40		信息	0x2386f26fc10000 转换为10进制: 10000000000000000
2023-06-15 16:21:40		信息	转换 10000000000000000 为十六进制: 2386f26fc10000
2023-06-15 16:21:40		信息	转换 10000000000000000 为十六进制: 10000000000000000
2023-06-15 16:21:40		信息	typeof(innerAmount): number , innerAmount: 10000000000000000
2023-06-15 16:21:40		信息	innerAmount: 10000000000000000
2023-06-15 16:21:40		信息	Number.MAX_SAFE_INTEGER: 9007199254740991

観察を通じて次のことがわかりました。

Log("转换", innerAmount, "为十六进制:", innerAmount.toString(16))

このコード行に対応するログ出力は次のとおりです。转换 10000000000000000 为十六进制: 10000000000000000が正しく変換されませんでした。理由は当然100000000000000000を超えるからですNumber.MAX_SAFE_INTEGER

ただし、小数値が安全範囲内、つまりNumber.MAX_SAFE_INTEGER時間、toString(16)関数変換は再び正常になります。例:

function main() {
    var value = 1000
    Log("把value转换为十六进制:", "0x" + value.toString(16))   // 0x3e8
    Log("把0x3e8转换为十进制:", Number("0x3e8"))               // 1000
}

ブロックチェーンの分野でも0.01ETHに変換weiユニットの値10000000000000000また、Number.MAX_SAFE_INTEGERしたがって、この状況でのより安全な変換は次のようになります。BigInt(10000000000000000).toString(16)

通話をシミュレートする

Ethereumでトランザクションを実行し、スマートコントラクトを呼び出すWriteこの方法は一定量のガスを消費し、失敗するリスクもあります。トランザクションを送信または呼び出す前に、どのトランザクションが失敗する可能性があるかを把握することが重要です。 Ethereum にはテスト用のシミュレートされた呼び出しメソッドがあります。

eth_call

イーサリアムのRPC方式eth_call:トランザクションをシミュレートし、可能なトランザクション結果を返すことはできますが、ブロックチェーン上で実際にトランザクションを実行することはありません。

eth_callこのメソッドには2つのパラメータがあり、最初のパラメータは辞書構造です。transactionObject

// transactionObject
{
    "from" : ...,     // The address from which the transaction is sent
    "to" : ...,       // The address to which the transaction is addressed
    "gas" : ...,      // The integer of gas provided for the transaction execution
    "gasPrice" : ..., // The integer of gasPrice used for each paid gas encoded as hexadecimal
    "value" : ...,    // The integer of value sent with this transaction encoded as hexadecimal
    "data" : ...,     // The hash of the method signature and encoded parameters. For more information, see the Contract ABI description in the Solidity documentation
}

2番目のパラメータはblockNumber: タグを渡すことができるlatest/pending/earliest待って:

/* blockNumber
The block number in hexadecimal format or the string latest, earliest, pending, safe or 
finalized (safe and finalized tags are only supported on Ethereum, Gnosis, Arbitrum, 
Arbitrum Nova and Avalanche C-chain), see the default block parameter description in 
the official Ethereum documentation
*/

次にトークンを使用しますDAIスマートコントラクト方式approvetransfer通話は模擬通話の一例です。以下のテスト環境はEthereumメインネットワークです。

承認の呼び出しをシミュレートする

ERC20契約の場合approve私たちはすでにこの方法に精通しており、以前のコースで練習しました。 ERC20 コントラクトはすでに FMZ プラットフォーム ABI に組み込まれているため、シミュレートするスマート コントラクトの ABI を登録する必要はありません。

function main() {
    var contractAddressUniswapV3SwapRouterV2 = "0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45"
    var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f"
    var wallet = exchange.IO("address")

    // encode approve
    var data = exchange.IO("encode", contractAddress_DAI, "approve(address,uint256)", 
        contractAddressUniswapV3SwapRouterV2, "0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
    Log("ERC20 token DAI approve encode, data:", data)
    
    var transactionObject = {
        "from" : wallet,
        "to" : contractAddress_DAI,
        // "gasPrice" : "0x" + parseInt("21270894680").toString(16),
        // "gas" : "0x" + parseInt("21000").toString(16),
        "data" : "0x" + data,
    }
    var blockNumber = "latest"
    
    var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
    Log("ret:", ret)
}

この例のコードはまずapprove(address,uint256)メソッドとパラメータはエンコードされます。approveメソッドパラメータ値0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff承認の最大数を示します。スマートコントラクト、アドレスを承認する0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45今すぐUniswap V3ルーティング契約。最後にEthereum RPCメソッドを呼び出すeth_callシミュレーションを実行します。見ることができますtransactionObjectパラメータgasPricegasフィールドは省略できます。

デバッグ ツールが実行され、approve メソッドの呼び出しによる正常な承認をシミュレートします (ただし、実際には承認されません)。

2023-06-09 11:58:39		信息	ret: 0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 11:58:39		信息	ERC20 token DAI approve encode, data: 095ea7b300000000000000000000000068b3465833fb72a70ecdf485e0e4c7bd8665fc45ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff

失敗のシナリオをシミュレーションすることもできます。gasPriceそしてgasパラメータを設定する際に、ウォレット内の ETH がガス料金を支払うのに十分でない場合はエラーが報告されます。

insufficient funds

ガス料金が低すぎると、エラーが報告されます。

intrinsic gas too low: have 21000, want 21944 (supplied gas 21000)

通話転送をシミュレートする

ERC20の場合transferこの方法も私たちもよく知っています。この方法では、ERC20トークンを特定のウォレットアドレスに転送できます。1,000 DAIをVitalik Buterinに転送するシミュレーションを試みます。

function toInnerAmount(n, decimals) {
    return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}

function main() {
    var walletVitalik = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
    var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f"
    var wallet = exchange.IO("address")

    // 转账给V神
    var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals")
    var transferAmount = toInnerAmount(1000, decimals_DAI)
    Log("转账金额:", 1000, "DAI, 使用 toInnerAmount 转换为:", transferAmount)

    // encode transfer
    var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)",
        walletVitalik, transferAmount)

    var transactionObject = {
        "from" : wallet,
        "to" : contractAddress_DAI,
        "data" : "0x" + data,
    }
    var blockNumber = "latest"
    
    var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
    return ret 
}

私のテストウォレットには DAI トークンがないので、デバッグツールで実行した結果は予想どおりで、エラーが報告されます。

execution reverted: Dai/insufficient-balance

Vitalikのウォレットアドレスを確認してください:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045このウォレットにはDAIトークンが含まれていることがわかります。次に、シミュレートされた通話の転送方向を交換し、Vitalik が 1,000 DAI を私たちに転送することをシミュレートしてみましょう。

コードを変更し、変更した箇所にコメントを追加します。

function toInnerAmount(n, decimals) {
    return (BigDecimal(n) * BigDecimal(Math.pow(10, decimals))).toFixed(0)
}

function main() {
    var walletVitalik = "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045"
    var contractAddress_DAI = "0x6b175474e89094c44da98b954eedeac495271d0f"
    var wallet = exchange.IO("address")

    var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals")
    var transferAmount = toInnerAmount(1000, decimals_DAI)
    Log("转账金额:", 1000, "DAI, 使用 toInnerAmount 转换为:", transferAmount)

    // encode transfer
    var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)",
        wallet, transferAmount)     // 使用wallet变量作为参数,转账接收方地址改为我自己

    var transactionObject = {
        "from" : walletVitalik,     // 使用walletVitalik变量作为from字段的值,模拟这个调用是由V神钱包地址发出
        "to" : contractAddress_DAI,
        "data" : "0x" + data,
    }
    var blockNumber = "latest"
    
    var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
    Log(ret)
}

デバッグツールテスト:

2023-06-09 13:34:31		信息	0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31		信息	转账金额: 1000 DAI, 使用 toInnerAmount 转换为: 1000000000000000000000

Inventor Quantitative Trading Platform を使用すると、トランザクションの結果を簡単にシミュレートし、失敗する可能性のあるトランザクションの送信によって発生する不必要なガス料金の損失を回避できます。この章のサンプル コードを使用して、Vitalik のウォレットへの送金と Vitalik のウォレットから私たちに送金する呼び出しをシミュレートします。もちろん、これはeth_callメソッドにはさらに多くの用途があります。想像力を働かせればeth_callこの方法はどこで使用されていますか?

ERC721コントラクトを識別する

ETH や BTC などのトークンはすべて同質のトークンであることはわかっています。あなたの手にあるトークンは、私の手にあるトークンと何ら変わりません。しかし、世の中には不動産や骨董品、仮想アート作品など、均質なトークンで抽象的に表現することができない、性質の異なるものが数多く存在します。そこで、非均質なオブジェクトを抽象化するためにERC721規格が作成され、NFTや関連する概念が誕生しました。 では、Ethereum に展開されている多数のスマート コントラクトの中で、どのスマート コントラクトが ERC721 標準のスマート コントラクトであるかをどのように識別するのでしょうか?

ERC721 を識別するには、まず ERC165 標準を理解する必要があります。

ERC165

ERC165 標準を通じて、スマート コントラクトは、他のコントラクトがチェックできるようにサポートするインターフェイスを宣言できます。 ERC165 インターフェース コントラクトには 1 つの機能しかありません。supportsInterface(bytes4 interfaceId)、パラメータinterfaceId照会するインターフェース ID。コントラクトがインターフェース Id を実装している場合はブール値の true 値を返し、そうでない場合は false 値を返します。

次はこれについてお話ししますinterfaceId具体的に計算してエンコードする方法。

ERC165 標準例を挙げます。

pragma solidity ^0.4.20;

interface Solidity101 {
    function hello() external pure;
    function world(int) external pure;
}

contract Selector {
    function calculateSelector() public pure returns (bytes4) {
        Solidity101 i;
        return i.hello.selector ^ i.world.selector;
    }
}

インターフェースの関数シグネチャ(関数名とパラメータ型リストで構成)に対して XOR 演算を実行します。関数が 1 つだけの ERC165 インターフェース コントラクトの場合:

pragma solidity ^0.4.20;

interface ERC165 {
    /// @notice Query if a contract implements an interface
    /// @param interfaceID The interface identifier, as specified in ERC-165
    /// @dev Interface identification is specified in ERC-165. This function
    ///  uses less than 30,000 gas.
    /// @return `true` if the contract implements `interfaceID` and
    ///  `interfaceID` is not 0xffffffff, `false` otherwise
    function supportsInterface(bytes4 interfaceID) external view returns (bool);
}

The interface identifier for this interface is 0x01ffc9a7. You can calculate this by running bytes4(keccak256(‘supportsInterface(bytes4)’)); or using the Selector contract above.

関数シグネチャを直接計算し、最初の4バイトを取得してinterfaceId

function main() {
    var ret = Encode("keccak256", "string", "hex", "supportsInterface(bytes4)")
    Log("supportsInterface(bytes4) interfaceId:", "0x" + ret.slice(0, 8))
}

デバッガーでテストを実行できます。

2023-06-13 14:53:35		信息	supportsInterface(bytes4) interfaceId: 0x01ffc9a7

計算結果を見ることができ、ERC165 標準文書内の説明は一貫しています。

ERC721

次に、ERC721 コントラクト標準のインターフェース定義を見てみましょう。

”`solidity interface ERC721 /* is ERC165 */ { event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);

event Approval(address indexed _owner, address indexed _approved, uint256 indexed _tokenId);

event ApprovalForAll(address indexed _owner, address indexed _operator, bool _approved);

function balanceOf(address _owner) external view re