Sumber dimuat naik... memuat...

Mulakan dengan Pembangunan web3 Mudah Berdasarkan Ethereum Menggunakan FMZ

Penulis:FMZ~Lydia, Dicipta: 2023-06-25 09:17:53, Dikemas kini: 2024-11-11 22:34:49

0000000000164f2434262e1cc", transaksiHash: 0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048, alamat: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, blockHash: 0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9 ♪, ♪ alamat: 0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, blockHash: 0xcd3d567c9bd02a4549b1de0dc638ab5523e847c3c156b096424f56c633000fd9, Blok nombor: 0x109b1cd, logIndex: 0xde, dihapus: salah, topics: [0x7fcf532c15f0a6db0bd6d0e038bea71d30d808c7d98cb3bf7268a95bf5081b65, 0x00000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b], data: 0x0000000000000000000000000000000000000000000000000164f2434262e1cc, transaksiHash: 0x6aa8d80daf42f442591e7530e31323d05e1d6dd9f9f9b9c102e157d89810c048, TransactionIndex: 0x91 {y: i}


We can see that there are various events in the logs data, if we only care about ```Transfer``` events, we need to filter out the ```Transfer``` events in these data.

### Retrieving Logs

The Ethereum log is divided into two parts: 1. ```topics```; 2. ```data```.

- ```topics```
  Taking the results of the code run for the ```eth_getLogs``` section test as an example, the data in the ```topics``` field is:

  ```desc
  "topics": ["0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000012b791bb27b3a4ee958b5a435fea7d49ec076e9c", "0x000000000000000000000000ef1c6e67703c7bd7107eed8303fbe6ec2554bf6b"],  

Nilaitopicsmedan adalah struktur array yang digunakan untuk menerangkan peristiwa. Ia ditentukan bahawa panjangnya (array) tidak boleh melebihi 4 dan elemen pertama adalah tanda tangan hash peristiwa. Dalam Platform Perdagangan Kuantum FMZ, kita boleh mengira hash tandatangan ini menggunakanEncodefungsi, menggunakan kod berikut:

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

Mengirakeccak256nilai hash (koding hex)Transfer(address,address,uint256)adalah0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef.

Nilaitopicsmedan adalah struktur array, dengan unsur kedua, dan unsur ketiga, masing-masing:

  • Alamat penghantaranfrom

  • Alamat penerimato

  • data

    Data dalamdatamedan adalah:

    "data": "0x0000000000000000000000000000000000000000000000000164f2434262e1cc",
    

    Parameter tertentu dalam kes (parameter tanpa pengisytiharan indeks dalam kod Soliditi kontrak pintar) disimpan dalamdata section.

    Menganalisis data0x0000000000000000000000000000000000000000000000000164f2434262e1cc

    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
    }
    

    Data ini diperoleh sebagai 0.10047146239950075 dandataialah jumlah pemindahan yang sepadan.


Yang di atas telah dijelaskan, dilatih dan siap untuk pergi.

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)

    // Traverse 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++
        }
    }
}

Periksa.https://etherscan.io/:

img

Hasil kod ujian yang dijalankan dalam alat pembaikan FMZ:

img

Data dalamfrom, tomedan juga boleh dianalisis bergantung kepada keperluan pada masa pengambilan, contohnya:

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

Hasil berjalan:

Alamat: 0x12b791bb27b3a4ee958b5a435fea7d49ec076e9c

Mendengarkan Peristiwa Kontrak

Sejakalat debugginghanya boleh menguji kod untuk masa yang singkat dan mengeluarkan kandungan hanya selepas pelaksanaan kod selesai, ia tidak boleh memaparkan dan mengeluarkan log dalam masa nyata.

Di sini kita menggunakan Ethereum mainnet, dan kita mendengarTransfer(address,address,uint256)peristiwaUSDTBerdasarkan apa yang kita pelajari dalam pelajaran terakhir, kita merancang dan menulis contoh mendengar secara berterusan peristiwa kontrak pintar tertentu:

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("End the run and verify the record")
    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("Length varies!")
        return 
    }
    
    for (var i = 0; i < arrLog.length; i++) {
        Log("Determine the blockNumber:", logs[i].blockNumber == arrLog[i].blockNumber, ", Determine from:", logs[i].topics[1] == arrLog[i].topics[1], 
            "Determine to:", logs[i].topics[2] == arrLog[i].topics[2])
    }
}
*/

Berjalan pada perdagangan langsung:

img

Untuk hasil pelaksanaan, bahagian pengesahan (TODO: ujian) juga ditulis dalam kod.Transferperistiwa kontrak USDT sentiasa dipantau dan data direkodkan dan perbandingan antara data ini dan data peristiwa yang diperoleh sekaligus dapat diperhatikan bahawa data itu konsisten dengan:

img

Penapisan Acara

Berdasarkan pelajaran sebelumnyaListening to contract events, kita mengembangkannya dengan menambah penapis kepada proses mendengar untuk mendengar pemindahan ke dan dari alamat tertentu.topicsmengandungi sehingga 4 keping maklumat. jadi kita merancang peraturan penapis dengan[[A1, A2, ...An], null, [C1], D]Sebagai contoh.

  1. [A1, A2, ...An]sepadan dengan data pada kedudukantopics[0].
  2. Nullsepadan dengan data pada kedudukantopics[1].
  3. [C1]sepadan dengan data pada kedudukantopics[2].
  4. Dsepadan dengan data pada kedudukantopics[3].
  • Jika elemen dalam struktur keadaan ditetapkannullbermakna ia tidak ditapis, contohnyanullsama dengantopics[1]dan sebarang kecocokan nilai.
  • Jika elemen dalam struktur keadaan menetapkan satu nilai tunggal yang menunjukkan bahawa kedudukan mesti sepadan, contohnya[C1]sama dengantopics[2]atauDsama dengantopics[3], dan log yang tidak sepadan disaring.
  • Jika elemen dalam struktur keadaan adalah array, ia bermakna bahawa sekurang-kurangnya satu elemen dalam array harus sepadan, contohnya[A1, A2, ...An]sama dengantopics[0], [A1, A2, ...An]dengan mana-mana daripada mereka yang sepadantopics[0], maka log tidak akan ditapis.

Mendengar pemindahan USDT dari bursa

PemantauanUSDTtransaksi yang dipindahkan dari dan ke Bursa Binance:

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("Set filter conditions:", 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) {
                // Check the filter condition, and execute the judgment if the filter condition is set
                if (self.filters.length != 0) {
                    // Initial filter marker
                    var isFilter = true 
                    // Traverse filter condition setting
                    for (var j = 0; j < self.filters.length; j++) {
                        // Take a filter setting, e.g: [[A1, A2, ...An], null, [C1], D]
                        var cond = self.filters[j]

                        // Traverse the filter setting
                        var final = true
                        for (var topicsIndex = 0; topicsIndex < cond.length; topicsIndex++) {
                            // Take one of the conditions in the filter setting, if it is the first condition: i.e. the data to be compared with topics[0]
                            var condValue = cond[topicsIndex]

                            // Data in the logs
                            if (topicsIndex > logs[i].topics.length - 1) {
                                continue 
                            }
                            var topicsEleValue = logs[i].topics[topicsIndex]
                            // If it's a Transfer event, you need to handle the from and to
                            if (logs[i].topics[0] == "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef") {
                                if (topicsIndex == 1 || topicsIndex == 2) {
                                    topicsEleValue = "0x" + exchange.IO("encodePacked", "address", topicsEleValue)
                                }
                            }

                            // If the condValue type is an array, it means that there are multiple comparison conditions in this position, and the multiple condition comparison is a logical or relationship
                            if (Array.isArray(condValue) && condValue.length > 1) {
                                // Determine 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() {
    // Initial clean-up log
    LogReset(1)
    LogProfitReset()

    var event = "Transfer(address,address,uint256)"                          // Listening to events
    var contractAddress = "0xdac17f958d2ee523a2206206994597c13d831ec7"       // USDT contract address
    var decimals = exchange.IO("api", contractAddress, "decimals")           // Get the precision information of USDT token
    var accountBinanceAddress = "0x28C6c06298d514Db089934071355E5743bf21d60" // Binance hot wallet address
    accountBinanceAddress = accountBinanceAddress.toLowerCase()              // Addresses are handled in lowercase
    Log(exchange.IO("api", contractAddress, "name"), " decimals:", decimals)

    // Creating a listener object
    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("Binance transfer out - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#CD32CD")
        } else if (toAddress == accountBinanceAddress) {
            Log("Binance transfer in - ", " Transfer:", fromAddress, "->", toAddress, ", value:", toAmount(log.data, decimals), ", blockNumber:", toAmount(log.blockNumber, 0), "#FF0000")
        }        
    })

    // Set up event filtering
    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("Binance wallet address:", accountBinanceAddress, " balance:", balance, direction)
            LogProfit(balance, "&")   // Drawing only, no log printing
            preBalance = balance
        }
        LogStatus(_D(), "Binance wallet address:", accountBinanceAddress, ", balance:", balance)
        Sleep(5000 * 3)
    }
}

Kod di atas berjalan dalam perdagangan langsung:

img

Dalam pelajaran ini, kami memperkenalkan bagaimana untuk mereka bentuk penapis peristiwa. dan menggunakannya untuk mendengarUSDTAnda boleh mengubah suai dan memperluaskan program contoh ini untuk mendengar apa-apa acara yang anda minat, untuk melihat apa transaksi barusmart moneytelah dibuat, apa barangan baru yangNFTOrang kaya telah bergegas, dan sebagainya.

Penukaran unit

Banyak pengiraan yang berkaitan dengan Ethereum mempunyai nilai yang melebihi bilangan bulat selamat maksimumJavaScriptOleh itu, beberapa kaedah diperlukan pada Platform Dagangan Kuantum FMZ untuk mengendalikan nilai yang besar, yang telah kita gunakan secara khusus dalam kursus sebelumnya dan tidak dibahaskan secara terperinci.

Mencetak bilangan bulat maksimum selamat yang ditakrifkan dalamJavaScriptBahasa:

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

Hasil berjalan:

Nombor.MAX_SAFE_INTEGER: 9007199254740991

BigInt

Unit terkecil yang ditakrifkan dalam Ethereum adalah1wei, dan definisi1Gweiadalah sama dengan1000000000 wei. 1Gweiadalah nombor yang tidak terlalu besar dalam pengiraan berkaitan Ethereum, dan beberapa data lebih besar daripada itu.Number.MAX_SAFE_INTEGER: 9007199254740991.

Di FMZ Quant Trading Platform, kami menggunakan platformBigIntobjek untuk mewakili data bilangan bulat yang sangat besar ini.BigInt()untuk membinaBigIntobjek. anda boleh membinaBigIntobjek menggunakan nombor, hexadecimal nombor rentetan sebagai parameter.toString()kaedahBigIntobjek untuk mengeluarkan data yang diwakili oleh objek sebagai rentetan.

Operasi yang disokong olehBigIntobjek adalah:

  • Tambahan:+
  • Pengurangan:-
  • Pembagian:*
  • Bahagian:/
  • Operasi modulo:%
  • Operasi kuasa:*

Rujuk kepada contoh kod berikut:

function main() {
    // Decimal representation of 1Gwei
    var oneGwei = 1000000000

    // Decimal to hexadecimal conversion of 1Gwei
    var oneGweiForHex = "0x" + oneGwei.toString(16)

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

    // Constructing BigInt objects
    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("The square root of 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))
}

Ujian alat debug:

2023-06-08 11:39:50		Info	Number.MAX_SAFE_INTEGER * 2 : 18014398509481982
2023-06-08 11:39:50		Info	Number.MAX_SAFE_INTEGER : 9007199254740991
2023-06-08 11:39:50		Info	The square root of 100 : 10
2023-06-08 11:39:50		Info	1Gwei ** 2 : 1000000000000000000
2023-06-08 11:39:50		Info	(1Gwei + 1) % 1Gwei : 1
2023-06-08 11:39:50		Info	1Gwei + 1Gwei : 2000000000
2023-06-08 11:39:50		Info	1Gwei - 1Gwei : 0
2023-06-08 11:39:50		Info	1Gwei * 1Gwei : 1000000000000000000
2023-06-08 11:39:50		Info	1Gwei / 1Gwei : 1
2023-06-08 11:39:50		Info	oneGweiForHex : 0x3b9aca00
2023-06-08 11:39:50		Info	oneGwei : 1000000000

BigFloat

PeraturanBigFloatobjek digunakan sama sepertiBigIntobjek untuk mewakili nombor koma terapung dengan nilai yang lebih besar, dan ia juga menyokong penambahan, pengurangan, perkalian dan pembahagian. PeraturanBigFloatobjek menyokongtoFixed() method.

Rujuk kepada contoh kod berikut:

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

Ujian alat debug:

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

BigDecimal

PeraturanBigDecimalobjek adalah serasi dengan nilai bilangan bulat dan nilai koma terapung, dan menyokong inisialisasi denganBigIntobjek danBigFloatobjek, dan ia juga menyokong penambahan, pengurangan, perkalian dan pembahagian.

Rujuk kepada contoh kod berikut:

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

Memandu dalam alat debugging:

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

Penukaran unit

Dua fungsi berikut:toAmount(), toInnerAmount()kita telah digunakan berkali-kali dalam kursus sebelumnya, kedua-dua fungsi ini digunakan terutamanya untuk penukaran data ketepatan.

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

PeraturantoAmount()fungsi menukar (mengurangkan) pembolehubahsmengikut parameter ketepatandecimalsDalam pembangunan praktikal web3, ia sering perlu untuk berurusan dengan beberapa data hexadecimal berantai. Kami sering menemui ini dalam kursus kami sebelumnya, contohnya,datadata lapangan dalamTransfer(address,address,uint256)kejadian kontrak pintar:

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

Apabila memproses data"data": "0x00000000000000000000000000000000000000000000000001c1a55000000000", kita gunakantoAmount()Pemprosesan ini direka untuk melakukan kerja yang baik menukar data medan data kepada nilai yang boleh dibaca.

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

function main() {
    var data = "0x00000000000000000000000000000000000000000000000001c1a55000000000"
    Log(toAmount(data, 18))  // Print out 0.12656402755905127
}

1 token ETH, seperti yang kita tahu, adalah1e18 wei, jika kita mendapat data126564027559051260dalamwei, bagaimana untuk menukarnya kepada token ETH? MenggunakantoAmount(, 18)fungsi adalah kaedah penukaran yang sangat mudah.toInnerAmount()fungsi adalah operasi terbaliktoAmount()fungsi (bergantung pada ketepatan, memperbesar), dan mudah untuk menukar data menggunakan kedua-dua fungsi ini.

Adalah penting untuk diperhatikan julat keselamatan nilai bilangan bulat dalam bahasa JavaScript,Number.MAX_SAFE_INTEGER, dan contoh berikut menggambarkan masalah tersembunyi semasa menukar 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() {
    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)
    
    // Decimal value 10000000000000000 -> Hexadecimal value 0x2386f26fc10000
    Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))
    Log("Convert", BigInt(10000000000000000).toString(10), "to hexadecimal:", BigInt(10000000000000000).toString(16))
    
    Log("0x" + BigInt(10000000000000000).toString(16), "Convert to decimal:", toAmount("0x" + BigInt(10000000000000000).toString(16), 0))
}

Ia adalah mungkin untuk menjalankan dalam alat debugging:

2023-06-15 16:21:40		Info	Convert 0x2386f26fc10000 to decimal: 10000000000000000
2023-06-15 16:21:40		Info	Convert 10000000000000000 to hexadecimal: 2386f26fc10000
2023-06-15 16:21:40		Info	Convert 10000000000000000 to hexadecimal: 10000000000000000
2023-06-15 16:21:40		Info	typeof(innerAmount): number , innerAmount: 10000000000000000
2023-06-15 16:21:40		Info	innerAmount: 10000000000000000
2023-06-15 16:21:40		Info	Number.MAX_SAFE_INTEGER: 9007199254740991

Melalui pemerhatian kami mendapati bahawa:

Log("Convert", innerAmount, "to hexadecimal:", innerAmount.toString(16))

Baris kod ini sepadan dengan output log:Converting 10000000000000000 to hex: 10000000000000000, yang tidak ditukar dengan betul. Sebabnya adalah semula jadi bahawa 1000000000000000000 adalah di luarNumber.MAX_SAFE_INTEGER.

Tetapi apabila nilai perpuluhan adalah dalam julat yang selamat, iaitu, kurang daripadaNumber.MAX_SAFE_INTEGER, yangtoString(16)fungsi menukarnya dengan betul lagi, contohnya:

function main() {
    var value = 1000
    Log("Convert value to hexadecimal:", "0x" + value.toString(16))   // 0x3e8
    Log("Convert 0x3e8 to decimal:", Number("0x3e8"))               // 1000
}

Dalam blockchain, walaupun0.01ETH ditukar kepada nilai10000000000000000dalamweiakan melebihiNumber.MAX_SAFE_INTEGER``, so a safer conversion for such cases is:BigInt ((10000000000000000).toString ((16) ``.

Panggilan Simulasi

Melakukan transaksi dan memanggilWritekaedah kontrak pintar pada Ethereum kos sejumlah gas dan kadang-kadang ia gagal. Adalah penting untuk mengetahui transaksi yang mungkin gagal sebelum menghantar dan memanggil mereka.

eth_call

Kaedah RPC Ethereumeth_call: ia boleh mensimulasikan transaksi dan mengembalikan hasil transaksi yang mungkin, tetapi ia tidak benar-benar melaksanakan transaksi di blockchain.

Peraturaneth_callkaedah mempunyai 2 parameter, yang pertama adalah struktur kamus,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
}

Parameter kedua ialahblockNumber: anda boleh lulus labellatest/pending/earliest, dan sebagainya:

/* 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
*/

Seterusnya, kita mengambil kaedah kontrak pintarapprovedantransferpanggilan tokenDAIsebagai contoh untuk panggilan simulasi, dan persekitaran ujian berikut adalah rangkaian utama Ethereum.

Sambutan simulasi diluluskan

Kita semua sudah biasa denganapproveOleh kerana kontrak ERC20 sudah dibina ke dalam platform FMZ ABI, tidak perlu mendaftar ABI kontrak pintar untuk dipanggil oleh simulasi.

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

Kod dalam contoh pertama mengkodkanapprove(address,uint256)kaedah dan parameter, dan nilai parameter0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffdaripadaapprovePerintah diberikan kepada kontrak pintar di alamat0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45iaitu kontrak penghala untukUniswap V3. Akhirnya kaedah Ethereum RPCeth_callanda boleh melihat bahawagasPricedangasladang ditransactionObjectparameter boleh dihilangkan.

Alat debugging dijalankan dan simulasi memanggil kaedah meluluskan untuk membenarkan dengan berjaya (ia tidak benar-benar membenarkan):

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

Ia juga mungkin untuk mensimulasikan beberapa senario kegagalan, apabila kita menyesuaikangasPricedangasparameter, jika ETH dalam dompet tidak mencukupi untuk membayar yuran gas, satu ralat akan dilaporkan::

dana yang tidak mencukupi

Apabila kos gas ditetapkan terlalu rendah, satu ralat akan dilaporkan:

gas intrinsik terlalu rendah: mempunyai 21000, mahu 21944 (disediakan gas 21000)

Pemindahan panggilan simulasi

Kami biasa dengan ERC20stransferkaedah, yang membolehkan anda memindahkan token ERC20 ke alamat dompet tertentu, jadi mari kita cuba mensimulasikan pemindahan 1000 DAI ke 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")

    // transfer to Vitalik Buterin
    var decimals_DAI = exchange.IO("api", contractAddress_DAI, "decimals")
    var transferAmount = toInnerAmount(1000, decimals_DAI)
    Log("Transfer amount:", 1000, "DAI, use toInnerAmount convert to:", 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 
}

Oleh kerana saya tidak mempunyai token DAI dalam dompet ujian ini, menjalankan ia dalam alat debug melaporkan ralat berikut secara tidak dijangka:

pelaksanaan terbalik: Dai/tidak mencukupi-saldo

Periksa alamat dompet Vitalik Buterin:0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045, jelas bahawa dompet ini mempunyai token DAI. jadi mari kita menyesuaikan arah pemindahan panggilan simulasi dan mensimulasikan pemindahan 1000 DAI dari Vitalik Buterin kepada kita.

Mengubah kod, di mana perubahan yang saya buat komen:

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("Transfer amount:", 1000, "DAI, use toInnerAmount convert to:", transferAmount)

    // encode transfer
    var data = exchange.IO("encode", contractAddress_DAI, "transfer(address,uint256)",
        wallet, transferAmount)     // Use the wallet variable as a parameter and change the transfer recipient's address to my own

    var transactionObject = {
        "from" : walletVitalik,     // Use the walletVitalik variable as the value of the from field to simulate that the call was made from the Vitalik Buterin's wallet address
        "to" : contractAddress_DAI,
        "data" : "0x" + data,
    }
    var blockNumber = "latest"
    
    var ret = exchange.IO("api", "eth", "eth_call", transactionObject, blockNumber)
    Log(ret)
}

Ujian alat penyempurnaan:

2023-06-09 13:34:31		Info	0x0000000000000000000000000000000000000000000000000000000000000001
2023-06-09 13:34:31		Info	Transfer amount: 1000 DAI, use toInnerAmount convert to: 1000000000000000000000

Dengan menggunakan Platform Perdagangan Kuantum FMZ, mudah untuk mensimulasikan hasil transaksi dan mengelakkan kehilangan yuran gas yang tidak perlu daripada menghantar transaksi yang berpotensi gagal. Kami menggunakan kod contoh dari bab ini kursus untuk mensimulasikan panggilan untuk memindahkan wang ke dompet Vitalik Buterin dan dompet Vitalik Buterin untuk memindahkan wang kepada kita.eth_callGunakan imaginasi anda, apa yang anda akan menggunakaneth_callKaedah untuk?

Mengenal pasti kontrak ERC721

Kami tahu bahawa token seperti ETH dan BTC adalah token homogen, dan token di dompet anda tidak berbeza dengan token di dompet saya. Tetapi terdapat banyak perkara di dunia yang tidak homogen, seperti hartanah, barang antik, karya seni maya, dll. Ini tidak dapat diwakili oleh token homogen dalam abstraksi. Oleh itu, ada standard ERC721 untuk abstrak objek yang tidak homogen, dan ada NFT dan konsep yang berkaitan. Jadi di antara banyak kontrak pintar yang digunakan di Ethereum, bagaimana kita mengenal pasti kontrak pintar mana yang merupakan kontrak pintar standard ERC721?

Untuk mengenal pasti ERC721, penting untuk mengetahui standard ERC165 terlebih dahulu.

ERC165

Dengan standard ERC165, kontrak pintar boleh mengisytiharkan antara muka yang disokongnya untuk kontrak lain untuk diperiksa.supportsInterface(bytes4 interfaceId), parameterinterfaceIdadalah ID antara muka yang akan ditanyakan. Jika kontrak melaksanakan ID antara muka mengembalikan nilai benar boolean, jika tidak, ia mengembalikan nilai palsu.

Di sini kita akan bercakap tentang bagaimana iniinterfaceIddikira dan dikodkan secara khusus.

Standard ERC165menunjukkan contoh:

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;
    }
}

Untuk tandatangan fungsi antara muka (yang terdiri daripada nama fungsi dan senarai jenis parameter) untuk melakukan operasi tidak serupa, untuk kontrak antara muka ERC165 di mana kontrak hanya mempunyai satu fungsi:

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

Pengiktirafan antara muka untuk antara muka ini adalah 0x01ffc9a7. Anda boleh mengira ini dengan menjalankan bytes4 ((keccak256 ((supportsInterface ((bytes4) )); atau menggunakan kontrak Selector di atas.

Mengira tandatangan fungsi secara langsung dan mengambil 4 bait pertama untuk sampai keinterfaceId.

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

Ujian boleh dijalankan dalam alat debug di:

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

Ia dapat dilihat bahawa hasil yang dikira adalah konsisten dengan penerangan dalamStandard ERC165 document.

ERC721

Seterusnya mari kita lihat definisi antara muka standard kontrak ERC721:

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 returns (uint256);

    function ownerOf(uint256 _tokenId) external view returns (address);

    function safeTransferFrom(address _from, address _to, uint256 _tokenId, bytes data) external payable;

    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;

    function transferFrom(address _from, address _to, uint256 _tokenId) external payable;

    function approve(address _approved, uint256 _tokenId) external payable;

    function setApprovalForAll(address _operator, bool _approved) external;

    function getApproved(uint256 _tokenId) external view returns (address);

    function isApprovedForAll(address _owner, address _operator) external view returns (bool);
}

Jika kita mahu menentukan sama ada kontrak pintar adalah kontrak ERC721, pertama kita perlu tahuinterfaceIddaripada kontrak ERC721 sebelum kita boleh cuba untuk menggunakansupportsInterface(bytes4 interfaceId)Dalam kursus sebelumnya kita telah membiasakan diri dengan beberapa konsep standard ERC165 dan algoritma untuk mengirainterfaceId, dan kita menulis kod untuk mengira secara langsung:

function calcSelector(arrSelector) {
    var ret = null
    if (Array.isArray(arrSelector)) {
        if (arrSelector.length == 1) {
            ret = Encode("keccak256", "string", "hex", arrSelector[0])
        } else if (arrSelector.length == 0) {
            throw "Error: the number of elements in the array is 0"
        } else {
            var viewEncodeData = null
            for (var i = 0; i < arrSelector.length; i++) {
                if (i == 0) {
                    ret = new Uint8Array(Encode("keccak256", "string", "raw", arrSelector[i]))
                } else {
                    viewData = new Uint8Array(Encode("keccak256", "string", "raw", arrSelector[i]))
                    
                    if (viewData.length != ret.length) {
                        throw "Error: TypeArray view length is different"
                    }

                    for (var index = 0; index < ret.length; index++) {
                        ret[index] ^= viewData[index]
                    }
                }
            }
            ret = Encode("raw", "raw", "hex", ret.buffer)
        }
    } else {
        throw "Error: The parameter requires an array type."
    }

    return "0x" + ret.slice(0, 8)
}
function main() {
    // supportsInterface(bytes4): 0x01ffc9a7
    // var ret = calcSelector(["supportsInterface(bytes4)"])

    // ERC721Metadata: 0x5b5e139f
    /* 
    var arrSelector = [
        "name()",
        "symbol()",
        "tokenURI(uint256)"
    ]
    var ret = calcSelector(arrSelector)
    */

    // ERC721: 0x80ac58cd
    // /*
    var arrSelector = [
        "balanceOf(address)",
        "ownerOf(uint256)",
        "safeTransferFrom(address,address,uint256,bytes)",
        "safeTransferFrom(address,address,uint256)",
        "transferFrom(address,address,uint256)",
        "approve(address,uint256)",
        "setApprovalForAll(address,bool)",
        "getApproved(uint256)",
        "isApprovedForAll(address,address)",
    ]
    var ret = calcSelector(arrSelector)
    // */

    Log(ret)
}

Kod ini menggunakanEncode()fungsi untuk pengiraan tandatangan fungsi (yangkeccak256algoritma), dan untuk pengiraan dalam contoh kod di atas, menentukan parameter outputEncode()fungsi sebagai"raw", fungsi mengembalikanArrayBufferjenisJavaScriptBahasa. Untuk melaksanakan^(iso-atau) operasi pada duaArrayBufferobjek, anda perlu membuatTypedArraypandangan berdasarkanArrayBufferobjek, kemudian mengulangi melalui data di dalamnya dan menjalankan operasi iso-atau satu demi satu.

Jalankan dalam alat debugging:

2023-06-13 15:04:09		Info	0x80ac58cd

Ia dapat dilihat bahawa hasil yang dikira adalah konsisten dengan yang diterangkan dalameip-721.

pragma solidity ^0.4.20;

/// @title ERC-721 Non-Fungible Token Standard/// @dev See https://eips.ethereum.org/EIPS/eip-721///  Note: the ERC-165 identifier for this interface is 0x80ac58cd.interface ERC721 /* is ERC165 */ {
    /// @dev This emits when ownership of any NFT changes by any mechanism.
    ///  This event emits when NFTs are created (`from` == 0) and destroyed
    ///  (`to` == 0). Exception: during contract creation, any number of NFTs
    ///  may be created and assigned without emitting Transfer. At the time of
    ///  any transfer, the approved address for that NFT (if any) is reset to none.
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
...

Dengan ID antara muka ERC721, kita boleh menentukan sama ada kontrak adalah kontrak standard ERC721 atau tidak.BAYCUntuk melakukan ujian, yang merupakan kontrak yang mengikuti ERC721. pertama kita perlu mendaftarkan ABI, dan kerana kita hanya memanggil tiga kaedah berikut, kita boleh mendaftarkan tiga kaedah ini:

  • menyokong Interface ((interfaceId)
  • lambang ((()
  • nama (()

Kod khusus adalah seperti berikut:

function main() {
    // Contract address for ERC721, BAYC is used here
    var testContractAddress = "0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d"

    var testABI = `[{
        "inputs": [{
            "internalType": "bytes4",
            "name": "interfaceId",
            "type": "bytes4"
        }],
        "name": "supportsInterface",
        "outputs": [{
            "internalType": "bool",
            "name": "",
            "type": "bool"
        }],
        "stateMutability": "view",
        "type": "function"
    }, {
        "inputs": [],
        "name": "symbol",
        "outputs": [{
            "internalType": "string",
            "name": "",
            "type": "string"
        }],
        "stateMutability": "view",
        "type": "function"
    }, {
        "inputs": [],
        "name": "name",
        "outputs": [{
            "internalType": "string",
            "name": "",
            "type": "string"
        }],
        "stateMutability": "view",
        "type": "function"
    }]`

    // ERC721 Interface Id, calculated in the previous course
    var interfaceId = "0x80ac58cd"

    // Register ABI
    exchange.IO("abi", testContractAddress, testABI)

    // Call the supportsInterface method
    var isErc721 = exchange.IO("api", testContractAddress, "supportsInterface", interfaceId)

    // Output Information
    Log("Contract address:", testContractAddress)
    Log("Contract name:", exchange.IO("api", testContractAddress, "name"))
    Log("Contract code:", exchange.IO("api", testContractAddress, "symbol"))
    Log("Whether the contract is ERC721 standard:", isErc721)
}

Ujian boleh dijalankan dalam alat debugging:

2023-06-13 16:32:57		Info	Whether the contract is ERC721 standard: true
2023-06-13 16:32:57		Info	Contract code: BAYC
2023-06-13 16:32:57		Info

Berkaitan

Lebih lanjut