<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>Cara Wang</title>
        <link>https://paragraph.com/@carawang-2</link>
        <description>A Developer who love animals and drawing. 

https://matters.news/@carawang</description>
        <lastBuildDate>Fri, 21 Aug 2026 23:51:13 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Cara Wang</title>
            <url>https://storage.googleapis.com/papyrus_images/2405a56dd92259275aa7d3d0512abc61b09d1d720fbfe139d074373f26f62538.jpg</url>
            <link>https://paragraph.com/@carawang-2</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Introduce Blockchain to Students in Elementary School]]></title>
            <link>https://paragraph.com/@carawang-2/introduce-blockchain-to-students-in-elementary-school</link>
            <guid>tH5PxWCqSAdyZZ8QMLCe</guid>
            <pubDate>Sat, 24 Sep 2022 16:12:41 GMT</pubDate>
            <description><![CDATA[What is mining?Is there anyone who can explain mining? Mining is a process of exploring and extracting coal, gold, or other metal. Let&apos;s draw some pictures to let me know what looks like a miner in your mind. This is what I imagine the miner is.MinerThey work hard to dig and find resources for all humankind. For example, we can buy these resources, like coal, from them to cook. The miners in Blockchain are similar to it. But, it is the computers to mine the resources for us. Does anyone ...]]></description>
            <content:encoded><![CDATA[<h2 id="h-what-is-mining" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is mining?</h2><p>Is there anyone who can explain mining?</p><p>Mining is a process of exploring and extracting coal, gold, or other metal. Let&apos;s draw some pictures to let me know what looks like a miner in your mind. This is what I imagine the miner is.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/784a273c15d7ae03ca49733118175380117ece3966341e727b6381a271081157.jpg" alt="Miner" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Miner</figcaption></figure><p>They work hard to dig and find resources for all humankind. For example, we can buy these resources, like coal, from them to cook.</p><p>The miners in Blockchain are similar to it. But, it is the computers to mine the resources for us. Does anyone want to guess how our computer can mine the resources?</p><p>One of the significant power of a computer is to compute things. So, the miner in Blockchain will compute some complicated mathematical problem to mine the resources.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e9de4be2ccadf95b3b79c511fd5a294a3d60305eddc766d8a875bb51b9d550b0.jpg" alt="Miner in Blockchain" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Miner in Blockchain</figcaption></figure><p>The resources in Blockchain are cryptocurrency. The most famous one is bitcoin.</p><h2 id="h-what-is-blockchain" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">What is Blockchain?</h2><p>People say the Blockchain is a distributed network because everyone in the world can be the miner without anyone’s approval. Let me draw a simple graph to show the difference between distributed and centralized networks.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/bb83c6f4c78ff0f57c4db35835554da8244bc46bf6e39d901032b354b2d28801.jpg" alt="Centralized v.s. Distributed Network" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Centralized v.s. Distributed Network</figcaption></figure><p>It&apos;s a little hard to think about it, right? Let me give you some examples. If your teacher asks you questions and then tells your classmates about your answer, it is how centralized network work. Your teacher is the agent to collect information and share it with all of you. On the contrary, if classmate A can share his secret with classmate B, then classmate B share it with classmate C, D, E, and so on. After that, everyone in this room knows the secret about classmate A. It is what these graphs tell us. It may take a longer time, but it is important that we can share the same information among ourselves without any central agency.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a8beca742000c7c97e9b34c63b84f2e83a2c867eeb60fbe77042455dc4e96820.jpg" alt="Centralized and Distributed scenario in a class" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Centralized and Distributed scenario in a class</figcaption></figure><p>So, the miner in Blockchain doesn&apos;t need to fill up any application to get this job. This job is so important for the users of Blockchain because they also help people to transfer their resources from one to another. So, if I live in Taiwan, I can instantly share resources with friends who live in the USA without any central agency. In return, the miner can receive some resources as a reward because they are hard-working for us.</p><p>Can anyone explain what Blockchain is now? Or, can you imagine what Blockchain is?</p><p>Let&apos;s separate the word Blockchain to block and chain like this picture.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/64799cf94b87a996074df5220f365257ca5b3d5e847c2effe122e6f76031e4be.jpg" alt="Block and Chain" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Block and Chain</figcaption></figure><p>It is just like a train. Every carriage is chained together and only can move in one direction. In Blockchain, every block contains the user&apos;s transaction. Like cargos in the train carriages. When the resources are exchanged between my friends and me, they will record in one of the blocks, and everyone can see them. After transferring only one resource to my friend, I can&apos;t transfer it to another person again, right? In Blockchain, every record is transparent to everyone. It is an important attribute because everyone can validate it and ensure no one is cheating.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1051bd8e7956f09c95140b7339a7694e060d8b1e591e913645ae651476b7d6d4.jpg" alt="Train Carriage and Hook" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Train Carriage and Hook</figcaption></figure><p>But who will pick those cargos and put them on the train? Who can tell me?</p><p>We know the miners in Blockchain will help us to transfer our resources, right? In the mining process, miners will pick some transactions and validate them to protect our resources are well treated. After, the miner will put them into a block, which is a process of mining a block.</p><p>We know what block is now. Does anyone want to guess what the chain between these blocks means?</p><p>We can&apos;t chain things together as the train does in a digital world. So, the chain between these blocks is really magic. Every block will contain the previous block&apos;s information. So, the result will look like every block is chained together. I would say it is a pretty elegant mechanism in Blockchain.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/11b61bee332f899213cc2ae0d60e5a769a6fb7fad46a5151e988c08f81516cb4.jpg" alt="Invisible chain" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Invisible chain</figcaption></figure><p>Because of the invisible chain, one significant attribute comes to the Blockchain. It is the transactions in Blockchain are immutable. It can not be changed after it is put into a block and chained together. Let&apos;s think about the train. If every carriage on this train has a unique hook between them. So, if I change one carriage, this train will break into two pieces, and it can not move together forever. The only way I can change this carriage is to change the hook and the carriage behind it, which can fit the new hook. When the carriage behind the fraud one changes, I need to change its hook, too, and so on.</p><p>This is how Blockchain work to protect history from being mutable. If I change this block, then the chain will break up. The other miners or users will figure out if there is some bad thing happening because the chain length is different compared to the previous times, and the only way you can cover the cheating behavior by yourself is you need to change every single block behind it. I would say it is tough for a single miner can do it by himself.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/12059588f873f38ddd40ce07e2722e0cfd1f7560879d4ced1f400501ca6e0dbb.jpg" alt="There is one block that has been hacked. But the chain length will be changed, and he needs to change all of the blocks after it." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">There is one block that has been hacked. But the chain length will be changed, and he needs to change all of the blocks after it.</figcaption></figure><h2 id="h-about-consensus" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">About Consensus</h2><p>Here is the last question for you. There are tons of miners who can put valid transactions and append the block to the Blockchain. What will happen if they validate the different transactions and put different content in a block?</p><p>Do you remember the complex mathematical problem I said? After the miner collects some transactions and validates them to build a new block, they will start to calculate the mathematical problem. The first one who solves the problem, his block will be accepted by the other miners, which means the whole network will have a consensus for this valid block to be the latest block.</p><p>However, the miners are doing it in parallel, so it might have a situation in which two or more miners solve the problem simultaneously and be accepted by the miners near them first. So, there is another principle in Blockchain that is called the longest-chain rule. If there are two or more blocks generated at the same time, then the miners will both record them and wait. After some time, they can choose the longest chain and remove the branch.</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ad0a2356717efa6a96bfb04af779fe5df31edebf15e0402cc62d619864f55939.jpg" alt="The branch might occur if two or more miners produce a new block simultaneously." blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">The branch might occur if two or more miners produce a new block simultaneously.</figcaption></figure><h2 id="h-conclusion" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Conclusion</h2><p>Let&apos;s have some takeaways from what we learned today.</p><p>First, we know that miners play an important role in Blockchain. They will mine blocks and be responsible for Blockchain security and can receive rewards depending on their work.</p><p>Second, Blockchain is a distributed network that everyone can join as a miner or a user. We can transfer resources without any central agency.</p><p>Third, Blockchain is immutable and transparent. Everyone can see transactions on Blockchain, but we can’t change the history.</p><p>Fourth, there is a consensus rule in Blockchain. In the PoW algorithm we learned today, the miners will calculate mathematical problems to win the right to produce the latest block. Moreover, we will need to wait some time for the network to reach the longest chain.</p>]]></content:encoded>
            <author>carawang-2@newsletter.paragraph.com (Cara Wang)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/287c588f86074f798d3874074834c6b5cddc1b0c527ae057db00466c49e8fcea.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[ENS (Ethereum Name Service) 智能合約解析 (1)]]></title>
            <link>https://paragraph.com/@carawang-2/ens-ethereum-name-service-1</link>
            <guid>CSDX4Mdz18CplBZZFCxi</guid>
            <pubDate>Fri, 15 Apr 2022 16:32:49 GMT</pubDate>
            <description><![CDATA[目前 ENS 提供的綁定方式有以下兩種：購買想要的 ENS 名稱、綁定到地址上使用既有的 DNS 綁定持有的網址到地址上先前利用了兩篇文章記錄上述兩種方式的綁定步驟，此篇智能合約解析只會先解析到一般 ENS 名稱綁定到地址上的程式碼，因此建議在繼續閱讀本篇智能合約解析的文章前，可以先看過先前的下方文章，了解 ENS、申請與綁定步驟後再回來看智能合約的拆解與解析。 https://mirror.xyz/carawang.eth/_zLa85tHYaps-ivRUE1DspMYWuNf86RDGrKvRJxHvJEENS 智能合約中的時間區間定義首先，此篇文章先從大家比較容易碰到的綁定方式說起，這類型是透過直接購買、註冊 ENS 名稱的方式進行地址與 ENS 名稱的綁定。 筆者搭配下方圖片，先針對一些時間點進行定義，並將觸發智能合約的時間點標註上來，待會進入程式碼時，預期會比較容易懂。ENS 名稱的生命週期搭配上方的圖，筆者將由上而下做解釋，灰色的區塊中，是用戶觸發智能合約的時機點，主要包含兩個步驟:Request To Register (遞交申請單): 提交欲註冊的 ENS 名稱...]]></description>
            <content:encoded><![CDATA[<p>目前 ENS 提供的綁定方式有以下兩種：</p><ol><li><p>購買想要的 ENS 名稱、綁定到地址上</p></li><li><p>使用既有的 DNS 綁定持有的網址到地址上</p></li></ol><p>先前利用了兩篇文章記錄上述兩種方式的綁定步驟，此篇智能合約解析只會先解析到一般 ENS 名稱綁定到地址上的程式碼，因此建議在繼續閱讀本篇智能合約解析的文章前，可以先看過先前的下方文章，了解 ENS、申請與綁定步驟後再回來看智能合約的拆解與解析。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/carawang.eth/_zLa85tHYaps-ivRUE1DspMYWuNf86RDGrKvRJxHvJE">https://mirror.xyz/carawang.eth/_zLa85tHYaps-ivRUE1DspMYWuNf86RDGrKvRJxHvJE</a></p><h2 id="h-ens" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ENS 智能合約中的時間區間定義</h2><p>首先，此篇文章先從大家比較容易碰到的綁定方式說起，這類型是透過直接購買、註冊 ENS 名稱的方式進行地址與 ENS 名稱的綁定。</p><p>筆者搭配下方圖片，先針對一些時間點進行定義，並將觸發智能合約的時間點標註上來，待會進入程式碼時，預期會比較容易懂。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/99a655340b6065722080befa15fc7cde0b38622b70883b50b6a21cb716678db1.jpg" alt="ENS 名稱的生命週期" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">ENS 名稱的生命週期</figcaption></figure><p>搭配上方的圖，筆者將由上而下做解釋，灰色的區塊中，是用戶觸發智能合約的時機點，主要包含兩個步驟:</p><ol><li><p>Request To Register (遞交申請單): 提交欲註冊的 ENS 名稱申請，提交申請書後七天為考慮期，在此期間可以進入第 2 步驟，若未在考慮期進行註冊動作，則需要重新遞交申請書。</p></li><li><p>Register (註冊): 確認註冊、購買，此步驟會依據你所購買的 ENS 名稱字串長度、年限做註冊款扣款。</p></li></ol><p>淺紫色與深紫色區塊分別顯示了「可進行註冊」的期間與「可進行持有期延展」的期間:</p><ol><li><p>可進行註冊期間: 等待期的一分鐘過後，便可以執行註冊動作。此 ENS 名稱申請只保留七天，七天結束後，用戶需要重新遞交申請書。</p></li><li><p>可進行持有期延展: 用戶註冊後，直到年限結束後的 90 天寬限期內，都可以針對持有的 ENS 名稱進行延展。</p></li></ol><p>中間彩色區塊分別代表四個時間區塊:</p><ol><li><p>等待期: 避免同時有人進行註冊，因此會強制等待一分鐘後 (先冷靜一下情緒？！)，才能進行註冊。</p></li><li><p>考慮期: 當用戶遞交申請後，七天內可以進行註冊動作，此處筆者雖稱其為考慮期。此期間並不會幫用戶保留 ENS 名稱，假如這中間有其他人早你一步進行註冊了，你將失去註冊該 ENS 名稱的權利。</p></li><li><p>持有期: 註冊 ENS 名稱時，會需要設定持有年限。因此，在註冊成功後，直到年限到期前，筆者稱其為持有期。</p></li><li><p>寬限期: 當 ENS 名稱的持有年限到期後，會有 90 天的寬限期，此期間其他地址無法購買、註冊此 ENS 名稱，而元持有者可以選擇是否要延展持有期。</p></li></ol><p>最底下的三個參數名稱，將在後續程式碼中出現時，再進行進一步的介紹。</p><h2 id="h-ens" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ENS 程式架構介紹</h2><p>在進入智能合約程式碼解析前，筆者先描述一下 ENS 的架構設計，先建立一個概觀。</p><p>在 ENS 的架構中，智能合約大體分成兩個部分:</p><ol><li><p>ENS Registry: 負責記錄 ENS 名稱所定應到的 Resolver</p></li><li><p>Resolver: 也是智能合約，但其所記載的是每個 ENS 名稱實際上所對應到的以太坊地址與這個地址所有人主動提交到鏈上的個人資訊，例如 BTC、LTC 地址、email、網址等等…</p></li></ol><p>因此，由下圖中可以看到，當一個應用程式想要藉由 ENS 的智能合約獲得用戶的真實以太坊地址時，需要先詢問 ENS Registry 獲得 Resolver 的智能合約地址，接著再呼叫 Resolver 上的智能合約功能獲取該 ENS 名稱實際對應的用戶地址。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/31e2174d876d7a878733527f35bac68ccbe05632b98c4e12f4aa3841a4429dae.png" alt="圖片來源: https://docs.ens.domains/" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">圖片來源: https://docs.ens.domains/</figcaption></figure><p>知道取法後，可以先預期待會在看程式碼時，會有針對 <code>ENS Register</code> 設定 <code>Resolver</code> 地址與針對 <code>Resolver</code> 設定用戶實際地址的邏輯。</p><p>本篇文章著重於註冊 ENS 名稱的流程，此流程中，會由用戶觸發兩個智能合約的函式，其一是遞交申請書時的 <code>commit</code>，註冊時則是呼叫 <code>registerWithConfig</code> 函式:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cd8281b9f6df933cf56dd5673f34bced0cfaa920e666f8ff6f7e70fd6cdaf229.jpg" alt="一般 ENS 名稱購買、註冊的智能合約交互" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">一般 ENS 名稱購買、註冊的智能合約交互</figcaption></figure><p>其中，可以看到 <code>commit</code> 函式的呼叫較為單純，只有與 <code>ETHRegistrarController</code> 產生互動。而 <code>registerWithConfig</code> 函式中，則會與其他四個智能合約進行交互。</p><ol><li><p><code>ETHRegistrarController</code>: 用戶申請、註冊動作的主要函式呼叫都是在這個合約中發生，由這個合約再去觸發下方四個合約中的函式。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x283af0b28c62c092c9727f1ee09c02ca627eb7f5#code">https://etherscan.io/address/0x283af0b28c62c092c9727f1ee09c02ca627eb7f5#code</a></p></li><li><p><code>ENS</code>: 因為不是使用 Solidity 實作，但依據程式碼前後呼叫、技術文章的說明，可以判斷此合約是用於建立 ENS 名稱樹資料結構的主要實作。用戶註冊的 ENS 名稱與綁定地址的關聯也會被寫入此合約中。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x314159265dd8dbb310642f98f50c066173c1259b#code">https://etherscan.io/address/0x314159265dd8dbb310642f98f50c066173c1259b#code</a></p></li><li><p><code>BaseRegistrarImplementation</code>: ERC721 合約，用於鑄造 ENS 名稱的 NFT</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85#code">https://etherscan.io/address/0x57f1887a8bf19b14fc0df6fd9b2acc9af147ea85#code</a></p></li><li><p><code>PriceOracle</code>: 用於計算包含字數、購買年限的金額定義，並藉由 ETH/USD 的價格轉換換算成 ETH 價值之用。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xb9d374d0fe3d8341155663fae31b7beae0ae233a#code">https://etherscan.io/address/0xb9d374d0fe3d8341155663fae31b7beae0ae233a#code</a></p></li><li><p><code>PublicResolver</code>: 設定 ENS 名稱實際對應到哪個用戶地址。此合約在鏈上有兩個，猜測是 ENS 避免單一 <code>Resolver</code> 紀錄的資料量太大而進行的拆分。</p></li></ol><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/address/0xdaaf96c344f63131acadd0ea35170e7892d3dfba#code">https://etherscan.io/address/0xdaaf96c344f63131acadd0ea35170e7892d3dfba#code</a></p><p>以下將進入細部的程式碼解析。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">智能合約程式碼解析</h2><h3 id="h-request-to-register-commit" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Request To Register 遞交申請書 (commit)</h3><p>此步驟會呼叫到的智能合約的函式名稱是 <code>ETHRegistrarController</code>中的 <code>commit</code> ，這一段程式碼只有兩行：</p><pre data-type="codeBlock" text="function commit(bytes32 commitment) public {
    require(commitments[commitment] + maxCommitmentAge &lt; now);
    commitments[commitment] = now;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">commit</span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span> commitment</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
    <span class="hljs-built_in">require</span>(commitments[commitment] <span class="hljs-operator">+</span> maxCommitmentAge <span class="hljs-operator">&#x3C;</span> <span class="hljs-built_in">now</span>);
    commitments[commitment] <span class="hljs-operator">=</span> <span class="hljs-built_in">now</span>;
}
</code></pre><p>假如先思考遞交申請書這個動作在現實世界中會怎麼做，我們便可以猜測這個函式的用途。若要遞交申請，我們首先需要表明自己的身份 (地址)，以及我們想要申請註冊的 ENS 名稱，不過直接看程式碼時，就會產生第一個疑惑了，那就是此處只有一個 input，也就是資料型態是 <code>bytes32</code> 的 <code>commitment</code> 參數，若依據前述猜測，底下的 function 應該是要拿 ENS 的名稱以及當前用戶，也就是 <code>msg.sender</code> 與 ENS 名稱來進行記錄 (註: <code>msg.sender</code> 是智能合約的內建函式，會紀錄呼叫此智能合約的地址)，但實際上，此函式的實作卻是只有一個傳入值，且直接把傳入參數 <code>commitment</code> 放入了名為 <code>commitments</code> 的 mapping 中了。</p><p>我們先看一下 <code>commitments</code> 的資料結構，他的確就是一個 <code>bytes32</code> 為 key，<code>uint256</code> 為 value 的 mapping:</p><pre data-type="codeBlock" text="mapping(bytes32 =&gt; uint256) public commitments;
"><code><span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">bytes32</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">uint256</span>) <span class="hljs-keyword">public</span> commitments;
</code></pre><p>因此，我們可以進一步猜測，這個 <code>commitment</code> 所代表的，已經同時是用戶的地址與欲註冊的 ENS 名稱了。因此，在 ENS 的網站運作中，已經將我們欲申請的資料進行了某種組合、壓縮才傳入。</p><p>從同一份智能合約的程式碼中，可以看到一個名為 <code>makeCommitment</code> 與 <code>makeCommitmentWithConfig</code> 的函式:</p><pre data-type="codeBlock" text="function makeCommitment(string memory name, address owner, bytes32 secret) pure public returns(bytes32) {
    return makeCommitmentWithConfig(name, owner, secret, address(0), address(0));
}

function makeCommitmentWithConfig(string memory name, address owner, bytes32 secret, address resolver, address addr) pure public returns(bytes32) {
    bytes32 label = keccak256(bytes(name));
    if (resolver == address(0) &amp;&amp; addr == address(0)) {
        return keccak256(abi.encodePacked(label, owner, secret));
    }
    require(resolver != address(0));
    return keccak256(abi.encodePacked(label, owner, resolver, addr, secret));
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeCommitment</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name, <span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">bytes32</span> secret</span>) <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span></span>) </span>{
    <span class="hljs-keyword">return</span> makeCommitmentWithConfig(name, owner, secret, <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>));
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">makeCommitmentWithConfig</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name, <span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">bytes32</span> secret, <span class="hljs-keyword">address</span> resolver, <span class="hljs-keyword">address</span> addr</span>) <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">bytes32</span></span>) </span>{
    <span class="hljs-keyword">bytes32</span> label <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-keyword">bytes</span>(name));
    <span class="hljs-keyword">if</span> (resolver <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>) <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> addr <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
        <span class="hljs-keyword">return</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(label, owner, secret));
    }
    <span class="hljs-built_in">require</span>(resolver <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>));
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(label, owner, resolver, addr, secret));
}
</code></pre><p>依據 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.ens.domains/contract-api-reference/.eth-permanent-registrar/controller">ENS 的技術文章</a>中描述的流程，在呼叫 <code>commit</code> 前，會先使用 <code>makeCommitment</code> 的結果來作為 <code>commit</code> 的傳入值，因此，此時呼叫 <code>makeCommitmentWithConfig</code> 函式的傳入值中， <code>resolver</code> 與 <code>addr</code> 會是 <code>0x0</code> 的地址。</p><p>獲得解答後，先讓我們回到 <code>commit</code> 函式，其中有一條條件式的邏輯:</p><pre data-type="codeBlock" text="require(commitments[commitment] + maxCommitmentAge &lt; now);
"><code><span class="hljs-built_in">require</span>(commitments[commitment] <span class="hljs-operator">+</span> maxCommitmentAge <span class="hljs-operator">&#x3C;</span> <span class="hljs-built_in">now</span>);
</code></pre><p>此部分的 <code>commitments[commitment]</code> 便是用戶發出「Request To Register (遞交申請單)」的時間點，而 <code>maxCommitmentAge</code> 則是在合約創建時指定的全域變數:</p><pre data-type="codeBlock" text="constructor(BaseRegistrar _base, PriceOracle _prices, uint _minCommitmentAge, uint _maxCommitmentAge) public {
    require(_maxCommitmentAge &gt; _minCommitmentAge);

    base = _base;
    prices = _prices;
    minCommitmentAge = _minCommitmentAge;
    maxCommitmentAge = _maxCommitmentAge;
}
"><code>constructor(BaseRegistrar _base, PriceOracle _prices, uint _minCommitmentAge, uint _maxCommitmentAge) public {
    require(_maxCommitmentAge > _minCommitmentAge)<span class="hljs-comment">;</span>

    <span class="hljs-attr">base</span> = _base<span class="hljs-comment">;</span>
    <span class="hljs-attr">prices</span> = _prices<span class="hljs-comment">;</span>
    <span class="hljs-attr">minCommitmentAge</span> = _minCommitmentAge<span class="hljs-comment">;</span>
    <span class="hljs-attr">maxCommitmentAge</span> = _maxCommitmentAge<span class="hljs-comment">;</span>
}
</code></pre><p>若要查看全域變數的內容，可以使用 etherscan 中解析出來的 <code>Read Contract</code> 功能，其中可以看到 <code>maxCommitmentAge</code> 的內容是 <code>604800</code>，單位是秒的話，便可換算成是七天這個數字。</p><p>也就是說，若用戶進行了「Request To Register (遞交申請單)」的動作後，七天內就不能再重複呼叫 <code>commit</code> 函式，用意不用多說，是為了避免去複寫 <code>commitment</code> 的內容而造成保留七天、七天後該 ENS 名稱將重新開放供所有人註冊的條件失效。</p><h3 id="h-register-ens-registerwithconfig" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Register 註冊、購買 ENS 名稱 (registerWithConfig)</h3><p>此處會呼叫的函式是 <code>ETHRegistrarController</code> 中的 <code>registerWithConfig</code>，這段程式碼比較長，完整的程式碼如下，筆者後續會拆分成四大段進行解析:</p><pre data-type="codeBlock" text="function registerWithConfig(string memory name, address owner, uint duration, bytes32 secret, address resolver, address addr) public payable {
    bytes32 commitment = makeCommitmentWithConfig(name, owner, secret, resolver, addr);
    uint cost = _consumeCommitment(name, duration, commitment);

    bytes32 label = keccak256(bytes(name));
    uint256 tokenId = uint256(label);

    uint expires;
    if(resolver != address(0)) {
        // Set this contract as the (temporary) owner, giving it
        // permission to set up the resolver.
        expires = base.register(tokenId, address(this), duration);

        // The nodehash of this label
        bytes32 nodehash = keccak256(abi.encodePacked(base.baseNode(), label));

        // Set the resolver
        base.ens().setResolver(nodehash, resolver);

        // Configure the resolver
        if (addr != address(0)) {
            Resolver(resolver).setAddr(nodehash, addr);
        }

        // Now transfer full ownership to the expeceted owner
        base.reclaim(tokenId, owner);
        base.transferFrom(address(this), owner, tokenId);
    } else {
        require(addr == address(0));
        expires = base.register(tokenId, owner, duration);
    }

    emit NameRegistered(name, label, owner, cost, expires);

    // Refund any extra payment
    if(msg.value &gt; cost) {
        msg.sender.transfer(msg.value - cost);
    }
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">registerWithConfig</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name, <span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">uint</span> duration, <span class="hljs-keyword">bytes32</span> secret, <span class="hljs-keyword">address</span> resolver, <span class="hljs-keyword">address</span> addr</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
    <span class="hljs-keyword">bytes32</span> commitment <span class="hljs-operator">=</span> makeCommitmentWithConfig(name, owner, secret, resolver, addr);
    <span class="hljs-keyword">uint</span> cost <span class="hljs-operator">=</span> _consumeCommitment(name, duration, commitment);

    <span class="hljs-keyword">bytes32</span> label <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-keyword">bytes</span>(name));
    <span class="hljs-keyword">uint256</span> tokenId <span class="hljs-operator">=</span> <span class="hljs-keyword">uint256</span>(label);

    <span class="hljs-keyword">uint</span> expires;
    <span class="hljs-keyword">if</span>(resolver <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
        <span class="hljs-comment">// Set this contract as the (temporary) owner, giving it</span>
        <span class="hljs-comment">// permission to set up the resolver.</span>
        expires <span class="hljs-operator">=</span> base.register(tokenId, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), duration);

        <span class="hljs-comment">// The nodehash of this label</span>
        <span class="hljs-keyword">bytes32</span> nodehash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(base.baseNode(), label));

        <span class="hljs-comment">// Set the resolver</span>
        base.ens().setResolver(nodehash, resolver);

        <span class="hljs-comment">// Configure the resolver</span>
        <span class="hljs-keyword">if</span> (addr <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
            Resolver(resolver).setAddr(nodehash, addr);
        }

        <span class="hljs-comment">// Now transfer full ownership to the expeceted owner</span>
        base.reclaim(tokenId, owner);
        base.transferFrom(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), owner, tokenId);
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">require</span>(addr <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>));
        expires <span class="hljs-operator">=</span> base.register(tokenId, owner, duration);
    }

    <span class="hljs-keyword">emit</span> NameRegistered(name, label, owner, cost, expires);

    <span class="hljs-comment">// Refund any extra payment</span>
    <span class="hljs-keyword">if</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">></span> cost) {
        <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> - cost);
    }
}
</code></pre><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">第一段: 條件式檢查</h3><p>進入函式後的頭兩行，可以看到又去呼叫了一次前面出現過的 <code>makeCommitmentWithConfig</code>，因此我們主要先看一下第二個呼叫的函式 <code>_consumeCommitment</code></p><pre data-type="codeBlock" text="bytes32 commitment = makeCommitmentWithConfig(name, owner, secret, resolver, addr);
uint cost = _consumeCommitment(name, duration, commitment);
"><code>bytes32 <span class="hljs-attr">commitment</span> = makeCommitmentWithConfig(name, owner, secret, resolver, addr)<span class="hljs-comment">;</span>
uint <span class="hljs-attr">cost</span> = _consumeCommitment(name, duration, commitment)<span class="hljs-comment">;</span>
</code></pre><p>進入程式碼前，我們可以先猜測一下這邊要做的事情，在智能合約中，一個函式若有限制條件，通常便會在函式的進入點做檢查，但上方兩行程式碼的下面也都沒有針對限制條件做確認，因此，我們可以推測，這個函式裡可能便包含了一些條件式上的判斷，例如：確認這個用戶是否曾經經過「Request To Register (遞交申請單)」的步驟、是否超過七天才進行此一步驟的函式呼叫等等…</p><p>以下便是 <code>_consumeCommitment</code> 函式的程式碼，接著筆者再一一做解析:</p><pre data-type="codeBlock" text="function _consumeCommitment(string memory name, uint duration, bytes32 commitment) internal returns (uint256) {
    // Require a valid commitment
    require(commitments[commitment] + minCommitmentAge &lt;= now);

    // If the commitment is too old, or the name is registered, stop
    require(commitments[commitment] + maxCommitmentAge &gt; now);
    require(available(name));

    delete(commitments[commitment]);

    uint cost = rentPrice(name, duration);
    require(duration &gt;= MIN_REGISTRATION_DURATION);
    require(msg.value &gt;= cost);

    return cost;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_consumeCommitment</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name, <span class="hljs-keyword">uint</span> duration, <span class="hljs-keyword">bytes32</span> commitment</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span></span>) </span>{
    <span class="hljs-comment">// Require a valid commitment</span>
    <span class="hljs-built_in">require</span>(commitments[commitment] <span class="hljs-operator">+</span> minCommitmentAge <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-built_in">now</span>);

    <span class="hljs-comment">// If the commitment is too old, or the name is registered, stop</span>
    <span class="hljs-built_in">require</span>(commitments[commitment] <span class="hljs-operator">+</span> maxCommitmentAge <span class="hljs-operator">></span> <span class="hljs-built_in">now</span>);
    <span class="hljs-built_in">require</span>(available(name));

    <span class="hljs-keyword">delete</span>(commitments[commitment]);

    <span class="hljs-keyword">uint</span> cost <span class="hljs-operator">=</span> rentPrice(name, duration);
    <span class="hljs-built_in">require</span>(duration <span class="hljs-operator">></span><span class="hljs-operator">=</span> MIN_REGISTRATION_DURATION);
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">></span><span class="hljs-operator">=</span> cost);

    <span class="hljs-keyword">return</span> cost;
}
</code></pre><p>忽略註解的話，前兩句的確就是在檢查「Request To Register」動作的時間有效性:</p><pre data-type="codeBlock" text="require(commitments[commitment] + minCommitmentAge &lt;= now);
require(commitments[commitment] + maxCommitmentAge &gt; now);
"><code><span class="hljs-built_in">require</span>(commitments[commitment] <span class="hljs-operator">+</span> minCommitmentAge <span class="hljs-operator">&#x3C;</span><span class="hljs-operator">=</span> <span class="hljs-built_in">now</span>);
<span class="hljs-built_in">require</span>(commitments[commitment] <span class="hljs-operator">+</span> maxCommitmentAge <span class="hljs-operator">></span> <span class="hljs-built_in">now</span>);
</code></pre><p>第二行比較沒有問題，先前已經說過，「Request To Register (遞交申請單)」動作後，用戶有七天的考慮期，超過了就要重新遞交申請單。</p><p>但第一行又是什麼呢？<code>minCommitmentAge</code>也是在合約創建時便有寫入初始值的全域變數，此數值的設定是 <code>60</code> 秒，也就是一分鐘。此處在先前的文章中也有提到，當用戶進行「Request To Register (遞交申請單)」後，會有一分鐘的等待期，目的是要避免同時間有兩個用戶針對同個 ENS 名稱做「Request To Register (遞交申請單)」，有了這個等待期的機制設計，減少第二次再同時進入「Register (註冊)」的機會。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/39863f0f1cba3b8b1e7d680d8e0e7c4888bcd9f1f28c1972fa9cadce26895dff.png" alt="可以透過 Etherscan 的「Read Contract」看到目前的設定值" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">可以透過 Etherscan 的「Read Contract」看到目前的設定值</figcaption></figure><p>接著來看第三個檢查，是呼叫了一個 <code>available</code> 的函式，這段程式碼則是針對用戶欲註冊的 ENS 名稱做檢查，ENS 名稱必須符合:</p><ol><li><p>名稱至少要三個字</p></li><li><p>未被註冊走、未落入註冊者的有效期+寬限期內</p></li></ol><pre data-type="codeBlock" text="function available(string memory name) public view returns(bool) {
    bytes32 label = keccak256(bytes(name));
    return valid(name) &amp;&amp; base.available(uint256(label));
}

function valid(string memory name) public pure returns(bool) {
    return name.strlen() &gt;= 3;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">available</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
    <span class="hljs-keyword">bytes32</span> label <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-keyword">bytes</span>(name));
    <span class="hljs-keyword">return</span> valid(name) <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> base.available(<span class="hljs-keyword">uint256</span>(label));
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">valid</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">pure</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
    <span class="hljs-keyword">return</span> name.strlen() <span class="hljs-operator">></span><span class="hljs-operator">=</span> <span class="hljs-number">3</span>;
}
</code></pre><p>其中，值得一提的是 <code>base.available(uint256(label))</code>，此處的 <code>base</code> 是在合約創建之初就寫入的全域變數，可以參考上方有貼過的 <code>constructor</code> 創建子，其中可以看到 <code>base</code> 的資料型別是 <code>BaseRegistrar</code> ，這部分在 <code>ETHRegistrarController</code> 中只定義了 <code>interface</code> ，亦即實際的實作應在其他智能合約中，此智能合約即為 <code>BaseRegistrarImplementation</code> ，從中可以找到 <code>BaseRegistrar</code>合約、 <code>available</code> 的實作:</p><pre data-type="codeBlock" text="// Returns true iff the specified name is available for registration.
function available(uint256 id) public view returns(bool) {
    // Not available if it&apos;s registered here or in its grace period.
    return expiries[id] + GRACE_PERIOD &lt; now;
}
"><code><span class="hljs-comment">// Returns true iff the specified name is available for registration.</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">available</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> id</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
    <span class="hljs-comment">// Not available if it's registered here or in its grace period.</span>
    <span class="hljs-keyword">return</span> expiries[id] <span class="hljs-operator">+</span> GRACE_PERIOD <span class="hljs-operator">&#x3C;</span> <span class="hljs-built_in">now</span>;
}
</code></pre><p>此處的 <code>GRACE_PERIOD</code> 即為 90 天寬限期，當註冊者的 ENS 名稱過期了，只要在 90 天內進行延長即可，超過 90 天的寬限期後，這個 ENS 名稱才會重新開放給所有人進行註冊登記。</p><p>至此，<code>_consumeCommitment</code> 已完成了「Register (註冊)」動作所需要的條件檢查了，接著往下看的話，會先把此用戶在 <code>commitments</code>內的紀錄刪除，接著，使用 <code>rentPrice</code> 來計算此用戶需要付出的購買費用，並進行費用與最小租用年限的檢查:</p><pre data-type="codeBlock" text="delete(commitments[commitment]);

uint cost = rentPrice(name, duration);
require(duration &gt;= MIN_REGISTRATION_DURATION);
require(msg.value &gt;= cost);

return cost;
"><code><span class="hljs-keyword">delete</span>(commitments[commitment]);

<span class="hljs-keyword">uint</span> cost <span class="hljs-operator">=</span> rentPrice(name, duration);
<span class="hljs-built_in">require</span>(duration <span class="hljs-operator">></span><span class="hljs-operator">=</span> MIN_REGISTRATION_DURATION);
<span class="hljs-built_in">require</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">></span><span class="hljs-operator">=</span> cost);

<span class="hljs-keyword">return</span> cost;
</code></pre><p>此處的 <code>MIN_REGISTRATION_DURATION</code> 是 28 天，此部分在 ENS 的文件中沒有被提及，因為 UI 介面上的顯示最小租用期是 1 年，所以原本筆者以為這個設定應為 365 天。</p><p><code>rentPrice</code> 函式無庸置疑是在計算 ENS 名稱的長度與年限換算的租用費用:</p><pre data-type="codeBlock" text="    function rentPrice(string memory name, uint duration) view public returns(uint) {
        bytes32 hash = keccak256(bytes(name));
        return prices.price(name, base.nameExpires(uint256(hash)), duration);
    }
"><code>    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">rentPrice</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name, <span class="hljs-keyword">uint</span> duration</span>) <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint</span></span>) </span>{
        <span class="hljs-keyword">bytes32</span> hash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-keyword">bytes</span>(name));
        <span class="hljs-keyword">return</span> prices.price(name, base.nameExpires(<span class="hljs-keyword">uint256</span>(hash)), duration);
    }
</code></pre><p>可以看到，這個函式裡有再呼叫到另外兩個智能合約的函式，以下我們逐一檢視。</p><p>剛剛有提到 <code>base</code> 是在智能合約創建之初就設定好的全域變數，指向的是 <code>BaseRegistrar</code>，因此，我們從該至能合約中找到 <code>nameExpires</code> 函式的實作如下:</p><pre data-type="codeBlock" text="    // Returns the expiration timestamp of the specified id.
    function nameExpires(uint256 id) external view returns(uint) {
        return expiries[id];
    }
"><code>    <span class="hljs-comment">// Returns the expiration timestamp of the specified id.</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">nameExpires</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> id</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint</span></span>) </span>{
        <span class="hljs-keyword">return</span> expiries[id];
    }
</code></pre><p>此處我們先知道這個函式會回傳此 ENS 名稱當初註冊的年限結束時間點，是以 timestamp 作為回傳值。至於此 <code>expiries</code> 的 mapping 寫入是在何處發生的，將在後面的程式碼中看到。</p><p>另外一個在 <code>rentPrice</code> 函式中使用到的智能合約是 <code>prices</code>，它一樣是在合約創建之初就設定好了，資料型態是 <code>PriceOracle</code>，這次筆者是透過 Etherscan 的 Label 標註為 ENS 的合約地址中找到這個智能合約。我們要找的 <code>price</code> 函式的實作如下:</p><pre data-type="codeBlock" text="    /**
     * @dev Returns the price to register or renew a name.
     * @param name The name being registered or renewed.
     * @param duration How long the name is being registered or extended for, in seconds.
     * @return The price of this renewal or registration, in wei.
     */
    function price(string calldata name, uint /*expires*/, uint duration) view external returns(uint) {
        uint len = name.strlen();
        if(len &gt; rentPrices.length) {
            len = rentPrices.length;
        }
        require(len &gt; 0);
        uint priceUSD = rentPrices[len - 1].mul(duration);

        // Price of one ether in attodollars
        uint ethPrice = uint(usdOracle.read());

        // priceUSD and ethPrice are both fixed-point values with 18dp, so we
        // multiply the numerator by 1e18 before dividing.
        return priceUSD.mul(1e18).div(ethPrice);
    }
"><code>    <span class="hljs-comment">/**
     * @dev Returns the price to register or renew a name.
     * @param name The name being registered or renewed.
     * @param duration How long the name is being registered or extended for, in seconds.
     * @return The price of this renewal or registration, in wei.
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">price</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">calldata</span> name, <span class="hljs-keyword">uint</span> <span class="hljs-comment">/*expires*/</span>, <span class="hljs-keyword">uint</span> duration</span>) <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint</span></span>) </span>{
        <span class="hljs-keyword">uint</span> len <span class="hljs-operator">=</span> name.strlen();
        <span class="hljs-keyword">if</span>(len <span class="hljs-operator">></span> rentPrices.<span class="hljs-built_in">length</span>) {
            len <span class="hljs-operator">=</span> rentPrices.<span class="hljs-built_in">length</span>;
        }
        <span class="hljs-built_in">require</span>(len <span class="hljs-operator">></span> <span class="hljs-number">0</span>);
        <span class="hljs-keyword">uint</span> priceUSD <span class="hljs-operator">=</span> rentPrices[len <span class="hljs-operator">-</span> <span class="hljs-number">1</span>].mul(duration);

        <span class="hljs-comment">// Price of one ether in attodollars</span>
        <span class="hljs-keyword">uint</span> ethPrice <span class="hljs-operator">=</span> <span class="hljs-keyword">uint</span>(usdOracle.read());

        <span class="hljs-comment">// priceUSD and ethPrice are both fixed-point values with 18dp, so we</span>
        <span class="hljs-comment">// multiply the numerator by 1e18 before dividing.</span>
        <span class="hljs-keyword">return</span> priceUSD.mul(<span class="hljs-number">1e18</span>).div(ethPrice);
    }
</code></pre><p>此部分的程式碼算很好理解，在實作的部分就是以 ENS 的以下定價規則乘以用戶想要租用的年限後，再將美金價格藉由 <code>usdOracle</code> 合約的 ETH/USD 價格進行換算。</p><ol><li><p>五個字以上: 年費 5 美元</p></li><li><p>四個字: 年費 160 美元</p></li><li><p>三個字: 年費 640 美元</p></li></ol><p>至此，我們總算一起看完了 <code>_consumeCommitment</code> 函式的實作，稍微複習一下，這個函式在做的事情有:</p><ol><li><p>檢查「Request To Register (遞交申請書)」的有效性</p></li><li><p>檢查欲註冊的 ENS 名稱有效性</p></li><li><p>計算此 ENS 名稱長度搭配用戶想要租用的年限所對應的 ETH 價格</p></li><li><p>確認用戶餘額足夠支付後，將 ETH 價格回傳</p></li></ol><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">第二段: 首次註冊流程</h3><p>讓我們再次回到「Register (註冊)」動作所呼叫的 <code>registerWithConfig</code> 函式中，經過了條件檢查後，其下出現一個 <code>if-else</code> 判斷式:</p><pre data-type="codeBlock" text="bytes32 label = keccak256(bytes(name));
uint256 tokenId = uint256(label);
uint expires;
if(resolver != address(0)) {
  ...
} else {
  ...
}
"><code><span class="hljs-keyword">bytes32</span> label <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-keyword">bytes</span>(name));
<span class="hljs-keyword">uint256</span> tokenId <span class="hljs-operator">=</span> <span class="hljs-keyword">uint256</span>(label);
<span class="hljs-keyword">uint</span> expires;
<span class="hljs-keyword">if</span>(resolver <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
  ...
} <span class="hljs-keyword">else</span> {
  ...
}
</code></pre><p>我們先確認區域變數的宣告，其中 <code>label</code> 會是 ENS 名稱轉成 bytes 後的 hash 字串，<code>tokenId</code> 是 <code>label</code>再轉成的整數數字，從上方一路看 ENS 的程式碼看下來，我們可以猜測得到，這個 <code>tokenId</code> 便會是剛剛在 <code>expiries[id]</code> 中的 key 帶入值。而底下沒有預設值的 <code>expires</code> 大概就會用於擺放藉由用戶設定的 <code>duration</code> 換算成的 timestamp。</p><p>由於在筆者實際購買、綁定 ENS 名稱的過程中，resolver 在此時是一個非 <code>0x0</code> 的地址，因此，我們可以先猜測，<code>resolver != address(0)</code> 的條件式中，即是首次申購會進入的流程。</p><p>以下為節錄了<code>resolver != address(0)</code> 的條件式內的程式片段，下方將會把此段程式在拆成三小段逐一進行解析 :</p><pre data-type="codeBlock" text="// Set this contract as the (temporary) owner, giving it
// permission to set up the resolver.
expires = base.register(tokenId, address(this), duration);

// The nodehash of this label
bytes32 nodehash = keccak256(abi.encodePacked(base.baseNode(), label));

// Set the resolver
base.ens().setResolver(nodehash, resolver);

// Configure the resolver
if (addr != address(0)) {
    Resolver(resolver).setAddr(nodehash, addr);
}

// Now transfer full ownership to the expeceted owner
base.reclaim(tokenId, owner);
base.transferFrom(address(this), owner, tokenId);
"><code><span class="hljs-comment">// Set this contract as the (temporary) owner, giving it</span>
<span class="hljs-comment">// permission to set up the resolver.</span>
expires <span class="hljs-operator">=</span> base.register(tokenId, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), duration);

<span class="hljs-comment">// The nodehash of this label</span>
<span class="hljs-keyword">bytes32</span> nodehash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(base.baseNode(), label));

<span class="hljs-comment">// Set the resolver</span>
base.ens().setResolver(nodehash, resolver);

<span class="hljs-comment">// Configure the resolver</span>
<span class="hljs-keyword">if</span> (addr <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
    Resolver(resolver).setAddr(nodehash, addr);
}

<span class="hljs-comment">// Now transfer full ownership to the expeceted owner</span>
base.reclaim(tokenId, owner);
base.transferFrom(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), owner, tokenId);
</code></pre><h4 id="h-erc721-ens" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">第一小段: 生成 ERC721、生成 ENS 樹節點</h4><p>首先，我們一樣得回到 <code>BaseRegistrar</code> 智能合約中的 <code>register</code> 函式，<strong>不過此處需要特別注意一下，上方程式碼中，呼叫</strong> <code>register</code> <strong>的傳入參數中，第二的參數帶入的是</strong> <code>address(this)</code><strong>，也就是當前智能合約</strong> <code>ETHRegistrarController</code> <strong>的地址，因此，下方在解析</strong> <code>register</code> <strong>的函式呼叫中，</strong> <code>owner</code> <strong>都還不是實際用戶的地址</strong>:</p><pre data-type="codeBlock" text="/**
 * @dev Register a name.
 * @param id The token ID (keccak256 of the label).
 * @param owner The address that should own the registration.
 * @param duration Duration in seconds for the registration.
 */
function register(uint256 id, address owner, uint duration) external returns(uint) {
  return _register(id, owner, duration, true);
}

function _register(uint256 id, address owner, uint duration, bool updateRegistry) internal live onlyController returns(uint) {
    require(available(id));
    require(now + duration + GRACE_PERIOD &gt; now + GRACE_PERIOD); // Prevent future overflow

    expiries[id] = now + duration;
    if(_exists(id)) {
        // Name was previously owned, and expired
        _burn(id);
    }
    _mint(owner, id);
    if(updateRegistry) {
        ens.setSubnodeOwner(baseNode, bytes32(id), owner);
    }

    emit NameRegistered(id, owner, now + duration);

    return now + duration;
}
"><code><span class="hljs-comment">/**
 * @dev Register a name.
 * @param id The token ID (keccak256 of the label).
 * @param owner The address that should own the registration.
 * @param duration Duration in seconds for the registration.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">register</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> id, <span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">uint</span> duration</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint</span></span>) </span>{
  <span class="hljs-keyword">return</span> _register(id, owner, duration, <span class="hljs-literal">true</span>);
}

<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_register</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> id, <span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">uint</span> duration, <span class="hljs-keyword">bool</span> updateRegistry</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title">live</span> <span class="hljs-title">onlyController</span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span>(<span class="hljs-params"><span class="hljs-keyword">uint</span></span>) </span>{
    <span class="hljs-built_in">require</span>(available(id));
    <span class="hljs-built_in">require</span>(<span class="hljs-built_in">now</span> <span class="hljs-operator">+</span> duration <span class="hljs-operator">+</span> GRACE_PERIOD <span class="hljs-operator">></span> <span class="hljs-built_in">now</span> <span class="hljs-operator">+</span> GRACE_PERIOD); <span class="hljs-comment">// Prevent future overflow</span>

    expiries[id] <span class="hljs-operator">=</span> <span class="hljs-built_in">now</span> <span class="hljs-operator">+</span> duration;
    <span class="hljs-keyword">if</span>(_exists(id)) {
        <span class="hljs-comment">// Name was previously owned, and expired</span>
        _burn(id);
    }
    _mint(owner, id);
    <span class="hljs-keyword">if</span>(updateRegistry) {
        ens.setSubnodeOwner(baseNode, <span class="hljs-keyword">bytes32</span>(id), owner);
    }

    <span class="hljs-keyword">emit</span> NameRegistered(id, owner, <span class="hljs-built_in">now</span> <span class="hljs-operator">+</span> duration);

    <span class="hljs-keyword">return</span> <span class="hljs-built_in">now</span> <span class="hljs-operator">+</span> duration;
}
</code></pre><p>直接看 <code>_register</code> 函式，前兩行是在做執行條件的檢查，由於上方已有相似的說明過，此處就不再贅述。接著，<code>expiries[id] = now + duration;</code> 是在 <code>BaseRegister</code> 中，紀錄下此 ENS 名稱的過期時間。</p><p>接在這之後，可以看到下方的兩個針對 ERC721 NFT (在先前的文章有提到，一般 <code>.eth</code> 結尾的 ENS 名稱實際上是以 ERC721 實作而成) 的重要動作:</p><ol><li><p>若此 ENS 名稱曾經被人註冊過，則在此時進行 ERC721 NFT 燒毀的動作。</p></li><li><p>重新鑄造一個新的 ERC721 NFT 給當前用戶</p></li></ol><pre data-type="codeBlock" text="/**
 * @dev Internal function to burn a specific token
 * Reverts if the token does not exist
 * @param tokenId uint256 ID of the token being burned
 */
function _burn(uint256 tokenId) internal {
    _burn(ownerOf(tokenId), tokenId);
}

/**
 * @dev Internal function to burn a specific token
 * Reverts if the token does not exist
 * Deprecated, use _burn(uint256) instead.
 * @param owner owner of the token to burn
 * @param tokenId uint256 ID of the token being burned
 */
function _burn(address owner, uint256 tokenId) internal {
    require(ownerOf(tokenId) == owner);

    _clearApproval(tokenId);

    _ownedTokensCount[owner] = _ownedTokensCount[owner].sub(1);
    _tokenOwner[tokenId] = address(0);

    emit Transfer(owner, address(0), tokenId);
}

/**
 * @dev Internal function to mint a new token
 * Reverts if the given token ID already exists
 * @param to The address that will own the minted token
 * @param tokenId uint256 ID of the token to be minted
 */
function _mint(address to, uint256 tokenId) internal {
    require(to != address(0));
    require(!_exists(tokenId));

    _tokenOwner[tokenId] = to;
    _ownedTokensCount[to] = _ownedTokensCount[to].add(1);

    emit Transfer(address(0), to, tokenId);
}
"><code><span class="hljs-comment">/**
 * @dev Internal function to burn a specific token
 * Reverts if the token does not exist
 * @param tokenId uint256 ID of the token being burned
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_burn</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
    _burn(ownerOf(tokenId), tokenId);
}

<span class="hljs-comment">/**
 * @dev Internal function to burn a specific token
 * Reverts if the token does not exist
 * Deprecated, use _burn(uint256) instead.
 * @param owner owner of the token to burn
 * @param tokenId uint256 ID of the token being burned
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_burn</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">uint256</span> tokenId</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
    <span class="hljs-built_in">require</span>(ownerOf(tokenId) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> owner);

    _clearApproval(tokenId);

    _ownedTokensCount[owner] <span class="hljs-operator">=</span> _ownedTokensCount[owner].sub(<span class="hljs-number">1</span>);
    _tokenOwner[tokenId] <span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>);

    <span class="hljs-keyword">emit</span> Transfer(owner, <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), tokenId);
}

<span class="hljs-comment">/**
 * @dev Internal function to mint a new token
 * Reverts if the given token ID already exists
 * @param to The address that will own the minted token
 * @param tokenId uint256 ID of the token to be minted
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_mint</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> to, <span class="hljs-keyword">uint256</span> tokenId</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> </span>{
    <span class="hljs-built_in">require</span>(to <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>));
    <span class="hljs-built_in">require</span>(<span class="hljs-operator">!</span>_exists(tokenId));

    _tokenOwner[tokenId] <span class="hljs-operator">=</span> to;
    _ownedTokensCount[to] <span class="hljs-operator">=</span> _ownedTokensCount[to].add(<span class="hljs-number">1</span>);

    <span class="hljs-keyword">emit</span> Transfer(<span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>), to, tokenId);
}
</code></pre><p>上述的 <code>_burn</code> 與 <code>_mint</code> 動作較為單純，此處也不再一一解析。</p><p>回到 <code>_register</code> 函式中，在操作完 ERC721 NFT 後，下方剩餘的三個動作:</p><pre data-type="codeBlock" text="if(updateRegistry) {
    ens.setSubnodeOwner(baseNode, bytes32(id), owner);
}
emit NameRegistered(id, owner, now + duration);
return now + duration;
"><code><span class="hljs-keyword">if</span>(updateRegistry) {
    ens.setSubnodeOwner(baseNode, <span class="hljs-keyword">bytes32</span>(id), owner);
}
<span class="hljs-keyword">emit</span> NameRegistered(id, owner, <span class="hljs-built_in">now</span> <span class="hljs-operator">+</span> duration);
<span class="hljs-keyword">return</span> <span class="hljs-built_in">now</span> <span class="hljs-operator">+</span> duration;
</code></pre><p>此處的 <code>updateRegistry</code> 布林值是由 <code>register</code> 函式中帶入的，一定是 true，因此 <code>ens.setSubnodeOwner</code> 的動作將一定會執行。因此，我們一起來看一下 <code>ens</code> 代表的是哪個智能合約、其內的函式 <code>setSubnodeOwner</code> 又是在實作怎樣的邏輯呢？</p><p><code>ens</code> 是在 <code>BaseRegistrar</code> 智能合約部署之初便有設定初始值的合約地址，它的資料型別是 <code>ENS</code>。而用來帶入 <code>setSubnodeOwner</code> 函式中的 <code>baseNode</code> 也是在此時被建立，由 Etherscan 的 Read Contract 中可以看到，此變數的內容為 <code>0x93cdeb708b7545dc668eb9280176169d1c33cfd8ed6f04690a0bcc88a93fc4ae</code></p><pre data-type="codeBlock" text="constructor(ENS _ens, bytes32 _baseNode) public {
    ens = _ens;
    baseNode = _baseNode;
}
"><code>constructor(ENS _ens, bytes32 _baseNode) public {
    <span class="hljs-attr">ens</span> = _ens<span class="hljs-comment">;</span>
    <span class="hljs-attr">baseNode</span> = _baseNode<span class="hljs-comment">;</span>
}
</code></pre><p>不過當筆者找到 <code>ENS</code> 智能合約的實作時，發現他不是用 Solidity 實作而成的，其中，<code>setSubnodeOwner</code>函式的實作如下:</p><pre data-type="codeBlock" text=";; @notice Transfers ownership of a subnode to a new address. May only be 
;;         called by the owner of the parent node. 
;; @dev Signature: setSubnodeOwner(bytes32,bytes32,address) 
;; @param node The parent node. 
;; @param label The hash of the label specifying the subnode. 
;; @param new-owner The address of the new owner. 

(def &apos;node (calldataload 0x04)) 
(def &apos;label (calldataload 0x24)) 
(def &apos;new-owner (calldataload 0x44)) 

(function set-subnode-owner 
  (seq (only-node-owner node) 

    ;; Transfer ownership by storing passed-in address. 
    (set-subowner node label new-owner) 

    ;; Emit an event about the transfer. 
    ;; NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); 
    (mstore call-result new-owner) 
    (log3 call-result 32 
        (sha3 0x00 (lit 0x00 &quot;NewOwner(bytes32,bytes32,address)&quot;)) 
        node label) 

    ;; Nothing to return. 
    (stop))) 
"><code><span class="hljs-comment">;; @notice Transfers ownership of a subnode to a new address. May only be </span>
<span class="hljs-comment">;;         called by the owner of the parent node. </span>
<span class="hljs-comment">;; @dev Signature: setSubnodeOwner(bytes32,bytes32,address) </span>
<span class="hljs-comment">;; @param node The parent node. </span>
<span class="hljs-comment">;; @param label The hash of the label specifying the subnode. </span>
<span class="hljs-comment">;; @param new-owner The address of the new owner. </span>

<span class="hljs-punctuation">(</span>def 'node <span class="hljs-punctuation">(</span>calldataload <span class="hljs-number">0x04</span><span class="hljs-punctuation">))</span> 
<span class="hljs-punctuation">(</span>def 'label <span class="hljs-punctuation">(</span>calldataload <span class="hljs-number">0x24</span><span class="hljs-punctuation">))</span> 
<span class="hljs-punctuation">(</span>def 'new-owner <span class="hljs-punctuation">(</span>calldataload <span class="hljs-number">0x44</span><span class="hljs-punctuation">))</span> 

<span class="hljs-punctuation">(</span>function set-subnode-owner 
  <span class="hljs-punctuation">(</span>seq <span class="hljs-punctuation">(</span>only-node-owner node<span class="hljs-punctuation">)</span> 

    <span class="hljs-comment">;; Transfer ownership by storing passed-in address. </span>
    <span class="hljs-punctuation">(</span>set-subowner node label new-owner<span class="hljs-punctuation">)</span> 

    <span class="hljs-comment">;; Emit an event about the transfer. </span>
    <span class="hljs-comment">;; NewOwner(bytes32 indexed node, bytes32 indexed label, address owner); </span>
    <span class="hljs-punctuation">(</span>mstore <span class="hljs-keyword">call</span>-<span class="hljs-keyword">result</span> new-owner<span class="hljs-punctuation">)</span> 
    <span class="hljs-punctuation">(</span>log3 <span class="hljs-keyword">call</span>-<span class="hljs-keyword">result</span> <span class="hljs-number">32</span> 
        <span class="hljs-punctuation">(</span>sha3 <span class="hljs-number">0x00</span> <span class="hljs-punctuation">(</span>lit <span class="hljs-number">0x00</span> <span class="hljs-string">"NewOwner(bytes32,bytes32,address)"</span><span class="hljs-punctuation">))</span> 
        node label<span class="hljs-punctuation">)</span> 

    <span class="hljs-comment">;; Nothing to return. </span>
    <span class="hljs-punctuation">(</span>stop<span class="hljs-punctuation">)))</span> 
</code></pre><p>因為 ENS 的技術文章中有提及，他們的 ENS 名稱儲存方式是與 DNS 的樹狀資料結構相同，因此我們可以猜測，傳入值的 <code>baseNode</code> 即為 <code>eth</code>這個最上層的樹根節點，而傳入值 <code>bytes32(id)</code> 即為此用戶想要註冊的 ENS 名稱子節點。因此，這個 <code>setSubnodeOwner</code>的用途，大概便是將傳入的地址寫入到這顆 ENS 樹結構中的實作。</p><p>最後， <code>_register</code>函式中的最後兩個動作分別是:</p><ol><li><p>觸發 <code>NameRegistered</code> 事件通知</p></li><li><p>回傳 <code>now + duration</code> 的 timestamp</p></li></ol><p>至此，ENS 名稱對應的 ERC721 NFT 被鑄造出來、在 ENS 名稱的樹狀結構中記錄了新的 ENS 名稱。不過，需注意，此時 NFT 的持有者與 ENS 名稱樹的持有者，都是指向 <code>ETHRegistrarController</code> 合約地址。</p><h4 id="h-ens" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">第二小段: 設定 ENS 名稱與用戶地址關聯</h4><p>再次回到 <code>registerWithConfig</code>的 <code>resolver != address(0)</code> 的條件式中，第二大段的程式邏輯是在對 resolver 做設定，如同最開始所述，ENS 的程式碼架構中，大致上分成兩大塊，一塊稱為 <code>ENS Registry</code> ，是用來存放 ENS 名稱對應到的 resolver 地址，另一塊是 <code>Resolver</code> ，用於存放 ENS 名稱對應到的實際用戶地址與用戶基本資料內容。</p><p>而這一段程式碼，便是進行上述的兩項設定:</p><ol><li><p><code>base.ens().setResolver(nodehash, resolver)</code>: 設定 ENS 名稱對應到的 resolver 地址</p></li><li><p><code>Resolver(resolver).setAddr(nodehash, addr)</code>: 在 resolver 的智能合約中，設定 ENS 名稱實際上對應到的用戶地址</p></li></ol><pre data-type="codeBlock" text="bytes32 nodehash = keccak256(abi.encodePacked(base.baseNode(), label));

// Set the resolver
base.ens().setResolver(nodehash, resolver);

// Configure the resolver
if (addr != address(0)) {
    Resolver(resolver).setAddr(nodehash, addr);
}
"><code><span class="hljs-keyword">bytes32</span> nodehash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(base.baseNode(), label));

<span class="hljs-comment">// Set the resolver</span>
base.ens().setResolver(nodehash, resolver);

<span class="hljs-comment">// Configure the resolver</span>
<span class="hljs-keyword">if</span> (addr <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
    Resolver(resolver).setAddr(nodehash, addr);
}
</code></pre><h4 id="h-nft-ens" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">第三小段: NFT 轉移、ENS 數節點綁定持有者</h4><p>最後，<code>registerWithConfig</code> 中的 <code>resolver != address(0)</code> 的條件式中，最後兩個邏輯是:</p><pre data-type="codeBlock" text="// Now transfer full ownership to the expeceted owner
base.reclaim(tokenId, owner);
base.transferFrom(address(this), owner, tokenId);
"><code><span class="hljs-comment">// Now transfer full ownership to the expeceted owner</span>
base.reclaim(tokenId, owner);
base.transferFrom(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), owner, tokenId);
</code></pre><p>此處的 <code>base.reclaim(tokenId, owner)</code>實際上的實作為</p><pre data-type="codeBlock" text="/**
 * @dev Reclaim ownership of a name in ENS, if you own it in the registrar.
 */
function reclaim(uint256 id, address owner) external live {
    require(_isApprovedOrOwner(msg.sender, id));
    ens.setSubnodeOwner(baseNode, bytes32(id), owner);
}
"><code><span class="hljs-comment">/**
 * @dev Reclaim ownership of a name in ENS, if you own it in the registrar.
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">reclaim</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> id, <span class="hljs-keyword">address</span> owner</span>) <span class="hljs-title"><span class="hljs-keyword">external</span></span> <span class="hljs-title">live</span> </span>{
    <span class="hljs-built_in">require</span>(_isApprovedOrOwner(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, id));
    ens.setSubnodeOwner(baseNode, <span class="hljs-keyword">bytes32</span>(id), owner);
}
</code></pre><p>此處可以先看第二行，這個函式在前面 <code>register</code> 函式中已經執行過一次了，但當時傳入 <code>register</code> 函式中的 <code>owner</code> 地址實際上是 <code>ETHRegistrarController</code> 合約地址，因此，此處才是實際將 ENS 名稱的註冊人設定為用戶的地方。</p><p>而 <code>_isApprovedOrOwner</code> 的程式碼如下，其用途是在檢查 ERC721 NFT 是否符合以下其中一項：</p><ol><li><p>原本的所有人為 <code>ETHRegistrarController</code>合約 (上方可以看到傳入參數是 <code>msg.sender</code>，因為此時是從 <code>ETHRegistrarController</code> 觸發 <code>base.reclaim</code> 函式，因此，此時的 <code>msg.sender</code> 預期會是 <code>ETHRegistrarController</code>。而假如有經過正常流程進到 <code>reclaim</code> 函式的話，會先經過 <code>register</code> 將 ENS 名稱暫時歸給 <code>ETHRegistrarController</code> 合約地址的暫態。)</p></li><li><p><code>getApproved</code> 預期是針對舊版的 ERC721 的實作，舊版尚未有 <code>approveForAll</code> 的定義時，會是如同 ERC20 的定義，得要針對單一 token id 進行 <code>approve</code></p></li><li><p><code>isApprovedForAll</code> 即是上述所說，<code>setApproveForAll</code> 是 ERC721 協議中需要實作的函式，用途是授權某個地址 (通常是合約地址) 可以轉移你的 ERC721 NFT</p></li></ol><pre data-type="codeBlock" text="/**
 * @dev Returns whether the given spender can transfer a given token ID
 * @param spender address of the spender to query
 * @param tokenId uint256 ID of the token to be transferred
 * @return bool whether the msg.sender is approved for the given token ID,
 *    is an operator of the owner, or is the owner of the token
 */
function _isApprovedOrOwner(address spender, uint256 tokenId) internal view returns (bool) {
    address owner = ownerOf(tokenId);
    return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
}

/**
 * @dev Gets the owner of the specified token ID
 * @param tokenId uint256 ID of the token to query the owner of
 * @return owner address currently marked as the owner of the given token ID
 */
function ownerOf(uint256 tokenId) public view returns (address) {
    address owner = _tokenOwner[tokenId];
    require(owner != address(0));
    return owner;
}
"><code><span class="hljs-comment">/**
 * @dev Returns whether the given spender can transfer a given token ID
 * @param spender address of the spender to query
 * @param tokenId uint256 ID of the token to be transferred
 * @return bool whether the msg.sender is approved for the given token ID,
 *    is an operator of the owner, or is the owner of the token
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">_isApprovedOrOwner</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> spender, <span class="hljs-keyword">uint256</span> tokenId</span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">bool</span></span>) </span>{
    <span class="hljs-keyword">address</span> owner <span class="hljs-operator">=</span> ownerOf(tokenId);
    <span class="hljs-keyword">return</span> (spender <span class="hljs-operator">=</span><span class="hljs-operator">=</span> owner <span class="hljs-operator">|</span><span class="hljs-operator">|</span> getApproved(tokenId) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> spender <span class="hljs-operator">|</span><span class="hljs-operator">|</span> isApprovedForAll(owner, spender));
}

<span class="hljs-comment">/**
 * @dev Gets the owner of the specified token ID
 * @param tokenId uint256 ID of the token to query the owner of
 * @return owner address currently marked as the owner of the given token ID
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">ownerOf</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> tokenId</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">view</span></span> <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">address</span></span>) </span>{
    <span class="hljs-keyword">address</span> owner <span class="hljs-operator">=</span> _tokenOwner[tokenId];
    <span class="hljs-built_in">require</span>(owner <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>));
    <span class="hljs-keyword">return</span> owner;
}
</code></pre><p>依據正常程序購買、綁定 ENS 名稱的流程下，<code>_isApprovedOrOwner</code> 會因為第一項條件是有達成而回傳 true。</p><p>接著，便在 <code>reclaim</code> 中的 <code>ens.setSubnodeOwner</code> 函式中，將該 ENS 名稱在 ENS 樹結構中紀錄程式當前用戶所持有，並在 <code>registerWithConfig</code> 的 <code>base.transferFrom(address(this), owner, tokenId);</code> 將 ERC721 NFT 實際轉移到用戶的地址中。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">第三段: 延展持有期</h3><p>此時，讓我們再次查看 <code>registerWithConfig</code> 的完整程式碼，我們已經順利看完了 <code>if-else</code> 中 <code>resolver != address(0)</code>:</p><pre data-type="codeBlock" text="function registerWithConfig(string memory name, address owner, uint duration, bytes32 secret, address resolver, address addr) public payable {
    bytes32 commitment = makeCommitmentWithConfig(name, owner, secret, resolver, addr);
    uint cost = _consumeCommitment(name, duration, commitment);

    bytes32 label = keccak256(bytes(name));
    uint256 tokenId = uint256(label);

    uint expires;
    if(resolver != address(0)) {
        // Set this contract as the (temporary) owner, giving it
        // permission to set up the resolver.
        expires = base.register(tokenId, address(this), duration);

        // The nodehash of this label
        bytes32 nodehash = keccak256(abi.encodePacked(base.baseNode(), label));

        // Set the resolver
        base.ens().setResolver(nodehash, resolver);

        // Configure the resolver
        if (addr != address(0)) {
            Resolver(resolver).setAddr(nodehash, addr);
        }

        // Now transfer full ownership to the expeceted owner
        base.reclaim(tokenId, owner);
        base.transferFrom(address(this), owner, tokenId);
    } else {
        require(addr == address(0));
        expires = base.register(tokenId, owner, duration);
    }

    emit NameRegistered(name, label, owner, cost, expires);

    // Refund any extra payment
    if(msg.value &gt; cost) {
        msg.sender.transfer(msg.value - cost);
    }
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">registerWithConfig</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name, <span class="hljs-keyword">address</span> owner, <span class="hljs-keyword">uint</span> duration, <span class="hljs-keyword">bytes32</span> secret, <span class="hljs-keyword">address</span> resolver, <span class="hljs-keyword">address</span> addr</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
    <span class="hljs-keyword">bytes32</span> commitment <span class="hljs-operator">=</span> makeCommitmentWithConfig(name, owner, secret, resolver, addr);
    <span class="hljs-keyword">uint</span> cost <span class="hljs-operator">=</span> _consumeCommitment(name, duration, commitment);

    <span class="hljs-keyword">bytes32</span> label <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-keyword">bytes</span>(name));
    <span class="hljs-keyword">uint256</span> tokenId <span class="hljs-operator">=</span> <span class="hljs-keyword">uint256</span>(label);

    <span class="hljs-keyword">uint</span> expires;
    <span class="hljs-keyword">if</span>(resolver <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
        <span class="hljs-comment">// Set this contract as the (temporary) owner, giving it</span>
        <span class="hljs-comment">// permission to set up the resolver.</span>
        expires <span class="hljs-operator">=</span> base.register(tokenId, <span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), duration);

        <span class="hljs-comment">// The nodehash of this label</span>
        <span class="hljs-keyword">bytes32</span> nodehash <span class="hljs-operator">=</span> <span class="hljs-built_in">keccak256</span>(<span class="hljs-built_in">abi</span>.<span class="hljs-built_in">encodePacked</span>(base.baseNode(), label));

        <span class="hljs-comment">// Set the resolver</span>
        base.ens().setResolver(nodehash, resolver);

        <span class="hljs-comment">// Configure the resolver</span>
        <span class="hljs-keyword">if</span> (addr <span class="hljs-operator">!</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>)) {
            Resolver(resolver).setAddr(nodehash, addr);
        }

        <span class="hljs-comment">// Now transfer full ownership to the expeceted owner</span>
        base.reclaim(tokenId, owner);
        base.transferFrom(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>), owner, tokenId);
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-built_in">require</span>(addr <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-keyword">address</span>(<span class="hljs-number">0</span>));
        expires <span class="hljs-operator">=</span> base.register(tokenId, owner, duration);
    }

    <span class="hljs-keyword">emit</span> NameRegistered(name, label, owner, cost, expires);

    <span class="hljs-comment">// Refund any extra payment</span>
    <span class="hljs-keyword">if</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">></span> cost) {
        <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> - cost);
    }
}
</code></pre><p>接著，我們可以稍微猜測一下 <code>if-else</code> 中的 <code>else</code>判斷式中的用途，在 <code>else</code> 下，除了檢查地址要是 <code>0x0</code> 外，就只做了一件事情:</p><pre data-type="codeBlock" text="expires = base.register(tokenId, owner, duration);
"><code><span class="hljs-attr">expires</span> = base.register(tokenId, owner, duration)<span class="hljs-comment">;</span>
</code></pre><p>依據先前看過 <code>register</code> 函式的內容後，這裡在做的事情，可以知道，其實是在做期限的延展使用的。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">第四段: 觸發通知事件、退款</h3><p>最後，在 <code>registerWithConfig</code> 中，只剩下最後兩個主要邏輯:</p><ol><li><p>觸發 <code>NameRegistered</code> 通知</p></li><li><p>將估算下的註冊、購買費用與實際花費的差額退款給用戶的動作</p></li></ol><pre data-type="codeBlock" text="emit NameRegistered(name, label, owner, cost, expires);

// Refund any extra payment
if(msg.value &gt; cost) {
    msg.sender.transfer(msg.value - cost);
}
"><code><span class="hljs-keyword">emit</span> NameRegistered(name, label, owner, cost, expires);

<span class="hljs-comment">// Refund any extra payment</span>
<span class="hljs-keyword">if</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> <span class="hljs-operator">></span> cost) {
    <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span> - cost);
}
</code></pre><p>此處會有差額的原因，主要是因為 ENS 年限是以美金定價，但實收 ETH，在註冊提交到智能合約完整執行完畢的過程中，有可能會因為 ETH/USD 的價格波動而產生差額。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">結語</h2><p>有鑒於目前在大多數的 NFT 項目中，大多是以鑄造為主，比較少見針對 NFT 的後續應用，而在智能合約的撰寫上，多數工程師們也都還處於磨劍階段 (筆者也是)，因此，筆者希望可以藉由解析較為大型、老牌的智能合約應用來促進更近一步的技術討論，也藉此讓自己多向既有老項目們做學習，作為未來實作、應用的養分。</p><p>在 Etherscan 中，有針對 <code>ENS</code> 的合約進行標籤化的整理，可以由以下網址看到，除了這篇文章提及的智能合約外，尚有許多其他未被提及的智能合約內容:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/accounts/label/ens">https://etherscan.io/accounts/label/ens</a></p><p>而此篇文章在解析完 <code>commit</code> 與 <code>registerWithConfig</code> 兩個函式後，篇幅也已太長，導致反向綁定與設定個人資料的智能合約呼叫無法在同一篇文章中進行說明。但下一篇文章預計會針對 DNS 網址綁定的智能合約流程進行解析，完成後，筆者會再整理 ENS 智能合約中，筆者在這兩篇文章中未提及的部分、另闢一篇新文章做解析與介紹。</p><p>先前的兩篇文章則是針對註冊流程做紀錄，也歡迎閱讀:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/carawang.eth/_zLa85tHYaps-ivRUE1DspMYWuNf86RDGrKvRJxHvJE">https://mirror.xyz/carawang.eth/_zLa85tHYaps-ivRUE1DspMYWuNf86RDGrKvRJxHvJE</a></p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/carawang.eth/19c_8KPkWYyGjaEcVtF57eY-yvWKV99bXUCUz13TDCA">https://mirror.xyz/carawang.eth/19c_8KPkWYyGjaEcVtF57eY-yvWKV99bXUCUz13TDCA</a></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">寫在後面的介紹</h2><p>「區塊吧台」是 Metaverse Pets 社群中的子節目，會透過在 Discord 上直播的方式不定期地進行區塊鏈、虛擬貨幣有關的研究分享，若你對於此方面有任何想要分享卻苦於沒有舞臺的話，也非常歡迎你加入 Metaverse Pets 社群後與我報名「區塊吧台」的分享會。</p><p>以下是過往區塊吧台的分享會紀錄影片:</p><div data-type="youtube" videoId="CmFh6ymFiZY">
      <div class="youtube-player" data-id="CmFh6ymFiZY" style="background-image: url('https://i.ytimg.com/vi/CmFh6ymFiZY/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=CmFh6ymFiZY">
          <img src="{{DOMAIN}}/editor/youtube/play.png" class="play"/>
        </a>
      </div></div><p>若你從我的文章或影片中獲得一些幫助、希望支持我繼續創作或記錄研究結果的話，歡迎購買 Metaverse Pets 的 NFT 當作支持，謝謝！</p><p>你可以在 OpenSea 的網址後方直接帶入項目的 ENS 名稱找到我: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://opensea.io/metaversepets-nft.com">opensea.io/metaversepets-nft.com</a></p>]]></content:encoded>
            <author>carawang-2@newsletter.paragraph.com (Cara Wang)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/74407b6fc3b445cda848bab92a0da9dae45a7faf9ec60f3bd6559a5022d6f114.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[將既有 DNS 網址匯入到 ENS (Ethereum Name Service) 中並完成綁定]]></title>
            <link>https://paragraph.com/@carawang-2/dns-ens-ethereum-name-service</link>
            <guid>HnmNYItZlIH0MYTbAZMv</guid>
            <pubDate>Mon, 11 Apr 2022 16:06:08 GMT</pubDate>
            <description><![CDATA[目前 ENS 提供的綁定方式有以下兩種：購買想要的 ENS 名稱、綁定到地址上使用既有的 DNS 綁定持有的網址到地址上前一篇文章中，我們介紹了 ENS 是什麼以及第一種綁定自己的 Ethereum 地址到你所購買、註冊的 ENS 上的步驟說明。而目前你正在閱讀的這篇文章中，將紀錄如何使用既有的 DNS 服務做驗證來綁定持有網址到自己的地址上的步驟。 若你還沒有看過前一篇文章的話，建議可以先做閱讀、了解 ENS 是什麼: https://mirror.xyz/carawang.eth/_zLa85tHYaps-ivRUE1DspMYWuNf86RDGrKvRJxHvJE何謂在 ENS 中綁定既有 DNS 到地址上？在前一篇文章中，我們說明了大部分使用 ENS 做綁定的方式，是直接購買一組你想要的 ENS 名稱後，綁定到自己的地址上，而這些名稱，全部都會是以 .eth 做為結尾的名稱。例如在前一篇文章中，我購買了 carawang.eth 這個名稱並完成綁定後，就可以在其他有支援 ENS 的服務上，看到我的以太坊地只會出現 carawang.eth 的對應關係。 以下以 OpenS...]]></description>
            <content:encoded><![CDATA[<p>目前 ENS 提供的綁定方式有以下兩種：</p><ol><li><p>購買想要的 ENS 名稱、綁定到地址上</p></li><li><p>使用既有的 DNS 綁定持有的網址到地址上</p></li></ol><p>前一篇文章中，我們介紹了 ENS 是什麼以及第一種綁定自己的 Ethereum 地址到你所購買、註冊的 ENS 上的步驟說明。而目前你正在閱讀的這篇文章中，將紀錄如何使用既有的 DNS 服務做驗證來綁定持有網址到自己的地址上的步驟。</p><p>若你還沒有看過前一篇文章的話，建議可以先做閱讀、了解 ENS 是什麼:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/carawang.eth/_zLa85tHYaps-ivRUE1DspMYWuNf86RDGrKvRJxHvJE">https://mirror.xyz/carawang.eth/_zLa85tHYaps-ivRUE1DspMYWuNf86RDGrKvRJxHvJE</a></p><h2 id="h-ens-dns" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">何謂在 ENS 中綁定既有 DNS 到地址上？</h2><p>在前一篇文章中，我們說明了大部分使用 ENS 做綁定的方式，是直接購買一組你想要的 ENS 名稱後，綁定到自己的地址上，而這些名稱，全部都會是以 <code>.eth</code> 做為結尾的名稱。例如在前一篇文章中，我購買了 <code>carawang.eth</code> 這個名稱並完成綁定後，就可以在其他有支援 ENS 的服務上，看到我的以太坊地只會出現 <code>carawang.eth</code> 的對應關係。</p><p>以下以 OpenSea 網站為例，你可以看到，在我的以太坊地址 <code>0x71A1…86EE</code> 前方，出現了 <code>carawang.eth</code> 的 ENS 名稱:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e4a7bd9086c0b3731866517037b2a564650a910c36df546d0d20b4b1bf9beb86.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>而 ENS 也提供讓你可以綁定你持有的網址到以太坊地址上的方法，像筆者目前持有 <code>metaversepets-nft.com</code> 這個網址，則我可以利用 ENS 要求的驗證步驟一步步完成後，在 ENS 中紀錄我的地址對應的 ENS 即為我的網址。如下圖所示，你可以看到在 <code>0x1E60…4eE9</code>這組地址前方的 ENS 名稱便是我持有的網址 <code>metaversepets-nft.com</code>：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3e102c92d4c54805ae6d61cdff4fa8dfe40d71dc69098a15ebc28c22fdf12ecc.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-dns-ens" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">利用 DNS 綁定網址到 ENS 上的好處是什麼？</h2><p>筆者認為，將地址綁定到 ENS 上最大的好處，其實是多一層安全認證。以 NFT 在 OpenSea 這樣的二級市場上的認證方式為例，OpenSea 一般要給一個 Collection 或地址藍勾勾的認證，還是需要這個藝術家或項目有一定的銷售量或知名度才會給予，但筆者在 Instagram 或是其他社群網站中，其實也會發現有小型的藝術家或專案會被有心人進行偽造 (為了方便描述，我們姑且稱這個正版專案叫做 A)，而這些藝術家或項目方他們確實是首要發起者，但可能因為種種原因導致他們無法獲得 OpenSea 的藍勾勾。此時，當有有心人想要偽造 A 來販售作品時，通常就得靠聰明的使用者去識破後，通知 A，讓 A 在社群媒體中發佈訊息說明哪些發售者是盜版行為、提醒粉絲或消費者們多加注意。而前述所說的聰明使用者通常是怎麼識破的呢？無非就是看銷售量或是發售者的地址來多加判斷。</p><p>而作為藝術家或項目方，假如我們的 NFT 專案本身有架設網站的話，也許就可以選擇利用 DNS 綁定網址到 ENS 上的作法，去對自己的發佈作品的地址做認證，並宣導要用戶在購買前多一層檢查發佈者的 ENS 名稱的動作，減低使用者成為聰明消費者的門檻 (從要辨認一組難以記憶的以太坊地址到簡單檢查大家都熟悉的網址即可)，來做到增加自己與消費者的保護力。</p><p>而另外的好處，就如同前一篇文章所提的，綁定後的地址在其他有支援的 Web3.0 網站、服務中，可以顯示你的 ENS (當前文章所述的，便會是顯示你持有的網址)，以品牌經營的角度來說，也許就會更容易地讓大眾去產生記憶。</p><h2 id="h-dns-ens" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">如何透過 DNS 進行 ENS 註冊？</h2><p>DNS 的運作方式，是藉由新增 DNS Record 去做導向。例如，今天我的網站實際上架設於 <code>127.0.0.1</code> 這樣的 IP 位子上，因此我的 DNS 中就會新增一筆 DNS Record 去說明 <code>metaversepets-nft.com</code> 這個網址，要被指向 <code>127.0.0.1</code> 的 IP 地址。而我們也可以透過設定子網域 (subdomain) 的方式，去擴充網址可以指向的位址或回傳值，例如 ENS 所設計的驗證方式，就會是請驗證者在自己的 DNS 上，設定一組 DNS Record，子網域統一叫做 <code>_ens</code> ，而回傳值就會是你當下想要綁定的以太坊地址。</p><p>(圖)</p><p>然而，過去的域名系統 (DNS = Domain Name System) 的安全性並不高，並沒有嚴謹的加密與驗證技術在背後做支撐，因此，是有可能藉由中間人從中篡改回傳值，而導致正在照訪該網站的用戶，在完全渾然不覺的情況下，進入到了惡意網站或取得了錯誤的內容。</p><p>而要如何補足這一塊的安全缺漏呢？方法是透過啟用 DNSSEC (Domain Name Service Security Extensions) 在 DNS 上的安全性擴展，讓 DNS 的服務在取得 DNS Record (通常便是你的網址要導到哪個 IP 地址的資訊) 時可以驗證來源的正確性，以確保中間內容並未遭竄改。因此，有了 DNSSEC 後，ENS 便有辦法藉由請擁有者新增一筆 DNS Record 的方式，驗證此設定的來源正確性與內容來做到將 DNS 網址作為 ENS 名稱來綁定到特定地址上的需求。</p><p>更多關於 DNSSEC 的介紹可以參考這篇文章:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.ithome.com.tw/tech/92685">https://www.ithome.com.tw/tech/92685</a></p><h2 id="h-dns" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">綁定 DNS 到以太坊地址上的步驟</h2><p>首先，當你完成網站的功能開發後，會需要先在網址註冊商中購買、註冊一個你喜歡的網址，接著，你會需要有 DNS (Domain Name System) 託管服務來設定、將網址導向你的網站實際所在的 IP 位址。</p><p>因此，在做 ENS 驗證時，會涉及到購買網址的域名註冊服務，以及 DNS 託管服務兩個角色。不過大部分的註冊網址服務中，也會涵蓋到 DNS 託管的服務，筆者則是在 GoDaddy 註冊、購買得到 <code>metaversepets-nft.com</code> 的網址後，另外申請 <code>CloudFlare</code> 的 DNS 服務做託管。因此，以下的 ENS 流程，將以此兩個服務的結合做截圖範例。若你選擇的是其他的服務，還是可以搭配下方步驟再與你選擇的服務後台設定做比較，預期也可以順利完成 ENS 註冊。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">註冊、購買網址</h3><p>筆者是透過 GoDaddy 購買網址的，其他常見的網址註冊商還有 Google Domain，台灣在地服務則可以透過中華電信的域名註冊進行購買。</p><ul><li><p>GoDaddy:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://tw.godaddy.com/">https://tw.godaddy.com/</a></p></li><li><p>Google Domain:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://domains.google/">https://domains.google/</a></p></li><li><p>中華電信:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://domain.hinet.net/#/">https://domain.hinet.net/#/</a></p></li></ul><h3 id="h-dns" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">DNS 託管服務</h3><p>GoDaddy 本身也有提供 DNS 的代管服務，不過筆者的 DNS 代管服務是選擇使用 CloudFlare:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.cloudflare.com/zh-tw/">https://www.cloudflare.com/zh-tw/</a></p><p>因此，以下在介紹 ENS 註冊既有 DNS 到地址的步驟中，筆者截圖的畫面會是以 GoDaddy 配上 CloudFlare 的設定方式進行截圖、說明。</p><h2 id="h-ens-dns" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">在 ENS 中綁定既有 DNS 步驟說明</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">步驟一：搜尋你的網址</h3><p>首先，你一樣是進到 ENS 的網站中搜尋你想要綁定的網址。以筆者所擁有的網址為例，我就直接在搜尋框中搜尋 <code>metaversepets-nft.com</code> 即可：</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4ac221a97977e478dc4f838c634030993d3d41d678f1381b3ced9ef5c8ce96db.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>接著，你會看到這個網址尚未被註冊、納入 ENS 的管理範圍中，因此你便可以依照 ENS 的要求步驟來進行設定、證明你擁有該網址的所有權:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/53ac50ba7f8f80d7eab7e913246358027243545fdee65c1dad6bc394668a4c40.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>這裡的綁定流程不像一般購買 ENS 名稱的流程那樣，會在一開始就讓你知道所有步驟在做什麼，而是你得要一關一關渡過後，才能看到下一關要做的事情。</p><h3 id="h-dns-dnssec" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">步驟二：在 DNS 託管服務中開啟 DNSSEC 設定</h3><p>而就如同上圖所示，第一關需要達成的，是要進入你管理該網址的 DNS 中，開啟 DNSSEC 選項，此步驟是為了確保後續步驟中可以拿到 DNSKEY、RRSIG 進行來源正確性的驗證。</p><ul><li><p>DNSKEY: 是 DNS 的公鑰，可以拿來驗證訊息是否透過只有該 DNS 所擁有的私鑰進行簽章的資料正確性</p></li><li><p>RRSIG (Resource Record Signature): DNS 經由私鑰進行簽章後的的簽章內容</p></li></ul><p>而以筆者使用的 CloudFlare 託管服務為例，只需要點開你要設定的網址後，進入到「DNS」的管理頁面:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e7e466484a0f0f66a30619a9e1bc80d095fd909c25db5d8af732603ee7e4b81e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>進到管理畫面後，在右邊的設定畫面中，將捲軸往下拉，你就會看到「DNSSEC」的選項，旁邊會有「Enable DNSSEC」的藍色按鈕，點擊後，CloudFlare 就會幫你產生 DNSSEC 所需要的公私鑰並在一個 popup 的畫面中顯示給你看，此時你不需要特別把資料都先記下來，這些資料在 DNS 管理頁面中都看得到 (「DNSSEC」區塊右下方的「DS Record」選項，點開後，便可以看到相同內容)。</p><p>完成「Enable DNSSEC」動作後，你會看到 DNSSEC 那一塊的藍色按鈕變成了「Cancel Setup」，此時表示你已完成 DNSSEC 一半的設定:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cf5a0a41d388fa019031956dfe735bd998e6287cb897ab2bdacd3c792293cc9e.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>接著，你還需要將 CloudFlare 幫你產生的部分資訊放到原本的網址註冊商中，以筆者的網址為例，便是要回到 GoDaddy 的後台進行設定。在 GoDaddy 的後台中，找到「DNS Management」的頁面，往下拉找到「Advanced Features」區塊，區塊中就會看到「DNSSEC」選項。</p><p>由於筆者當時在 GoDaddy 後台找「DNS Management」入口找非常的久，假如有人也使用 GoDaddy 做設定的話，可以在登入 GoDaddy 後台後，直接使用以下網址進入「DNS Management」頁面:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://dcc.godaddy.com/manage/dns">https://dcc.godaddy.com/manage/dns</a></p><p>進到 GoDaddy 的「DNSSEC」頁面後，點選「ADD」按鈕，你就會看到以下畫面:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c50146041fe4fa81fa9161a86ebea5e8f7ad2cff3cf8058ad3e602bdbc156e21.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>接著，請從 CloudFlare 後台中，由 CloudFlare 幫你產生的 Key Tag、Digest 填入，Algorithm 預期都是填入 13，而 Digest Type 預期是填入 2。</p><p>假如你是在其他地方購買網址，但與筆者一樣使用 CloudFlare 做 DNS 託管的話，可以參考 CloudFlare 的這一頁，其中有一個「Provider-specific instructions」下拉清單，可以從中找到你的網址註冊商的 DNSSEC 設定說明頁:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://developers.cloudflare.com/dns/additional-options/dnssec/">https://developers.cloudflare.com/dns/additional-options/dnssec/</a></p><p>完成設定後，稍等約五分鐘的時間，接著在 ENS 的畫面就會顯示已偵測到該網址有開啟 DNSSEC 的選項:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/251429f5fab9b49b8d0c4b5a50680f21f4789abf7c86c899389c4bdc837e4451.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-dns-record" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">步驟三：新增指定的 DNS Record 表明你想綁定的地址</h3><p>到這邊，第一個步驟的「ENABLE DNSSEC」被打勾了，我們來到了第二個關卡，這個關卡是要你在你的 DNS 中，設定一個 type 是 TXT 的 DNS Record 並將此紀錄的子網域設定為 <code>_ens</code>，並將內容寫入為 <code>a=0x….</code> ，後方的 <code>0x…</code> 會是你當前連接在 ENS 上的地址。</p><p>藉由這個步驟，ENS 將可以透過 <code>_ens.metaversepets-nft.com</code> 來取得他要求你設定的內容，進而驗證你是這個 DNS 的擁有者。</p><p>此時，你需要回到 CloudFlare 的 DNS 管理頁面中，將捲軸拉到管理頁面的頂端，按下「Add Record」</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/acfc4e286b7d92d3a862d1549ebf812acb1c689a69cba1ca75cc7fca76de2052.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>接著，網頁畫面就會出現一塊讓你可以設定 DNS Record 的區域，你就可以依據 ENS 的要求進行設定:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/79da183d2d3a49e1ad61e0ff2724d33da2d8cf899d13b36c033339de4683ebbb.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>完成後，你在 ENS 的頁面會看到第二個勾勾也被勾起來了:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/37076da2fe674e3870ce875767e6c70e249f3962884b4d2e4c77ddd3ed328573.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">步驟四：將證明上鏈</h3><p>再來的步驟，便是實際與 ENS 的智能合約進行註冊 ENS 名稱的動作了。點選「Register」後，你的 MetaMask 會跳出視窗要你確認、發送交易，此動作將觸發 ENS 智能合約中的 <code>proveAndClaimWithResolver</code>，這個呼叫因為這個動作要發送的資料量較大、驗證較費資源，因此會需要耗費不少交易手續費，以筆者這次的範例為例，這個動作筆者花了 <code>0.27179</code> 左右的 ETH，大約是 824 鎂，換算台幣要兩萬多塊…</p><p>送出交易後，等待幾個交易確認數後，第三個勾勾也被勾起來了:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/65b1ab352d916c4c9cfb4bd67410894928e171594117ff9de08b4aaca2d07eb1.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ens" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">步驟五：將地址與 ENS 名稱綁定</h3><p>此步驟與一般的 ENS 綁定方式一樣，進到「My Account」後，在 Primary ENS Name 區塊的下拉選單中找到你的網址後，按下「Save」、送出交易即可。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6964f975e6df815744384d380a316031d7be74d7c9233a9329de66f979a26a26.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>眼尖的網友大概會發現，上方的截圖中，圖片下方的「Registrant」沒有出現你的網址名稱，這是因為對 ENS 來說，他並不是幫你註冊這個 DNS，而僅是透過既有的 DNS 再加上 DNSSEC 擴充協定來完成你原本就已經註冊過的網址驗證，因此這類型的 ENS 中，並不會有註冊者的選項，相比於上一篇文章的一般 ENS 名稱，可以看出以下的差異:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3b68b3b8039c06447b1e9a6bed5b86a35e9491bbf91ad10803551d9f9619ec1d.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>上圖中可以看出，利用 DNS 做驗證得到的 ENS 名稱中，沒有顯示註冊者 REGISTRANT，而下圖一般透過購買所得的 ENS 名稱則有 REGISTRANT 這一欄:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/06bfff9f252a6180879100f2f45280e7e0097a6098a9c827bb6f6dcead9aca2b.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h2 id="h-ens" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">與一般的 ENS 名稱比較</h2><p>既有網址的 ENS 類型，是透過產生 ERC721 的 NFT 來辨識 ENS 名稱的所有者，因此，當你想要把 ENS 轉給其他地址時，你實際上轉移的是這個 ENS 對應的 ERC721 NFT，但因為 DNS 網址的所有權仍是依據域名管理局所管理，因此，ENS 在這一塊的設計上，便不會幫這種 ENS 產生對應的 NFT、隨意讓你可以轉移此類型 ENS 的所有權。</p><p>若你想要改變 DNS 網址對應到的 ENS 名稱時，則會需要回到講解步驟時的步驟三，將 <code>_ens</code> 的子網域內容做更改，把新的網址放入內容中 ( <code>a=新網址</code> ) 以便讓 ENS 重新做驗證。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">結語</h2><p>比起一般註冊、購買得到的 <code>.eth</code> 結尾的 ENS 名稱，由於 DNS 的網址變化度較高，所以你可以幫你的以太坊地址綁定的 ENS 名稱增加更多不一樣的豐富度，不過就如同上方的範例所看到的，目前 ENS 的實作中，將驗證內容送到鏈上的步驟是需要消耗許多手續費的，對於已經在註冊 DNS 花費一筆錢的服務主來說，大概不會也不太需要為此再多付一次錢做 ENS 的註冊。</p><p>不過，就如同前面筆者所述，透過 ENS 的綁定，或許可以帶給消費者、投資者多一層安全性、提升產品的可驗證度，因此，對於 NFT 項目方、Web3.0 服務提供者，仍是可以考慮利用 DNS 驗證的方式，將既有的網址綁定到主要提供服務的以太坊地址上。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">寫在後面的介紹</h2><p>「區塊吧台」是 Metaverse Pets 社群中的子節目，會透過在 Discord 上直播的方式不定期地進行區塊鏈、虛擬貨幣有關的研究分享，若你對於此方面有任何想要分享卻苦於沒有舞臺的話，也非常歡迎你加入 Metaverse Pets 社群後與我報名「區塊吧台」的分享會。</p><p>以下是過往區塊吧台的分享會紀錄影片:</p><div data-type="youtube" videoId="CmFh6ymFiZY">
      <div class="youtube-player" data-id="CmFh6ymFiZY" style="background-image: url('https://i.ytimg.com/vi/CmFh6ymFiZY/hqdefault.jpg'); background-size: cover; background-position: center">
        <a href="https://www.youtube.com/watch?v=CmFh6ymFiZY">
          <img src="{{DOMAIN}}/editor/youtube/play.png" class="play"/>
        </a>
      </div></div><p>若你從我的文章或影片中獲得一些幫助、希望支持我繼續創作或記錄研究結果的話，歡迎購買 Metaverse Pets 的 NFT 當作支持，謝謝！</p><p>你可以在 OpenSea 的網址後方直接帶入項目的 ENS 名稱找到我: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://opensea.io/metaversepets-nft.com">opensea.io/metaversepets-nft.com</a></p>]]></content:encoded>
            <author>carawang-2@newsletter.paragraph.com (Cara Wang)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/b84cc405778b6010a36342be10a3d7bf9033d2e1055ab4962f9bfda1ef13a874.jpg" length="0" type="image/jpg"/>
        </item>
        <item>
            <title><![CDATA[Ethereum Name Service (ENS) 介紹與註冊教學]]></title>
            <link>https://paragraph.com/@carawang-2/ethereum-name-service-ens</link>
            <guid>ywHyUYIE1ShnvXG7GLgs</guid>
            <pubDate>Fri, 08 Apr 2022 15:07:25 GMT</pubDate>
            <description><![CDATA[這篇文章主要會介紹 ENS 是什麼、如何幫你的地址綁定到 ENS 上 目前 ENS 提供的綁定方式有以下兩種：購買想要的 ENS 名稱、綁定到地址上使用既有的 DNS 綁定持有的網址到地址上這篇文章僅先針對 ENS 介紹、第一種綁定方式做介紹，並提供實際綁定過程的步驟記錄。利用既有 DNS 去進行綁定的流程將於下一篇文章中說明。另外，我也將針對 ENS 的架構與智能合約做分析，預期也會是獨立的一篇文章。網路世界的域名系統 DNS在網路世界中，每個連接到網路上的接口其實是透過 IP 地址來進行通訊，IP 地址是一組由像是 127.0.0.1 的數字編號所組合而成，這樣的數字其實很難被人腦記憶或辨識。因此，DNS (Domain Name Service) 這樣的服務，便是幫大家把 IP 地址對應成人類熟悉的語言名稱，也就是大家每天都在使用的網址服務。 因為有了 DNS 這樣全球式的服務，讓我們在訪問網站時，不必記憶、輸入那些難以判別的 IP 地址，而是可以輸入像是 google.com 這樣簡單、好記得網址。 有關 DNS 的說明，可以參考下方文章的介紹: https://aws....]]></description>
            <content:encoded><![CDATA[<p>這篇文章主要會介紹 ENS 是什麼、如何幫你的地址綁定到 ENS 上</p><p>目前 ENS 提供的綁定方式有以下兩種：</p><ol><li><p>購買想要的 ENS 名稱、綁定到地址上</p></li><li><p>使用既有的 DNS 綁定持有的網址到地址上</p></li></ol><p>這篇文章僅先針對 ENS 介紹、第一種綁定方式做介紹，並提供實際綁定過程的步驟記錄。利用既有 DNS 去進行綁定的流程將於下一篇文章中說明。另外，我也將針對 ENS 的架構與智能合約做分析，預期也會是獨立的一篇文章。</p><h2 id="h-dns" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">網路世界的域名系統 DNS</h2><p>在網路世界中，每個連接到網路上的接口其實是透過 IP 地址來進行通訊，IP 地址是一組由像是 <code>127.0.0.1</code> 的數字編號所組合而成，這樣的數字其實很難被人腦記憶或辨識。因此，DNS (Domain Name Service) 這樣的服務，便是幫大家把 IP 地址對應成人類熟悉的語言名稱，也就是大家每天都在使用的網址服務。</p><p>因為有了 DNS 這樣全球式的服務，讓我們在訪問網站時，不必記憶、輸入那些難以判別的 IP 地址，而是可以輸入像是 google.com 這樣簡單、好記得網址。</p><p>有關 DNS 的說明，可以參考下方文章的介紹:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://aws.amazon.com/tw/route53/what-is-dns/">https://aws.amazon.com/tw/route53/what-is-dns/</a></p><h2 id="h-ens" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">以太坊區塊鏈的 ENS</h2><p>以太坊區塊鏈中，同樣有一組讓人難以記憶的地址，也就是大家的錢包地址，像是 <code>0x71A15b42Cb572143d4Fd1e0B92B75540599186EE</code> 這樣的一組地址，若你能夠稍微記住開頭與結尾就已經很不錯了，但當你要轉帳時，通常就只能靠著複製、貼上或者是掃 QR Code 的方式來避免填錯。若要你檢查整串地址是否有填錯，其實也只能稍微檢查開頭跟結尾是否一致，很難一個字一個字的去做比對。因此，也就出現了 ENS (Ethereum Name Service) 這樣的服務來幫助大家利用人類比較能夠記憶的語言來對應到難記得錢包地址。</p><p>但其實，目前一般的錢包服務，像是 MetaMask、中心化交易所的錢包，都會讓大家可以維護自己的地址聯絡簿，讓大家可以自行紀錄、對照每個地址是指向你的哪位親朋好友，或是你可以維護自己的多個地址、記錄每個錢包各自的用途為何。若你有經常使用的錢包服務的話，會遇到需要重新輸入地址的機會是少之又少，此時 ENS 的好處，似乎就沒有這麼的明顯了。也因此，筆者認為，其實在一般的使用情形下，或者你是單純的投資者、使用者的話，是可以不必購買 ENS 服務的。那麼，到底誰會需要購買 ENS 的服務呢？</p><h2 id="h-ens" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">ENS 的用途</h2><p>筆者認為，目前的 ENS 在應用上，仍是類似 DNS 的服務所帶給服務或內容提供者的便利性，讓大眾可以輕鬆的記憶、訪問相關服務或創作者的內容使用 (這類服務通常會是 Web3.0 的服務)。</p><p>像是筆者的 mirror 網址，若沒有註冊 ENS 服務的話，就會是 <code>mirror.xyz/0x71A15b42Cb572143d4Fd1e0B92B75540599186EE</code> 這樣的一串網址，但因為我有購買、將我使用的地址綁定到 <code>carawang.eth</code> 這個 ENS 上，才讓我在 mirror 上的主頁可以變成一個相對好記得、讓使用者容易產生記憶點的網址: <code>mirror.xyz/carawang.eth</code>。</p><p>另外，ENS 的其他用途，筆者認為，若能透過 ENS 作為募款地址或群眾募資地址，對於送錢方與收款方來說都會非常方便。對於收款方若有需求進行地址的更換時，可以簡單將 ENS 發送到新的地址上做綁定即可，此時對於送錢方也就不必擔心打錢進了舊的或是錯誤的地址而捶心肝。</p><p>但是，特別是作為收款地址的使用上的話，因為 ENS 與 DNS 一樣都會有一個效期存在，因此作為收款方，需要特別小心此一效期時間、即時地做效期延展，否則可能會造成送錢方將辛苦錢打進了錯誤的地址而無法獲得原本希望得到的服務或是捐款意圖了。當然，若是單純用來作為服務、內容提供者的方便識別使用的話，也需要注意此一效期，否則時間到了，代表你原本苦心經營的品牌的 ENS 名稱被別人買走，那豈不是太令人沮喪了嗎！?</p><p>不過 ENS 的效期到期後，會有 90 天的寬限期，你只要在寬限期內進行延展，仍可保留此 ENS 名稱的擁有權。</p><h2 id="h-ens" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">如何購買想要的 ENS 名稱並綁定到地址上?</h2><p>目前有兩種方式可以去綁定 ENS:</p><ol><li><p>直接購買想要的 ENS 名稱、綁定到地址上</p></li><li><p>使用既有的 DNS 綁定持有的網址到地址上</p></li></ol><p>以下介紹第一種綁定方式，第二種綁定方式將於下一篇文章中再做介紹。</p><h3 id="h-ens" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">步驟一：找到你想要的 ENS 名稱</h3><p>首先，你需要先透過以下網址進到 ENS 的服務中:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://app.ens.domains/">https://app.ens.domains/</a></p><p>接著，可以直接搜尋你想要購買的 ENS 名稱:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ecfaf51340efb263fc21b98e13bf31ca5e256541035ebb0f2b9fc968569e0442.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>因為 ENS 的名稱都會是以 <code>.eth</code> 結尾，所以在搜尋時，無論你有沒有帶入 <code>.eth</code> ，搜尋到的都會直接幫你帶入 <code>.eth</code>，以上方截圖為例，我搜尋到的結果就會是 <code>carawang.eth</code> 這樣的結果。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">步驟二：選擇購買、註冊年限</h3><p>接著，若你想要註冊的 ENS 仍未被人買走的話，你便可以點選你想購買的搜尋結果做展開，展開的畫面如下:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d92a02a4f8b66e8933c0dd886852b646e21781799fb840b81148c599380b2700.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>當你展開某個搜尋結果時，預設的效期會是 1 年，你可以自行決定要購買多久效期的 ENS 服務。以上方截圖範例來說，我選擇的是購買 3 年效期的 ENS 服務。</p><p>ENS 的價格是由字數來做定價:</p><ol><li><p>五個字以上: 年費 5 美元</p></li><li><p>四個字: 年費 160 美元</p></li><li><p>三個字: 年費 640 美元</p></li></ol><h3 id="h-ens" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">步驟三：遞交 ENS 名稱註冊申請書</h3><p>當設定好效期後，你就可以點選下方的「Request To Register」按鈕。</p><p>不過，這邊筆者遇到一個小狀況，是我在點選「Request To Register」時，預期會自動跳出 MetaMask 做錢包做交易確認或錢包連結的動作，但我怎麼按都沒反應。此時我注意到左方的錢包連結框的內容，沒有正確顯示我的錢包地址。若你也發生按按鈕沒有反應的情況的話，可以檢查一下網頁左方是否有顯示你的錢包地址，若有如下方截圖的左側一樣出現你的錢包地址的話，才表示你已經連結成功，若此時你看到左方沒有顯示你的錢包地址的話，你可以點選橘色「Connect」按鈕先進行錢包連結，接著再去點選「Request To Register」預期就會有反應囉！</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/48c683231923ccafb6ab384b6e57f89bae1ef6e98e7296800b47c033c8586102.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>點選「Request To Register」後，MetaMask 會彈出來、要你進行交易確認，此交易是執行 ENS 智能合約中的 <code>commit</code> 呼叫，此動作完成後，會有為期 7 天的考慮期，若你沒有在這 7 天內完成後續購買動作的話，過了第 7 天，你想購買的這個 ENS 名稱就會再次開放給所有人註冊、購買，你也需要重新遞交申請書，才能再次進入到後續的註冊環節。</p><p>當你在 MetaMask 點選「Confirm」、送出交易後，你會看到下方的綠色進度條開始往前跑。依據此步驟的說明，ENS 是要確保沒有同時間購買、註冊此 ENS 的情況發生。</p><p>這個步驟我所花費的 Gas 是 <code>0.003648156386851561</code>ETH，以當下價格換算美金價值約 11 鎂。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c05fb663fac951d93e2e7f1298d1549d7c3732322bb637947a3c1afd721907ed.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ens" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">步驟四：註冊 ENS 名稱</h3><p>經過一分鐘後，進度條會來到第三部驟、下方的「Register」按鈕可以點選了。</p><p>點選「Register」後，MetaMask 會再彈出要你進行交易確認的動作，此動作將觸發 ENS 智能合約中的 <code>registerWithConfig</code> 呼叫，即針對此 ENS 名稱進行付款、購買的動作，因此在 MetaMask 中，你可以看到 ENS 將跟你收取購買費用，也就是一開始在設定時間時，畫面上會幫你估算的購買費用。</p><p>此步驟我實際上花費了 <code>0.005027041231729858</code> ETH 的購買費用，交易手續費則付出了 <code>0.02191469984991315</code> ETH，換算成美元的話，總共花費了 88 鎂左右。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/be190c56ec379c79c5b6fcf06269ecac690dfe9bb5a69c4d65e7a8b4471560e1.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>此步驟點選「Confirm」後，原本畫面中的「Registry」按鈕又將變成「Tx Pending」，表示交易已經上鏈、正在等待交易確認數。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5accd96b927345eb54873c0d2ccda8789ed8dd6aaa6a81c2593e96f0f558b930.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>當 TX Pending 結束後，你的註冊畫面會出現「Set As Primary ENS Name」按鈕，此時，你的地址尚未與此 ENS 做綁定，若你想要即刻幫你的購買地址綁定到此 ENS 上的話，可以在此時點選「Set As Primary ENS Name」按鈕。點選後，會帶你進到「My Account」頁面。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f5ebf215e6b772beba5f8f6ac343345af04792563541c1ad6ca8fb36aee3216d.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><h3 id="h-ens" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">步驟五：為這個地址設定主要的 ENS 名稱</h3><p>進入「My Account」畫面後，你可以在紅色框框處的下拉選單進行選擇，選定後，點選「Save」按鈕即可進行綁定。</p><p>我在這個步驟花費的交易手續費是 <code>0.0136551531553498</code> ETH，約 44 鎂。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/f6b4f9d2d036607015917b13e33a6456dcd752bc76519c350f37d2465a9c5239.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>完成上述步驟後，你已經成功地將地址與你所購買的 ENS 進行綁定了。</p><p>整個流程包含交易手續費的花費，我總共花了 143 (11 + 88 +44) 美元，換算台幣約四千多塊。</p><h2 id="h-ens" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">管理我的 ENS</h2><p>在「My Account」的管理頁面中，你可以點選下方的 ENS 名稱，以上圖為例，即點選「carawang.eth」的地方，進入設定頁面，在設定頁面中，你可以針對此 ENS 進行你的個人網站網址、email 等資訊。你可以點選「ADD/EDIT RECORD」進行編輯。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/160c0e71d8edcc31a3a833ec4eb389780811cd2bca4a3fb57496122909662875.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>編輯完畢後，點選「Save」即會跳出以下視窗詢問你是否確定要進行修改:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/753ba83943418ca5753ae3eb63b58a834d7f0bcfe34d46033c87cf75ae4e79ed.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>點選「Confirm」後，MetaMask 會跳出視窗要你進行交易確認，此時會觸發的是智能合約中的 <code>setText</code> 呼叫。</p><p>此步驟因為筆者想等之後一次進行多個欄位的修改後再一次送出，因此這次就沒有實際觸發智能合約呼叫、上鏈做修改，也就沒有花費交易手續費。</p><p>待綁定後，若你在進行同一個 ENS 的名稱搜尋，你會看到該名稱被標註為「Unavailable」:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c6ad6990957cf8e93d988afcc76be6f1749608300d0b8d9485c8214b7d044af4.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>點選名稱後，可以看到此 ENS 的購買者相關資訊，此資訊及為你剛剛在「My Account」中可以進行的設定內容:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ea5e77c537e8e80d904ae7dac5fa765f8bf9ec1333f569fc3b82ea33cc71b802.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>此處有趣的地方是，即使你不是這個 ENS 的擁有者，你也可以替這個 ENS 的有效期進行延展，此動作不會改變此 ENS 的所有者，僅是針對原本的所有者的效期做延展而已。對於服務提供者來說，若他們的服務是一個團體所提供的，那就可以藉由團體中的任何一人去執行延展動即可，筆者認為算是一個方便的功能。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">綁定後可以在哪邊看得到？</h2><p>首先，你可以在 etherscan 上看到你的地址變成了你所綁定的 ENS 名稱:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/937a00bc7999e5c29e66cbf729592848b21850088f5725d1b25705af5f3e5a87.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>除了 etherscan 外，你也可以在有串接 ENS 服務的網站中，看到你的地址已經更新成 ENS 名稱，下圖以 OpenSea 為例:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e4a7bd9086c0b3731866517037b2a564650a910c36df546d0d20b4b1bf9beb86.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>其他有支援 ENS 的服務可以從下方網址中看到:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ens.domains/#home-ecosystem">https://ens.domains/#home-ecosystem</a></p><p>此 ENS 在區塊鏈上實際會是以 ERC721 的 NFT 形式所存在，當你在付款動作完成後，你的地址實際上會收到一個 ENS 的 NFT:</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c29bb4ecc796e1313cf19f355f6d00dd9122aa1f426d06ea320b6f91b77535eb.png" alt="" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="hide-figcaption"></figcaption></figure><p>此 NFT 可以在如 OpenSea、LooksRare、x2y2 等等的二級市場中進行交易，若你在第一個步驟裡，從 ENS 的搜尋結果中發現你想要的 ENS 名稱已經被別人註冊走了的話，不妨到二級市場中出價看看，看對方是否願意接受你的出價、讓出 ENS 給你使用。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">結語</h2><p>ENS 的目標是希望讓以太坊的使用者容易識別各自的地址所使用，然而，目前的虛擬貨幣錢包服務，也都會讓使用者自行輸入、設定每個地址的聯絡者名稱以方便進行發送地址的區分用途。因此，對於虛擬貨幣的發送方來說，只要注意第一次設定地址時，有沒有輸入錯誤的地址，後續轉帳就都可以依靠自己設定的聯絡者名稱來做區分，在此使用情境下，ENS 的功用也就不大顯著，因此，筆者認為 ENS 的用途仍是類似 DNS 用在網站的識別使用，此使用率預期也將隨著 Web3.0 逐漸被大眾接受後，漸漸地增加各大網站針對 ENS 的技術整合，此時將帶動、增加一般內容或服務提供者對於 ENS 的使用率上升。</p><p>若你本身是內容提供者，或是你正在或打算實作 Web3.0 的服務的話，不妨利用此文章中的步驟說明進行購買、設定，並與個人或服務的識別進行連接、讓使用者或閱讀者可以容易記憶你的品牌識別名稱。</p><p>不過說實在的，當初買 DNS 時也才花了一千台幣不到，這次買 ENS 配上不低的 ETH 價格 (撰文當下 ETH/USDT 價格為 $3260)，換算成本要四千多台幣實在是貴鬆鬆。</p><p>不過更貴的還在後頭呢！後續的文章會再針對 ENS 的第二種綁定方式進行記錄，另外，因為筆者想透過研究 ENS 智能合約中的呼叫與架構實作上進行學習，預計也會把過程以文章的方式記錄下來。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">寫在後面的介紹</h2><p>「區塊吧台」是 Metaverse Pets 社群中的子節目，會透過在 Discord 上直播的方式不定期地進行區塊鏈、虛擬貨幣有關的研究分享，若你對於此方面有任何想要分享卻苦於沒有舞臺的話，也非常歡迎你加入 Metaverse Pets 社群後與我報名「區塊吧台」的分享會。</p><p>以下是過往區塊吧台的分享會紀錄影片:</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://youtube.com/playlist?list=PL56QIjxhXjEb-joZwpE7vdDEVVxc3o6WR">https://youtube.com/playlist?list=PL56QIjxhXjEb-joZwpE7vdDEVVxc3o6WR</a></p><p>若你從我的文章或影片中獲得一些幫助、希望支持我繼續創作或記錄研究結果的話，歡迎購買 Metaverse Pets 的 NFT 當作支持，謝謝！</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://opensea.io/collection/metaverse-pets">https://opensea.io/collection/metaverse-pets</a></p>]]></content:encoded>
            <author>carawang-2@newsletter.paragraph.com (Cara Wang)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/686268fac3b8e00d944c5589f4f158941d88fea907fba31efd8dc6ca4829082b.jpg" length="0" type="image/jpg"/>
        </item>
    </channel>
</rss>