<?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>Blue</title>
        <link>https://paragraph.com/@blue-9</link>
        <description>web3 developer</description>
        <lastBuildDate>Wed, 27 May 2026 17:02:24 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>Blue</title>
            <url>https://storage.googleapis.com/papyrus_images/2d2ebb88cbd4911889723e5b2031201519ff582ae2a9e9ef96f00619dcc6e444.jpg</url>
            <link>https://paragraph.com/@blue-9</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[Web3 应用调用 IPFS 服务实现文件去中心化存储]]></title>
            <link>https://paragraph.com/@blue-9/web3-ipfs</link>
            <guid>mLnHB6w3hk1RoCcXHk3h</guid>
            <pubDate>Wed, 27 Dec 2023 06:37:32 GMT</pubDate>
            <description><![CDATA[在Web3应用中，有一个很重要的功能就是存储文件，在传统的Web2应用开发中，我们一般会把文件存储在服务器或OSS中，在Web3我们一般把文件存储在IPFS服务中，IPFS服务可以实现文件的去中心化存储，这篇文章主要讲讲Web3应用中如何使用IPFS服务来存储文件。一、关于 IPFSIPFS是一种点对点的超媒体协议，即一个实现文件去中心化存储的协议，通过IPFS可以将文件分解为很多区块并存储在多个提供存储服务的服务器上，和我们Web3去中心化的概念一样，所以我们在Web3应用中文件存储一般也是使用IPFS。 提供IPFS的服务商很多：Pinata: Pinata是一个去中心化的文件存储平台，为开发人员和企业提供了稳定、安全、高效的文件存储和分发服务。nft.storage: nft.storage是一个专门针对NFT存储的去中心化平台，为开发人员和企业提供了安全、高效的NFT存储服务。web3.storage: Web3.storage是用于与IPFS网络和Filecoin区块链互动的网关的另一次迭代。这次我们选择Pinata来实现IPFS上传的操作，选择其他服务商，如Web3....]]></description>
            <content:encoded><![CDATA[<blockquote><p>在<code>Web3</code>应用中，有一个很重要的功能就是存储文件，在传统的<code>Web2</code>应用开发中，我们一般会把文件存储在<code>服务器</code>或<code>OSS</code>中，在<code>Web3</code>我们一般把文件存储在<code>IPFS</code>服务中，<code>IPFS</code>服务可以实现文件的去中心化存储，这篇文章主要讲讲<code>Web3</code>应用中如何使用<code>IPFS</code>服务来存储文件。</p></blockquote><h3 id="h-ipfs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">一、关于 IPFS</h3><p><code>IPFS</code>是一种点对点的超媒体协议，即一个实现文件去中心化存储的协议，通过<code>IPFS</code>可以将文件分解为很多区块并存储在多个提供存储服务的服务器上，和我们<code>Web3</code>去中心化的概念一样，所以我们在<code>Web3</code>应用中文件存储一般也是使用<code>IPFS</code>。</p><p>提供<code>IPFS</code>的服务商很多：</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.pinata.cloud/">Pinata</a>: <code>Pinata</code>是一个去中心化的文件存储平台，为开发人员和企业提供了稳定、安全、高效的文件存储和分发服务。</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://nft.storage/">nft.storage</a>: <code>nft.storage</code>是一个专门针对<code>NFT</code>存储的去中心化平台，为开发人员和企业提供了安全、高效的NFT存储服务。</p></li><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://web3.storage/">web3.storage</a>: <code>Web3.storage</code>是用于与<code>IPFS</code>网络和<code>Filecoin</code>区块链互动的网关的另一次迭代。</p></li></ul><p>这次我们选择<code>Pinata</code>来实现<code>IPFS</code>上传的操作，选择其他服务商，如<code>Web3.storage</code>操作也是类似。</p><h3 id="h-pinata-gatewayapi-key" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">二、<code>Pinata</code> 服务申请<code>Gateway</code>及<code>API Key</code></h3><p>首先需要去<code>Pinata</code>官网申请一下<code>Gateway</code>和<code>API Key</code>。<code>Gateway</code>是用于最后我们展示图片路径前缀，<code>API Key</code>用于在<code>Web3</code>应用前端中调用<code>Pinata</code>的文件上传服务。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/995ee995906d027c8dd9a244c53c2834800880c685fd9fc77c5ec3be997cc9c8.webp" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/1b1c0ea9ad377d25c51d57420d068401ab0ead6b3d2f69e2ed04242294535725.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">三、编写链端服务</h3><p>这里我写了一个简单的链端服务，来<code>新增</code>和<code>查询</code>任务。</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract List {
    struct Task {
        uint id;
        string name;
        string ipfsHash;
        address owner;
    }

    Task[] private list;

    function createTask(string calldata name, string calldata ipfsHash) public {
        uint taskId = list.length;
        list.push(Task(
            taskId,
            name,
            ipfsHash,
            msg.sender
        ));
    }

    function getTask() public view returns (Task[] memory) {
        Task[] memory temporary = new TaskUnsupported embed;
        uint counter = 0;
        for (uint i = 0; i &lt; list.length; i++) {
            if (list[i].owner == msg.sender) {
                temporary[counter] = list[i];
                counter++;
            }
        }
        Task[] memory result = new TaskUnsupported embed;
        for (uint i = 0; i &lt; counter; i++) {
            result[i] = temporary[i];
        }
        return result;
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.19;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">List</span> </span>{
    <span class="hljs-keyword">struct</span> <span class="hljs-title">Task</span> {
        <span class="hljs-keyword">uint</span> id;
        <span class="hljs-keyword">string</span> name;
        <span class="hljs-keyword">string</span> ipfsHash;
        <span class="hljs-keyword">address</span> owner;
    }

    Task[] <span class="hljs-keyword">private</span> list;

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">createTask</span>(<span class="hljs-params"><span class="hljs-keyword">string</span> <span class="hljs-keyword">calldata</span> name, <span class="hljs-keyword">string</span> <span class="hljs-keyword">calldata</span> ipfsHash</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-keyword">uint</span> taskId <span class="hljs-operator">=</span> list.<span class="hljs-built_in">length</span>;
        list.<span class="hljs-built_in">push</span>(Task(
            taskId,
            name,
            ipfsHash,
            <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>
        ));
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getTask</span>(<span class="hljs-params"></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">Task[] <span class="hljs-keyword">memory</span></span>) </span>{
        Task[] <span class="hljs-keyword">memory</span> temporary <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> TaskUnsupported embed;
        <span class="hljs-keyword">uint</span> counter <span class="hljs-operator">=</span> <span class="hljs-number">0</span>;
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> list.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
            <span class="hljs-keyword">if</span> (list[i].owner <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>) {
                temporary[counter] <span class="hljs-operator">=</span> list[i];
                counter<span class="hljs-operator">+</span><span class="hljs-operator">+</span>;
            }
        }
        Task[] <span class="hljs-keyword">memory</span> result <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> TaskUnsupported embed;
        <span class="hljs-keyword">for</span> (<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> <span class="hljs-number">0</span>; i <span class="hljs-operator">&#x3C;</span> counter; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
            result[i] <span class="hljs-operator">=</span> temporary[i];
        }
        <span class="hljs-keyword">return</span> result;
    }
}
</code></pre><p>其中的<code>ipfsHash</code>字段就是用于前端上传文件到<code>IPFS</code>后，<code>Pinata</code>返回的<code>Hash</code>字段，最终要展示图片也是用这个<code>Hash</code>值和之前我们申请的<code>Gateway</code>拼接展示。</p><h3 id="h-ipfs" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">四、前端调用<code>IPFS</code>服务上传图片</h3><pre data-type="codeBlock" text="const axios = require(&apos;axios&apos;)
const FormData = require(&apos;form-data&apos;)
const fs = require(&apos;fs&apos;)
const JWT = &apos;PINATA_IPFS_API_KEY&apos;

const pinFileToIPFS = async () =&gt; {
    const formData = new FormData();
    const src = &quot;2.jpg&quot;;

    const file = fs.createReadStream(src)
    formData.append(&apos;file&apos;, file)

    const pinataMetadata = JSON.stringify({
        name: &apos;testjpg&apos;,
    });
    formData.append(&apos;pinataMetadata&apos;, pinataMetadata);

    const pinataOptions = JSON.stringify({
        cidVersion: 0,
    })
    formData.append(&apos;pinataOptions&apos;, pinataOptions);

    try{
        const res = await axios.post(&quot;https://api.pinata.cloud/pinning/pinFileToIPFS&quot;, formData, {
            maxBodyLength: &quot;Infinity&quot;,
            headers: {
                &apos;Content-Type&apos;: `multipart/form-data; boundary=${formData._boundary}`,
                &apos;Authorization&apos;: `Bearer ${JWT}`
            }
        });
        console.log(res.data);
    } catch (error) {
        console.log(error);
    }
}
pinFileToIPFS()
"><code>const axios <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">'axios'</span>)
const FormData <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">'form-data'</span>)
const fs <span class="hljs-operator">=</span> <span class="hljs-built_in">require</span>(<span class="hljs-string">'fs'</span>)
const JWT <span class="hljs-operator">=</span> <span class="hljs-string">'PINATA_IPFS_API_KEY'</span>

const pinFileToIPFS <span class="hljs-operator">=</span> async () <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    const formData <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> FormData();
    const src <span class="hljs-operator">=</span> <span class="hljs-string">"2.jpg"</span>;

    const file <span class="hljs-operator">=</span> fs.createReadStream(src)
    formData.append(<span class="hljs-string">'file'</span>, file)

    const pinataMetadata <span class="hljs-operator">=</span> JSON.stringify({
        name: <span class="hljs-string">'testjpg'</span>,
    });
    formData.append(<span class="hljs-string">'pinataMetadata'</span>, pinataMetadata);

    const pinataOptions <span class="hljs-operator">=</span> JSON.stringify({
        cidVersion: <span class="hljs-number">0</span>,
    })
    formData.append(<span class="hljs-string">'pinataOptions'</span>, pinataOptions);

    <span class="hljs-keyword">try</span>{
        const res <span class="hljs-operator">=</span> await axios.post(<span class="hljs-string">"https://api.pinata.cloud/pinning/pinFileToIPFS"</span>, formData, {
            maxBodyLength: <span class="hljs-string">"Infinity"</span>,
            headers: {
                <span class="hljs-string">'Content-Type'</span>: `multipart<span class="hljs-operator">/</span>form<span class="hljs-operator">-</span>data; boundary<span class="hljs-operator">=</span>${formData._boundary}`,
                <span class="hljs-string">'Authorization'</span>: `Bearer ${JWT}`
            }
        });
        console.log(res.data);
    } <span class="hljs-keyword">catch</span> (<span class="hljs-function"><span class="hljs-keyword">error</span>) </span>{
        console.log(<span class="hljs-function"><span class="hljs-keyword">error</span>)</span>;
    }
}
pinFileToIPFS()
</code></pre><p>前端主要<code>axios</code>去调用<code>Pinata</code>提供的<code>IPFS</code>上传接口上传文件，上传成功后在<code>res.data</code>中会响应一个<code>IpfsHash</code>字段，我们再把这个字段提供给智能合约存储即可。</p><p>最后在前端展示图片时通过<code>https://&lt;your-gateway-domain&gt;/ipfs/&lt;IpfsHash&gt;</code>拼接展示即可。</p><p>具体文档也可参考<code>Pinata</code>官网文档：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.pinata.cloud/docs/getting-started">点此查看</a></p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">五、案例及源码</h3><p>这个我写一个完整的案例，源码也开源给大家参考：</p><ul><li><p>案例演示：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ipfs-pinata-frontend.vercel.app/">点此查看</a></p></li><li><p>链端源码：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/junkaione/ipfs-pinata-server">点此查看</a></p></li><li><p>前端源码：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/junkaione/ipfs-pinata-frontend">点此查看</a></p></li></ul><blockquote><p>到此就实现了在<code>Web3</code>应用中通过<code>Pinata</code>提供的<code>IPFS</code>服务实现上传文件的操作，希望各位小伙伴给我多多点赞、收藏哦~</p></blockquote>]]></content:encoded>
            <author>blue-9@newsletter.paragraph.com (Blue)</author>
        </item>
        <item>
            <title><![CDATA[在 Solidity 智能合约中调用 Chainlink 预言机获取外部数据及生成随机数]]></title>
            <link>https://paragraph.com/@blue-9/solidity-chainlink</link>
            <guid>xDteOTnyjT2cPUEML5VF</guid>
            <pubDate>Wed, 27 Dec 2023 06:35:33 GMT</pubDate>
            <description><![CDATA[在我们做去中心化应用开发时，其中有两个头疼的问题，一是想要获取去中心化的一些数据，如代币价格等，还有一个就是安全的创建随机数，这篇文章带大家了解一下这两块的实际应用。一、调用 Chainklink 获取外部数据1. Chainlink 预言机介绍由于智能合约无法调用外部 API 这一特性，所以诞生了预言机这个机制用来帮助智能合约获取外部数据，除了应用最广泛的价格数据以外，还包括一些天气数据，体育比赛数据，股票市场数据，交通数据，甚至包括总统选结果等数据。 除了提供数据，预言机广义上的功能也包括提供随机数和作为触发器实现智能合约执行，它们都算是链下的工具来和链上的合约进行交互。 而 Chainlink 是目前预言机数据的最大提供平台。2. Chainlink 获取数据示例// SPDX-License-Identifier: MIT pragma solidity ^0.8.19; import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; /** * THIS IS AN EXAMPLE...]]></description>
            <content:encoded><![CDATA[<blockquote><p>在我们做去中心化应用开发时，其中有两个头疼的问题，一是想要获取去中心化的一些数据，如代币价格等，还有一个就是安全的创建随机数，这篇文章带大家了解一下这两块的实际应用。</p></blockquote><h3 id="h-chainklink" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">一、调用 Chainklink 获取外部数据</h3><h4 id="h-1-chainlink" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">1. Chainlink 预言机介绍</h4><p>由于智能合约无法调用外部 <code>API</code> 这一特性，所以诞生了预言机这个机制用来帮助智能合约获取外部数据，除了应用最广泛的价格数据以外，还包括一些天气数据，体育比赛数据，股票市场数据，交通数据，甚至包括总统选结果等数据。</p><p>除了提供数据，预言机广义上的功能也包括提供随机数和作为触发器实现智能合约执行，它们都算是链下的工具来和链上的合约进行交互。</p><p>而 <code>Chainlink</code> 是目前预言机数据的最大提供平台。</p><h4 id="h-2-chainlink" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">2. Chainlink 获取数据示例</h4><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

import &quot;@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol&quot;;

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED
 * VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

/**
 * If you are reading data feeds on L2 networks, you must
 * check the latest answer from the L2 Sequencer Uptime
 * Feed to ensure that the data is accurate in the event
 * of an L2 sequencer outage. See the
 * https://docs.chain.link/data-feeds/l2-sequencer-feeds
 * page for details.
 */

contract DataConsumerV3 {
    AggregatorV3Interface internal dataFeed;

    /**
     * Network: Sepolia
     * Aggregator: BTC/USD
     * Address: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
     */
    constructor() {
        dataFeed = AggregatorV3Interface(
            0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
        );
    }

    /**
     * Returns the latest answer.
     */
    function getPrice() public view returns (int) {
        // prettier-ignore
        (
            /* uint80 roundID */,
            int answer,
            /*uint startedAt*/,
            /*uint timeStamp*/,
            /*uint80 answeredInRound*/
        ) = dataFeed.latestRoundData();
        return answer / (10 ** 8);
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.19;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"</span>;

<span class="hljs-comment">/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED
 * VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */</span>

<span class="hljs-comment">/**
 * If you are reading data feeds on L2 networks, you must
 * check the latest answer from the L2 Sequencer Uptime
 * Feed to ensure that the data is accurate in the event
 * of an L2 sequencer outage. See the
 * https://docs.chain.link/data-feeds/l2-sequencer-feeds
 * page for details.
 */</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">DataConsumerV3</span> </span>{
    AggregatorV3Interface <span class="hljs-keyword">internal</span> dataFeed;

    <span class="hljs-comment">/**
     * Network: Sepolia
     * Aggregator: BTC/USD
     * Address: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) </span>{
        dataFeed <span class="hljs-operator">=</span> AggregatorV3Interface(
            <span class="hljs-number">0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43</span>
        );
    }

    <span class="hljs-comment">/**
     * Returns the latest answer.
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getPrice</span>(<span class="hljs-params"></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">int</span></span>) </span>{
        <span class="hljs-comment">// prettier-ignore</span>
        (
            <span class="hljs-comment">/* uint80 roundID */</span>,
            <span class="hljs-keyword">int</span> answer,
            <span class="hljs-comment">/*uint startedAt*/</span>,
            <span class="hljs-comment">/*uint timeStamp*/</span>,
            <span class="hljs-comment">/*uint80 answeredInRound*/</span>
        ) <span class="hljs-operator">=</span> dataFeed.latestRoundData();
        <span class="hljs-keyword">return</span> answer <span class="hljs-operator">/</span> (<span class="hljs-number">10</span> <span class="hljs-operator">*</span><span class="hljs-operator">*</span> <span class="hljs-number">8</span>);
    }
}
</code></pre><p>上面是一个获取<code>BTC/USD</code>价格的智能合约示例。</p><ul><li><p>首先 <code>import</code>名为<code>AggregatorV3Interface</code>的接口，这个接口由<code>chainlink</code>官方的包提供。</p></li><li><p><code>constructor</code>构造函数中，初始化一个名为<code>dataFeed</code>的<code>AggregatorV3Interface</code>接口对象，在初始化的接口函数中传入<code>Chainklink</code>部署的对应<code>BTC/USD</code>价格的合约地址，这里因为是用的<code>Sepolia</code>测试网，具体每个网络可以获取哪些价格数据的合约地址，<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.chain.link/data-feeds/price-feeds/addresses?network=ethereum&amp;page=1">点此查看</a></p></li><li><p>最后通过一个<code>getPrice</code>函数调用<code>dataFeed</code>的<code>latestRoundData</code>方法来获取价格，返回的<code>answer</code>参数就是具体的价格，但每个交易对的金额精度不一样，一般要除以金额精度才是真正的<code>USD</code>单位，如这里的精度是<code>8</code>，最终<code>answer</code>需要除以<code>10 ** 8</code>才是<code>USD</code></p></li></ul><h4 id="h-3-remix" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">3. 通过 Remix 部署合约测试</h4><p>我们的智能合约需要调用<code>Sepolia</code>其他智能合约，所以没办法直接在本地测试，需要部署到<code>Sepolia</code>测试网上，建议通过<code>Remix</code>来部署</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/397252d503b27064e58440b50ae208ed8963586cc8a600079304c163f6dc7c5e.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p>首先在<code>MetaMask</code>钱包中添加<code>Sepolia</code>网络，然后在<code>Remix</code>部署界面选择使用<code>MetaMask</code>钱包部署，最后点击<code>Deploy</code>按钮部署。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d06e89431b582af48908939811c49083821cab5ed2aef1fa61440fab70dc539a.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p>部署成功后就可以看到我们已经部署的智能合约，执行<code>getPrice</code>方法来获取价格。</p><p>更详细的获取外部数据文档，<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.chain.link/data-feeds/getting-started">点此查看官方文档</a></p><h3 id="h-chainlink" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">二、调用 Chainlink 生成随机数</h3><h4 id="h-1-solidity" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">1. Solidity 直接生成随机数</h4><pre data-type="codeBlock" text="/** 
* 链上伪随机数生成
* 利用keccak256()打包一些链上的全局变量/自定义变量
* 返回时转换成uint256类型
*/
function getRandomOnchain() public view returns(uint256){
    // remix运行blockhash会报错
    bytes32 randomBytes = keccak256(abi.encodePacked(block.number, msg.sender, blockhash(block.timestamp-1)));
    return uint256(randomBytes);
}
"><code><span class="hljs-comment">/** 
* 链上伪随机数生成
* 利用keccak256()打包一些链上的全局变量/自定义变量
* 返回时转换成uint256类型
*/</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRandomOnchain</span>(<span class="hljs-params"></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">uint256</span></span>)</span>{
    <span class="hljs-comment">// remix运行blockhash会报错</span>
    <span class="hljs-keyword">bytes32</span> randomBytes <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>(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">number</span>, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, <span class="hljs-built_in">blockhash</span>(<span class="hljs-built_in">block</span>.<span class="hljs-built_in">timestamp</span>-<span class="hljs-number">1</span>)));
    <span class="hljs-keyword">return</span> <span class="hljs-keyword">uint256</span>(randomBytes);
}
</code></pre><p>主要是利用<code>keccak256</code>函数根据<code>区块高度</code>、<code>当前调用人</code>、<code>时间戳</code>等生成随机数，但是这种方法并不安全，因为结果对于矿工是可预测的，如我们做一款<code>Web3彩票项目</code>，矿工可以选择根据这个生成结果是否中奖选择是否上报这个区块。</p><h4 id="h-2-chainklink-vrf" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">2. 调用 Chainklink VRF 生成随机数示例</h4><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
// An example of a consumer contract that directly pays for each request.
pragma solidity ^0.8.7;

import &quot;@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol&quot;;
import &quot;@chainlink/contracts/src/v0.8/vrf/VRFV2WrapperConsumerBase.sol&quot;;

/**
 * Request testnet LINK and ETH here: https://faucets.chain.link/
 * Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
 */

/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */

contract VRFv2DirectFundingConsumer is
    VRFV2WrapperConsumerBase,
    ConfirmedOwner
{
    event RequestSent(uint256 requestId, uint32 numWords);
    event RequestFulfilled(
        uint256 requestId,
        uint256[] randomWords,
        uint256 payment
    );

    struct RequestStatus {
        uint256 paid; // amount paid in link
        bool fulfilled; // whether the request has been successfully fulfilled
        uint256[] randomWords;
    }
    mapping(uint256 =&gt; RequestStatus)
        public s_requests; /* requestId --&gt; requestStatus */

    // past requests Id.
    uint256[] public requestIds;
    uint256 public lastRequestId;

    // Depends on the number of requested values that you want sent to the
    // fulfillRandomWords() function. Test and adjust
    // this limit based on the network that you select, the size of the request,
    // and the processing of the callback request in the fulfillRandomWords()
    // function.
    uint32 callbackGasLimit = 100000;

    // The default is 3, but you can set this higher.
    uint16 requestConfirmations = 3;

    // For this example, retrieve 2 random values in one request.
    // Cannot exceed VRFV2Wrapper.getConfig().maxNumWords.
    uint32 numWords = 2;

    // Address LINK - hardcoded for Sepolia
    address linkAddress = 0x779877A7B0D9E8603169DdbD7836e478b4624789;

    // address WRAPPER - hardcoded for Sepolia
    address wrapperAddress = 0xab18414CD93297B0d12ac29E63Ca20f515b3DB46;

    constructor()
        ConfirmedOwner(msg.sender)
        VRFV2WrapperConsumerBase(linkAddress, wrapperAddress)
    {}

    function requestRandomWords()
        external
        onlyOwner
        returns (uint256 requestId)
    {
        requestId = requestRandomness(
            callbackGasLimit,
            requestConfirmations,
            numWords
        );
        s_requests[requestId] = RequestStatus({
            paid: VRF_V2_WRAPPER.calculateRequestPrice(callbackGasLimit),
            randomWords: new uint256Unsupported embed,
            fulfilled: false
        });
        requestIds.push(requestId);
        lastRequestId = requestId;
        emit RequestSent(requestId, numWords);
        return requestId;
    }

    function fulfillRandomWords(
        uint256 _requestId,
        uint256[] memory _randomWords
    ) internal override {
        require(s_requests[_requestId].paid &gt; 0, &quot;request not found&quot;);
        s_requests[_requestId].fulfilled = true;
        s_requests[_requestId].randomWords = _randomWords;
        emit RequestFulfilled(
            _requestId,
            _randomWords,
            s_requests[_requestId].paid
        );
    }

    function getRequestStatus(
        uint256 _requestId
    )
        external
        view
        returns (uint256 paid, bool fulfilled, uint256[] memory randomWords)
    {
        require(s_requests[_requestId].paid &gt; 0, &quot;request not found&quot;);
        RequestStatus memory request = s_requests[_requestId];
        return (request.paid, request.fulfilled, request.randomWords);
    }

    /**
     * Allow withdraw of Link tokens from the contract
     */
    function withdrawLink() public onlyOwner {
        LinkTokenInterface link = LinkTokenInterface(linkAddress);
        require(
            link.transfer(msg.sender, link.balanceOf(address(this))),
            &quot;Unable to transfer&quot;
        );
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-comment">// An example of a consumer contract that directly pays for each request.</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.7;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@chainlink/contracts/src/v0.8/vrf/VRFV2WrapperConsumerBase.sol"</span>;

<span class="hljs-comment">/**
 * Request testnet LINK and ETH here: https://faucets.chain.link/
 * Find information on LINK Token Contracts and get the latest ETH and LINK faucets here: https://docs.chain.link/docs/link-token-contracts/
 */</span>

<span class="hljs-comment">/**
 * THIS IS AN EXAMPLE CONTRACT THAT USES HARDCODED VALUES FOR CLARITY.
 * THIS IS AN EXAMPLE CONTRACT THAT USES UN-AUDITED CODE.
 * DO NOT USE THIS CODE IN PRODUCTION.
 */</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">VRFv2DirectFundingConsumer</span> <span class="hljs-keyword">is</span>
    <span class="hljs-title">VRFV2WrapperConsumerBase</span>,
    <span class="hljs-title">ConfirmedOwner</span>
</span>{
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">RequestSent</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> requestId, <span class="hljs-keyword">uint32</span> numWords</span>)</span>;
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">RequestFulfilled</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> requestId,
        <span class="hljs-keyword">uint256</span>[] randomWords,
        <span class="hljs-keyword">uint256</span> payment
    </span>)</span>;

    <span class="hljs-keyword">struct</span> <span class="hljs-title">RequestStatus</span> {
        <span class="hljs-keyword">uint256</span> paid; <span class="hljs-comment">// amount paid in link</span>
        <span class="hljs-keyword">bool</span> fulfilled; <span class="hljs-comment">// whether the request has been successfully fulfilled</span>
        <span class="hljs-keyword">uint256</span>[] randomWords;
    }
    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">uint256</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> RequestStatus)
        <span class="hljs-keyword">public</span> s_requests; <span class="hljs-comment">/* requestId --> requestStatus */</span>

    <span class="hljs-comment">// past requests Id.</span>
    <span class="hljs-keyword">uint256</span>[] <span class="hljs-keyword">public</span> requestIds;
    <span class="hljs-keyword">uint256</span> <span class="hljs-keyword">public</span> lastRequestId;

    <span class="hljs-comment">// Depends on the number of requested values that you want sent to the</span>
    <span class="hljs-comment">// fulfillRandomWords() function. Test and adjust</span>
    <span class="hljs-comment">// this limit based on the network that you select, the size of the request,</span>
    <span class="hljs-comment">// and the processing of the callback request in the fulfillRandomWords()</span>
    <span class="hljs-comment">// function.</span>
    <span class="hljs-keyword">uint32</span> callbackGasLimit <span class="hljs-operator">=</span> <span class="hljs-number">100000</span>;

    <span class="hljs-comment">// The default is 3, but you can set this higher.</span>
    <span class="hljs-keyword">uint16</span> requestConfirmations <span class="hljs-operator">=</span> <span class="hljs-number">3</span>;

    <span class="hljs-comment">// For this example, retrieve 2 random values in one request.</span>
    <span class="hljs-comment">// Cannot exceed VRFV2Wrapper.getConfig().maxNumWords.</span>
    <span class="hljs-keyword">uint32</span> numWords <span class="hljs-operator">=</span> <span class="hljs-number">2</span>;

    <span class="hljs-comment">// Address LINK - hardcoded for Sepolia</span>
    <span class="hljs-keyword">address</span> linkAddress <span class="hljs-operator">=</span> <span class="hljs-number">0x779877A7B0D9E8603169DdbD7836e478b4624789</span>;

    <span class="hljs-comment">// address WRAPPER - hardcoded for Sepolia</span>
    <span class="hljs-keyword">address</span> wrapperAddress <span class="hljs-operator">=</span> <span class="hljs-number">0xab18414CD93297B0d12ac29E63Ca20f515b3DB46</span>;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>)
        <span class="hljs-title">ConfirmedOwner</span>(<span class="hljs-params"><span class="hljs-built_in">msg</span>.sender</span>)
        <span class="hljs-title">VRFV2WrapperConsumerBase</span>(<span class="hljs-params">linkAddress, wrapperAddress</span>)
    </span>{}

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">requestRandomWords</span>(<span class="hljs-params"></span>)
        <span class="hljs-title"><span class="hljs-keyword">external</span></span>
        <span class="hljs-title">onlyOwner</span>
        <span class="hljs-title"><span class="hljs-keyword">returns</span></span> (<span class="hljs-params"><span class="hljs-keyword">uint256</span> requestId</span>)
    </span>{
        requestId <span class="hljs-operator">=</span> requestRandomness(
            callbackGasLimit,
            requestConfirmations,
            numWords
        );
        s_requests[requestId] <span class="hljs-operator">=</span> RequestStatus({
            paid: VRF_V2_WRAPPER.calculateRequestPrice(callbackGasLimit),
            randomWords: <span class="hljs-keyword">new</span> uint256Unsupported embed,
            fulfilled: <span class="hljs-literal">false</span>
        });
        requestIds.<span class="hljs-built_in">push</span>(requestId);
        lastRequestId <span class="hljs-operator">=</span> requestId;
        <span class="hljs-keyword">emit</span> RequestSent(requestId, numWords);
        <span class="hljs-keyword">return</span> requestId;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">fulfillRandomWords</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> _requestId,
        <span class="hljs-keyword">uint256</span>[] <span class="hljs-keyword">memory</span> _randomWords
    </span>) <span class="hljs-title"><span class="hljs-keyword">internal</span></span> <span class="hljs-title"><span class="hljs-keyword">override</span></span> </span>{
        <span class="hljs-built_in">require</span>(s_requests[_requestId].paid <span class="hljs-operator">></span> <span class="hljs-number">0</span>, <span class="hljs-string">"request not found"</span>);
        s_requests[_requestId].fulfilled <span class="hljs-operator">=</span> <span class="hljs-literal">true</span>;
        s_requests[_requestId].randomWords <span class="hljs-operator">=</span> _randomWords;
        <span class="hljs-keyword">emit</span> RequestFulfilled(
            _requestId,
            _randomWords,
            s_requests[_requestId].paid
        );
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getRequestStatus</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint256</span> _requestId
    </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">uint256</span> paid, <span class="hljs-keyword">bool</span> fulfilled, <span class="hljs-keyword">uint256</span>[] <span class="hljs-keyword">memory</span> randomWords</span>)
    </span>{
        <span class="hljs-built_in">require</span>(s_requests[_requestId].paid <span class="hljs-operator">></span> <span class="hljs-number">0</span>, <span class="hljs-string">"request not found"</span>);
        RequestStatus <span class="hljs-keyword">memory</span> request <span class="hljs-operator">=</span> s_requests[_requestId];
        <span class="hljs-keyword">return</span> (request.paid, request.fulfilled, request.randomWords);
    }

    <span class="hljs-comment">/**
     * Allow withdraw of Link tokens from the contract
     */</span>
    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdrawLink</span>(<span class="hljs-params"></span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        LinkTokenInterface link <span class="hljs-operator">=</span> LinkTokenInterface(linkAddress);
        <span class="hljs-built_in">require</span>(
            link.<span class="hljs-built_in">transfer</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, link.balanceOf(<span class="hljs-keyword">address</span>(<span class="hljs-built_in">this</span>))),
            <span class="hljs-string">"Unable to transfer"</span>
        );
    }
}
</code></pre><p>这是一个<code>Sepolia</code>测试网的示例，每个网络下<code>linkAddress</code>和<code>wrapperAddress</code>地址不同， 每个网络具体可以 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.chain.link/vrf/v2/direct-funding/supported-networks">点此查看</a></p><h4 id="h-3" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">3. 部署及测试</h4><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a347c3b6c0025fd47510ebd501ec003a74d361abd518af20fe15f4c9e6934b3d.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p>部署的时候同样选择<code>MetaMask</code>连接到<code>Sepolia</code>网络，部署好了后，我们还需要给部署好的合约转入<code>LINK</code>代币(很重要的)。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/91c3347302fedc44ee7465c2b65a92f98113683b4fbe7cc5d8fbca5b8c0919dd.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p>复制这个合约地址，用<code>MetaMask</code>钱包给这个地址转入<code>LINK</code>代币2-3个左右(测试用)。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2c644673beadbe16f677a77260a835b0b062b4a77a41deb85c082084d5ccd35c.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p>转入成功后就可以调用<code>requestRandomWords</code>方法，它会生成一个<code>requestId</code>，这个<code>requestId</code>可以通过调用<code>lastRequestId</code>方法查看。</p><p>最后通过给<code>getRequestStatus</code>方法传入<code>requestId</code>参数并调用就可以查看<code>Chainlink</code>响应给我们的<code>随机字符串</code>了。</p><p>具体的获取随机数官方文档，<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.chain.link/vrf/v2/direct-funding/examples/get-a-random-number">点此查看</a></p><blockquote><p>这样我们就最终实现了在Solidity 智能合约中调用 Chainklink 获取外部数据及生成随机数，如果各位小伙伴有什么问题，也欢迎留言哦！</p></blockquote>]]></content:encoded>
            <author>blue-9@newsletter.paragraph.com (Blue)</author>
        </item>
        <item>
            <title><![CDATA[Web3 开发智能合约部署及合约验证全流程]]></title>
            <link>https://paragraph.com/@blue-9/web3</link>
            <guid>O5wAykKUZENHawaQ5hMp</guid>
            <pubDate>Wed, 27 Dec 2023 06:31:15 GMT</pubDate>
            <description><![CDATA[这篇文章主要讲一下用hardhat框架开发好了一个solidity智能合约后，如何部署到以太坊(ETH)网络上，我会以以太坊测试网(Sepolia)来部署，和以太坊主网操作完全一样，其中会包含测试网ETH币领取，以一个ERC20代币的智能合约为例部署上线，以及上线后智能合约的验证(不验证的话在区块浏览器中看不到源码，别人不太相信你的项目)。一、Solidity 智能合约开发开发这一块如果各位小伙伴还不太懂的话，建议看一下我之前的文章: 聊一聊Web3是什么，Web3从入门到精通开发学习路线。 这里我直接以一个ERC20代币的智能合约为例，项目开源在github上，点此查看 智能合约代码：contract/MyTestToken.sol：// SPDX-License-Identifier: MIT pragma solidity ^0.8.20; import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import "@openzeppelin/contracts/access/Ownable.sol"; contract ...]]></description>
            <content:encoded><![CDATA[<blockquote><p>这篇文章主要讲一下用<code>hardhat</code>框架开发好了一个<code>solidity</code>智能合约后，如何部署到<code>以太坊(ETH)</code>网络上，我会以<code>以太坊测试网(Sepolia)</code>来部署，和<code>以太坊主网</code>操作完全一样，其中会包含<code>测试网ETH币</code>领取，以一个<code>ERC20代币</code>的<code>智能合约</code>为例部署上线，以及上线后智能合约的验证(不验证的话在区块浏览器中看不到源码，别人不太相信你的项目)。</p></blockquote><h3 id="h-solidity" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">一、Solidity 智能合约开发</h3><p>开发这一块如果各位小伙伴还不太懂的话，建议看一下我之前的文章: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/0x7C2BF786255eDc86C28Da9543C8E662DFB1dF8b1/F9WyqPHOgfkSFt5K9MRGSUdHETOvVKQmhFqOBw5dEqA">聊一聊Web3是什么，Web3从入门到精通开发学习路线</a>。</p><p>这里我直接以一个<code>ERC20代币</code>的智能合约为例，项目开源在<code>github</code>上，<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/junkaione/MyTestToken">点此查看</a></p><p>智能合约代码：<code>contract/MyTestToken.sol：</code></p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import &quot;@openzeppelin/contracts/token/ERC20/ERC20.sol&quot;;
import &quot;@openzeppelin/contracts/access/Ownable.sol&quot;;

contract MyTestToken is ERC20, Ownable {
    constructor(
        string memory name,
        string memory symbol,
        uint256 initialSupply
    ) ERC20(name, symbol) Ownable(msg.sender) {
        _mint(msg.sender, initialSupply);
    }

    function mint(address to, uint256 amount) public onlyOwner {
        _mint(to, amount);
    }

    function burn(address account, uint256 amount) public onlyOwner {
        _burn(account, amount);
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> ^0.8.20;</span>

<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/token/ERC20/ERC20.sol"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@openzeppelin/contracts/access/Ownable.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">MyTestToken</span> <span class="hljs-keyword">is</span> <span class="hljs-title">ERC20</span>, <span class="hljs-title">Ownable</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params">
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> name,
        <span class="hljs-keyword">string</span> <span class="hljs-keyword">memory</span> symbol,
        <span class="hljs-keyword">uint256</span> initialSupply
    </span>) <span class="hljs-title">ERC20</span>(<span class="hljs-params">name, symbol</span>) <span class="hljs-title">Ownable</span>(<span class="hljs-params"><span class="hljs-built_in">msg</span>.sender</span>) </span>{
        _mint(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>, initialSupply);
    }

    <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> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        _mint(to, amount);
    }

    <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> account, <span class="hljs-keyword">uint256</span> amount</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title">onlyOwner</span> </span>{
        _burn(account, amount);
    }
}
</code></pre><p>单测代码：<code>test/MyTestToken.ts：</code></p><pre data-type="codeBlock" text="import { expect } from &quot;chai&quot;;
import { ethers } from &quot;hardhat&quot;;

describe(&quot;MyTestToken&quot;, function () {
  const useMTT = async () =&gt; {
    const MyTestToken = await ethers.getContractFactory(&quot;MyTestToken&quot;);
    const MTT = await MyTestToken.deploy(&quot;My Test Token&quot;, &quot;MTT&quot;, 1000000);

    return MTT;
  };

  it(&quot;Deployment&quot;, async function () {
    const [owner] = await ethers.getSigners();

    const MTT = await useMTT();

    expect(await MTT.totalSupply()).to.equal(1000000);
    expect(await MTT.balanceOf(owner)).to.equal(1000000);
  });

  it(&quot;Transfer&quot;, async function () {
    const [owner, address1] = await ethers.getSigners();

    const MTT = await useMTT();

    await MTT.transfer(address1, 400000);
    expect(await MTT.balanceOf(owner)).to.equal(600000);
    expect(await MTT.balanceOf(address1)).to.equal(400000);
  });

  it(&quot;Mint&quot;, async function () {
    const [owner] = await ethers.getSigners();

    const MTT = await useMTT();
    await MTT.mint(owner, 200000);
    expect(await MTT.totalSupply()).to.equal(1200000);
    expect(await MTT.balanceOf(owner)).to.equal(1200000);
  });

  it(&quot;Burn&quot;, async function () {
    const [owner] = await ethers.getSigners();

    const MTT = await useMTT();
    await MTT.burn(owner, 300000);
    expect(await MTT.totalSupply()).to.equal(700000);
    expect(await MTT.balanceOf(owner)).to.equal(700000);
  });
});
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">expect</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"chai"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">ethers</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"hardhat"</span>;

describe(<span class="hljs-string">"MyTestToken"</span>, <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
  const useMTT <span class="hljs-operator">=</span> async () <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    const MyTestToken <span class="hljs-operator">=</span> await ethers.getContractFactory(<span class="hljs-string">"MyTestToken"</span>);
    const MTT <span class="hljs-operator">=</span> await MyTestToken.deploy(<span class="hljs-string">"My Test Token"</span>, <span class="hljs-string">"MTT"</span>, <span class="hljs-number">1000000</span>);

    <span class="hljs-keyword">return</span> MTT;
  };

  it(<span class="hljs-string">"Deployment"</span>, async <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    const [owner] <span class="hljs-operator">=</span> await ethers.getSigners();

    const MTT <span class="hljs-operator">=</span> await useMTT();

    expect(await MTT.totalSupply()).to.equal(<span class="hljs-number">1000000</span>);
    expect(await MTT.balanceOf(owner)).to.equal(<span class="hljs-number">1000000</span>);
  });

  it(<span class="hljs-string">"Transfer"</span>, async <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    const [owner, address1] <span class="hljs-operator">=</span> await ethers.getSigners();

    const MTT <span class="hljs-operator">=</span> await useMTT();

    await MTT.<span class="hljs-built_in">transfer</span>(address1, <span class="hljs-number">400000</span>);
    expect(await MTT.balanceOf(owner)).to.equal(<span class="hljs-number">600000</span>);
    expect(await MTT.balanceOf(address1)).to.equal(<span class="hljs-number">400000</span>);
  });

  it(<span class="hljs-string">"Mint"</span>, async <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    const [owner] <span class="hljs-operator">=</span> await ethers.getSigners();

    const MTT <span class="hljs-operator">=</span> await useMTT();
    await MTT.mint(owner, <span class="hljs-number">200000</span>);
    expect(await MTT.totalSupply()).to.equal(<span class="hljs-number">1200000</span>);
    expect(await MTT.balanceOf(owner)).to.equal(<span class="hljs-number">1200000</span>);
  });

  it(<span class="hljs-string">"Burn"</span>, async <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) </span>{
    const [owner] <span class="hljs-operator">=</span> await ethers.getSigners();

    const MTT <span class="hljs-operator">=</span> await useMTT();
    await MTT.burn(owner, <span class="hljs-number">300000</span>);
    expect(await MTT.totalSupply()).to.equal(<span class="hljs-number">700000</span>);
    expect(await MTT.balanceOf(owner)).to.equal(<span class="hljs-number">700000</span>);
  });
});
</code></pre><p>智能合约部署上线后就不能修改了，单测非常非常非常重要，务必保证每个功能点没问题</p><p>运行<code>npx hardhat test</code>执行单测，如下图所示，全部通过</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3e9c6e76aceecedcd708f87e65d33296aa6007ab151beac677abfc59cc311483.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">二、智能合约部署</h3><p>我们的智能合约部署必须依赖区块链的节点上，如 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.infura.io/zh">Infura</a> 或<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.alchemy.com/">Alchemy</a></p><p>这里我以<code>Infura</code>为例，需要我们先上去注册一个账号</p><p>注册登录好后</p><ol><li><p>创建一个新的<code>API KEY</code></p></li></ol><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/af1a3d4f1febb2854edc8de8d66be17738762f86bdd870c412de57b67ed36072.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><ol><li><p>进入创建的<code>API KEY</code>里选择对应的部署的节点，这里我们部署到<code>ETH</code>的测试网<code>Sepolia</code>上，<code>Infura</code>也提供了一个可以领<code>Sepolia</code>测试币的水龙头，大家可以点后面这个链接领取测试币，<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.infura.io/faucet/sepolia">Sepolia水龙头</a></p></li></ol><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/3e5915f56099b110e046421baf99d9e1c2e45a282535d3036f9c3d7c40d1ec28.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><ol><li><p>最后可以在激活的节点里复制刚才我们选择的<code>节点URL</code></p></li></ol><p>回到项目中</p><h4 id="h-1-dotenvenv" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">1. 安装<code>dotenv</code>并创建<code>env</code>文件</h4><p><code>npm i dotenv -D</code>安装支持读取<code>env</code>文件的依赖，并在项目跟目录创建<code>env</code>文件</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/38250b6e92c93b44b2557789bffe48f168f3f8467e3b43945083de634f6e6655.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p><code>ETH_SEPOILA_URL</code>为刚才我们复制的节点URL</p><p><code>PRIVATE_KEY</code>是我们需要准备一个钱包来部署我们的智能合约，这里要填钱包的<code>私钥</code>，请一定要保存好<code>私钥</code>不要泄漏，这个钱包在智能合约部署后为智能合约<code>owner</code>，只有他可以<code>增发代币</code>和<code>销毁代币</code></p><h4 id="h-2-hardhatsepolia" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">2. 在<code>hardhat</code>配置文件中添加<code>Sepolia</code>网络节点</h4><p><code>hardhat.config.ts：</code></p><pre data-type="codeBlock" text="import { HardhatUserConfig } from &quot;hardhat/config&quot;;
import &quot;@nomicfoundation/hardhat-toolbox&quot;;
import dotenv from &quot;dotenv&quot;;

dotenv.config();

const { ETH_SEPOILA_URL, PRIVATE_KEY = &quot;&quot; } = process.env;

const config: HardhatUserConfig = {
  solidity: &quot;0.8.20&quot;,
  networks: {
    sepolia: {
      url: ETH_SEPOILA_URL,
      accounts: [PRIVATE_KEY],
    },
  },
};

export default config;
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">HardhatUserConfig</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"hardhat/config"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"@nomicfoundation/hardhat-toolbox"</span>;
<span class="hljs-keyword">import</span> <span class="hljs-title">dotenv</span> <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"dotenv"</span>;

dotenv.config();

const { ETH_SEPOILA_URL, PRIVATE_KEY <span class="hljs-operator">=</span> <span class="hljs-string">""</span> } <span class="hljs-operator">=</span> process.env;

const config: HardhatUserConfig <span class="hljs-operator">=</span> {
  solidity: <span class="hljs-string">"0.8.20"</span>,
  networks: {
    sepolia: {
      url: ETH_SEPOILA_URL,
      accounts: [PRIVATE_KEY],
    },
  },
};

export default config;
</code></pre><p>就是引入我们<code>env</code>中的配置，并通过<code>networks</code>添加<code>sepolia</code>网络节点</p><h4 id="h-3" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">3. 编写部署脚本</h4><p><code>scripts/deploy.ts：</code></p><pre data-type="codeBlock" text="import { ethers } from &quot;hardhat&quot;;

async function main() {
  const [deployer] = await ethers.getSigners();

  console.log(&quot;Deploying contracts with the account:&quot;, deployer.address);

  const MyTestToken = await ethers.getContractFactory(&quot;MyTestToken&quot;);
  const MTT = await MyTestToken.deploy(&quot;My Test Token&quot;, &quot;MTT&quot;, 1000000);

  console.log(&quot;MyTestToken address:&quot;, await MTT.getAddress());
}

main().catch((error) =&gt; {
  console.error(error);
  process.exitCode = 1;
});
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">ethers</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"hardhat"</span>;

async <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">main</span>(<span class="hljs-params"></span>) </span>{
  const [deployer] <span class="hljs-operator">=</span> await ethers.getSigners();

  console.log(<span class="hljs-string">"Deploying contracts with the account:"</span>, deployer.<span class="hljs-built_in">address</span>);

  const MyTestToken <span class="hljs-operator">=</span> await ethers.getContractFactory(<span class="hljs-string">"MyTestToken"</span>);
  const MTT <span class="hljs-operator">=</span> await MyTestToken.deploy(<span class="hljs-string">"My Test Token"</span>, <span class="hljs-string">"MTT"</span>, <span class="hljs-number">1000000</span>);

  console.log(<span class="hljs-string">"MyTestToken address:"</span>, await MTT.getAddress());
}

main().catch((<span class="hljs-function"><span class="hljs-keyword">error</span>) => </span>{
  console.error(<span class="hljs-function"><span class="hljs-keyword">error</span>)</span>;
  process.exitCode <span class="hljs-operator">=</span> <span class="hljs-number">1</span>;
});
</code></pre><p>其中在<code>MyTestToken.deploy()</code>方法中传入的三个参数就是我们智能合约构造函数所需的三个参数</p><h4 id="h-4" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">4. 执行部署</h4><p>执行<code>npx hardhat run scripts/deploy.ts --network sepolia</code>来部署</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/c71e7227561cd0a00e5a1f32f13c4bd7046a42722211886fbe5808fca3f05834.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p>成功后可以看到执行部署的钱包地址，及部署后的智能合约地址，前端就可以根据这个智能合约地址来执行相应的操作了</p><h4 id="h-5" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">5. 在区块浏览器中查看部署的智能合约</h4><p>复制上面的智能合约地址，到 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://sepolia.etherscan.io/">区块浏览器</a> 中，就可以搜索查看了</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/fc57352bcfa283327b75c1b0d671881407b96f87f3c4cbbef7fd88b1ed608b86.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p>这个我们智能合约的主页，因为我们发的是<code>Token</code>代币，还有个<code>Token</code>代币主页，点击上图中间标红查看</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/9cd5273894fb3d6e2fcae924ddf1551060c34ed9541860f5c10a98fe574bb558.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p>点击下面的<code>Contract</code>后可以看到我们合约是一段编译过的字符，用户并不能看到智能合约的源码，下面教大家如何验证我们的智能合约让用户能够看到源码</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4bd920300906fcd25dca07462d2cfda97f8d44c2798a739a5721ef26cd303ad5.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">三、智能合约验证</h3><p>为了增强用户对我们智能合约的信任，我们一般会将智能合约验证开源以增强用户对我们智能合约的信任度</p><ol><li><p>在<code>ETH</code>主网区块链浏览器中申请<code>API KEY</code></p></li></ol><p><code>API KEY</code>在主网及测试网是通用的，我们在主网申请即可</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e3af58c45023f85e75a8a9a6d09272aa9c091b17a6c5ce21cae0fad9bf28104e.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5ec3727259614dee842a636f941a5fd1008cb392bc12c76fa9aa45c1ee43ce52.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p>对<code>API KEY</code>取个名称创建即可，成功后复制这个<code>API KEY</code></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/15b93d3a3f4856ed58b3790f9754a701d451aea91c587350af8b9fdb9e746ec6.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><ol><li><p>在代码<code>env</code>中添加<code>ETHERSCAN_APIKEY</code>并设置刚才的<code>API KEY</code></p></li></ol><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/405450173e435620828b759f7498da0a0d7ba4963c2781a8a4029886f7ccfd8e.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><ol><li><p>在<code>hardhat.config.ts</code>中添加相应配置</p></li></ol><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/a3208e2ff57244af906e6771c2115d318d981b6adf726d313797434807ed1bbe.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><ol><li><p>运行命令，执行验证</p></li></ol><p><code>npx hardhat verify --network sepolia 0x634e6E8b817D5975B9808D0272D1f62DF1AdD91b &quot;My Test Token&quot; &quot;MTT&quot; 1000000</code></p><p>这行命令格式为<code>npx hardhat verify --network 您部署的网络 智能合约地址 构造函数参数</code></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cdb22ed04292af8a57f5484a711dd31df8452286826104b343efd0d9fc34e7ae.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><p>看到如下输出就验证成功了，成功后在区块链浏览器上就可以看到智能合约源码了</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/e2572068361ea86a5cad256fe22bc09f2dc8c5cd80b068dcca32bfdebcfd5002.png" alt="image.png" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">image.png</figcaption></figure><blockquote><p>如此就完成了我们Web3 开发智能合约部署的全流程，从最初的开发，到编译，单元测试，部署，最后的验证合约。</p></blockquote>]]></content:encoded>
            <author>blue-9@newsletter.paragraph.com (Blue)</author>
        </item>
        <item>
            <title><![CDATA[Solidity 数组删除指定位置元素及指定位置新增元素]]></title>
            <link>https://paragraph.com/@blue-9/solidity</link>
            <guid>TiYbOs6Btr1kaG8hDvvm</guid>
            <pubDate>Wed, 27 Dec 2023 06:26:27 GMT</pubDate>
            <description><![CDATA[在使用Solidity开发智能合约时，免不了使用数组，但Solidity中的数组，只提供了push和pop方法来实现尾部新增和删除元素功能，这篇文章主要讲讲实际开发中怎么实现数组删除指定位置元素及指定位置新增元素。一、数组删除指定位置元素1. 默认delete操作符// SPDX-License-Identifier: MIT pragma solidity 0.8.19; contract ArrayRemoveItem { uint[] public arr = [1,2,3,4,5]; function remove(uint index) public { require(index &#x3C; arr.length, "index not found"); delete arr[index]; } function getLength() public view returns(uint) { return arr.length; } } 这种操作有个问题是，它会把指定位置的元素恢复默认值，如这里uint类型的默认值是0。 我们执行remove(1)操作之后，arr的...]]></description>
            <content:encoded><![CDATA[<blockquote><p>在使用<code>Solidity</code>开发智能合约时，免不了使用数组，但<code>Solidity</code>中的数组，只提供了<code>push</code>和<code>pop</code>方法来实现尾部新增和删除元素功能，这篇文章主要讲讲实际开发中怎么实现数组删除指定位置元素及指定位置新增元素。</p></blockquote><h3 id="h-yuan" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">一、数组删除指定位置元素</h3><h4 id="h-1-delete" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">1. 默认<code>delete</code>操作符</h4><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract ArrayRemoveItem {
    uint[] public arr = [1,2,3,4,5];

    function remove(uint index) public {
        require(index &lt; arr.length, &quot;index not found&quot;);
        delete arr[index];
    }

    function getLength() public view returns(uint) {
        return arr.length;
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.19;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ArrayRemoveItem</span> </span>{
    <span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">public</span> arr <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>];

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">remove</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> index</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-built_in">require</span>(index <span class="hljs-operator">&#x3C;</span> arr.<span class="hljs-built_in">length</span>, <span class="hljs-string">"index not found"</span>);
        <span class="hljs-keyword">delete</span> arr[index];
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLength</span>(<span class="hljs-params"></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">uint</span></span>) </span>{
        <span class="hljs-keyword">return</span> arr.<span class="hljs-built_in">length</span>;
    }
}
</code></pre><p>这种操作有个问题是，它会把指定位置的元素恢复默认值，如这里<code>uint</code>类型的默认值是<code>0</code>。</p><p>我们执行<code>remove(1)</code>操作之后，<code>arr</code>的值为<code>[1,0,3,4,5]</code>，并且执行<code>getLength()</code>值为<code>5</code>。</p><p>这种方法的缺点是如果<code>0</code>本身在我们数组中是有意义的，那最终删除了之后值也是<code>0</code>就会导致最终的智能合约逻辑有问题。并且数组长度可能越来越长。</p><h4 id="h-2-yuanyuan" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">2. 和末尾元素交换位置，并删除末尾位置元素</h4><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract ArrayRemoveItem {
    uint[] public arr = [1,2,3,4,5];

    function remove(uint index) public {
        require(index &lt; arr.length, &quot;index not found&quot;);
        arr[index] = arr[arr.length - 1];
        arr.pop();
    }

    function getLength() public view returns(uint) {
        return arr.length;
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.19;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ArrayRemoveItem</span> </span>{
    <span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">public</span> arr <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>];

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">remove</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> index</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-built_in">require</span>(index <span class="hljs-operator">&#x3C;</span> arr.<span class="hljs-built_in">length</span>, <span class="hljs-string">"index not found"</span>);
        arr[index] <span class="hljs-operator">=</span> arr[arr.<span class="hljs-built_in">length</span> <span class="hljs-operator">-</span> <span class="hljs-number">1</span>];
        arr.<span class="hljs-built_in">pop</span>();
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLength</span>(<span class="hljs-params"></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">uint</span></span>) </span>{
        <span class="hljs-keyword">return</span> arr.<span class="hljs-built_in">length</span>;
    }
}
</code></pre><p>通过把末尾元素值先赋值给目标元素，再删除末尾元素实现删除目标元素的功能。</p><p>执行<code>remove(1)</code>操作后，<code>arr</code>的值为<code>[1,5,3,4]</code>，执行<code>getLength()</code>值为4。</p><p>这种方法的缺点是我们删除中间一个元素后，数组的顺序会有变化，如果我们数组本身顺序是有意义的(如按照时间排序的)，那最终也会导致我们智能合约逻辑有问题。</p><h4 id="h-3-yuanyuan" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">3. 目标元素后面所有元素向前移动一位</h4><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract ArrayRemoveItem {
    uint[] public arr = [1,2,3,4,5];

    function remove(uint index) public {
        require(index &lt; arr.length, &quot;index not found&quot;);
        for(uint i = index; i &lt; arr.length - 1; i++) {
            arr[i] = arr[i + 1];
        }
        arr.pop();
    }

    function getLength() public view returns(uint) {
        return arr.length;
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.19;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ArrayRemoveItem</span> </span>{
    <span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">public</span> arr <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>];

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">remove</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> index</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-built_in">require</span>(index <span class="hljs-operator">&#x3C;</span> arr.<span class="hljs-built_in">length</span>, <span class="hljs-string">"index not found"</span>);
        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> index; i <span class="hljs-operator">&#x3C;</span> arr.<span class="hljs-built_in">length</span> <span class="hljs-operator">-</span> <span class="hljs-number">1</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
            arr[i] <span class="hljs-operator">=</span> arr[i <span class="hljs-operator">+</span> <span class="hljs-number">1</span>];
        }
        arr.<span class="hljs-built_in">pop</span>();
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLength</span>(<span class="hljs-params"></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">uint</span></span>) </span>{
        <span class="hljs-keyword">return</span> arr.<span class="hljs-built_in">length</span>;
    }
}
</code></pre><p>要想删除目标元素也不改变原本数组位置，可以通过把目标元素后面所有元素向前移动一位，再删除最后一个元素来实现删除目标元素。</p><p>执行<code>remove(1)</code>后，<code>arr</code>的值为<code>[1,3,4,5]</code>，并且执行<code>getLength()</code>值为4，得到的结果全是我们想要的。</p><p>但这种方法也有缺点，就是花费的<code>gas</code>费较高。</p><p>所以没有完美的方法，大家可以根据自己业务开发中实际需要选择，如果在你的业务中数组顺序无意义，推荐使用第二种方法(和尾部元素交互位置，删除位置元素)，如果你的业务中是数组顺序有意义，推荐使用第三种方法。</p><h3 id="h-yuan" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">二、指定位置新增元素</h3><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity 0.8.19;

contract ArrayRemoveItem {
    uint[] public arr = [1,2,3,4,5];

    function remove(uint index) public {
        require(index &lt; arr.length, &quot;index not found&quot;);
        for(uint i = index; i &lt; arr.length - 1; i++) {
            arr[i] = arr[i + 1];
        }
        arr.pop();
    }

    function add(uint index, uint value) public {
        require(index &lt; arr.length, &quot;index not found&quot;);
        arr.push();
        for(uint i = arr.length - 1; i &gt; index; i--) {
            arr[i] = arr[i - 1];
        }
        arr[index] = value;
    }

    function getLength() public view returns(uint) {
        return arr.length;
    }
}
"><code><span class="hljs-comment">// SPDX-License-Identifier: MIT</span>
<span class="hljs-meta"><span class="hljs-keyword">pragma</span> <span class="hljs-keyword">solidity</span> 0.8.19;</span>

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">ArrayRemoveItem</span> </span>{
    <span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">public</span> arr <span class="hljs-operator">=</span> [<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">5</span>];

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">remove</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> index</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-built_in">require</span>(index <span class="hljs-operator">&#x3C;</span> arr.<span class="hljs-built_in">length</span>, <span class="hljs-string">"index not found"</span>);
        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> index; i <span class="hljs-operator">&#x3C;</span> arr.<span class="hljs-built_in">length</span> <span class="hljs-operator">-</span> <span class="hljs-number">1</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>) {
            arr[i] <span class="hljs-operator">=</span> arr[i <span class="hljs-operator">+</span> <span class="hljs-number">1</span>];
        }
        arr.<span class="hljs-built_in">pop</span>();
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">add</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> index, <span class="hljs-keyword">uint</span> value</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-built_in">require</span>(index <span class="hljs-operator">&#x3C;</span> arr.<span class="hljs-built_in">length</span>, <span class="hljs-string">"index not found"</span>);
        arr.<span class="hljs-built_in">push</span>();
        <span class="hljs-keyword">for</span>(<span class="hljs-keyword">uint</span> i <span class="hljs-operator">=</span> arr.<span class="hljs-built_in">length</span> <span class="hljs-operator">-</span> <span class="hljs-number">1</span>; i <span class="hljs-operator">></span> index; i<span class="hljs-operator">-</span><span class="hljs-operator">-</span>) {
            arr[i] <span class="hljs-operator">=</span> arr[i <span class="hljs-operator">-</span> <span class="hljs-number">1</span>];
        }
        arr[index] <span class="hljs-operator">=</span> value;
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">getLength</span>(<span class="hljs-params"></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">uint</span></span>) </span>{
        <span class="hljs-keyword">return</span> arr.<span class="hljs-built_in">length</span>;
    }
}
</code></pre><p>指定位置新增元素的原理和上面删除差不多，先把新增一个空元素，然后把指定位置后面的所有元素向后移动一位，再把新值指定位置元素就完成了。</p><p>同样的缺点，消耗<code>gas</code>费较多哦。</p><blockquote><p>这样就可以实现<code>Solidity</code>中数组删除指定位置元素和指定位置新增元素，具体选哪种方法，大家可以肯定自己实际业务需要选择。</p></blockquote>]]></content:encoded>
            <author>blue-9@newsletter.paragraph.com (Blue)</author>
        </item>
        <item>
            <title><![CDATA[聊一聊Web3是什么，Web3从入门到精通开发学习路线]]></title>
            <link>https://paragraph.com/@blue-9/web3-web3</link>
            <guid>uOs0PVqc4urDpBr6I1EF</guid>
            <pubDate>Wed, 27 Dec 2023 06:11:06 GMT</pubDate>
            <description><![CDATA[最近发现Web3这一块非常有意思，概念什么也很新颖超前，这篇文章主要是给大家讲讲关于Web3我的理解，以及如何入门去做这一块的开发。一、介绍 Web3Web3区别于传统的Web2的地方是后端这一块，以前我们传统的Web2应用，需要服务器来跑对应的后端服务，数据也需要放到数据库里(MySQL、Redis等)，Web3应用是把我们的后端服务(一般称链端)放到链上运行，数据等也是存到链上。 传统的Web2服务一般使用Java、Go等来写后端服务，Web3一般使用Solidity(最常用)或Rust等来编写智能合约部署到链上运行。 但Web3目前我接触下来发现一些优点和缺点。 优点：因为你整体的服务是放到链上运行的，除非你设置了一个权限很高的账号可以修改数据，或者你写的智能合约本身有安全漏洞（但使用你这个Web3应用的人都能看到你的智能合约代码，别人认为你的智能合约有漏洞之类的可以选择不用），否则数据是不容易被修改的。这也是为什么Web3中Defi项目(去中心化金融)很多。 缺点：也正是因为放到链上运行智能合约，如果你需要修改链上的数据，需要别人对你的数据进行打包确认，数据修改的即时性不...]]></description>
            <content:encoded><![CDATA[<blockquote><p>最近发现<code>Web3</code>这一块非常有意思，概念什么也很新颖超前，这篇文章主要是给大家讲讲关于<code>Web3</code>我的理解，以及如何入门去做这一块的开发。</p></blockquote><h3 id="h-web3" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">一、介绍 Web3</h3><p><code>Web3</code>区别于传统的<code>Web2</code>的地方是后端这一块，以前我们传统的<code>Web2</code>应用，需要服务器来跑对应的后端服务，数据也需要放到数据库里(<code>MySQL</code>、<code>Redis</code>等)，<code>Web3</code>应用是把我们的后端服务(一般称链端)放到链上运行，数据等也是存到链上。</p><p>传统的<code>Web2</code>服务一般使用<code>Java</code>、<code>Go</code>等来写后端服务，<code>Web3</code>一般使用<code>Solidity</code>(最常用)或<code>Rust</code>等来编写智能合约部署到链上运行。</p><p>但<code>Web3</code>目前我接触下来发现一些优点和缺点。</p><p>优点：因为你整体的服务是放到链上运行的，除非你设置了一个权限很高的账号可以修改数据，或者你写的智能合约本身有安全漏洞（但使用你这个<code>Web3</code>应用的人都能看到你的智能合约代码，别人认为你的智能合约有漏洞之类的可以选择不用），否则数据是不容易被修改的。这也是为什么<code>Web3</code>中<code>Defi</code>项目(去中心化金融)很多。</p><p>缺点：也正是因为放到链上运行智能合约，如果你需要修改链上的数据，需要别人对你的数据进行打包确认，数据修改的即时性不是很高会有延时，从我使用<code>ETH测试链</code>部署的智能合约来说，修改一个数据，大约有几秒到十几秒延时时间，所以目前我感觉对即时性很高的应用不是很适合。</p><p>但<code>Web3</code>这块，目前还是一个蓝海，我个人包括有兴趣的小伙伴，下面我也列出一份学习路线。</p><h3 id="h-web3" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">二、了解Web3</h3><p>学习<code>Web3</code>开发，希望各位小伙伴一定一定要会<code>科学上网</code>(非常重要)。目前很多的资料或问题都只有<code>Google</code>或国外才有。</p><p>要从事<code>Web3</code>开发，首先就是要了解区块链。</p><ul><li><p>推荐一个视频：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.bilibili.com/video/BV1Vt411X7JF/">北京大学肖臻老师《区块链技术与应用》公开课</a>，非常棒的新手了解区块链课程。</p></li><li><p>以太坊官网：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://ethereum.org/zh/developers/">以太坊官网</a>，以太坊是我们做Web3开发最重要，也是应用最多的一条区块链。</p></li></ul><h3 id="h-solidity" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">三、学习智能合约开发(Solidity)</h3><p>对区块链有了了解后，就需要进行<code>Web3开发</code>的核心，<code>智能合约(链端)</code>开发了，他是一个<code>Web3应用</code>的核心，当然前端也很重要，如果不会前端的小伙伴，后面也建议去学学前端哦。</p><ul><li><p><code>MetaMask</code>钱包：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/yidakoumi.eth/dov9q_buGX_CeDavQBlPmB6c3p-Ok2QINXTpQhimYSc">MetaMask小狐狸钱包新手入门使用教程</a>，首先你需要先学习<code>MetaMask</code>钱包的使用，我们所有跟<code>Web3</code>应用的交互都需要通过一个钱包，这块非常重要。</p></li><li><p><code>Solidity</code>开发：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.wtf.academy/">WTF Academy(开发者的Web3开源大学)</a>，推荐至少学习<code>Solidity 101</code>、<code>Solidity 102</code>课程。</p></li><li><p>崔棉大师的视频课程：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.youtube.com/playlist?list=PLV16oVzL15MS-Zw8a3eEOADwbHhm8GrMp">Solidity8.0智能合约全面精通</a>，如果你喜欢看视频学习的话，推荐崔棉大师的课程。</p></li><li><p>理想区块链视频课程：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.youtube.com/playlist?list=PLfdytmZZ4Yl3hTfeROY-ptgbdcXg9Pybl">智能合约solidity教程</a>，这也是我经常看的。</p></li><li><p><code>Solidity</code>官方文档：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://learnblockchain.cn/docs/solidity/">Solidity 中文文档</a></p></li><li><p><code>Hardhat</code>框架： <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://learnblockchain.cn/docs/hardhat/getting-started/">Hardhat中文文档</a>，这是<code>Solidity</code>的框架，包含了智能合约开发、测试及部署，实际开发中我们也是使用框架进行开发。</p></li><li><p><code>OpenZeppelin</code>智能合约库：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://docs.tokensafer.com/openzeppelin/index.html">OpenZeppelin中文文档</a>，对于一些我们在开发中常用的智能合约库进行封装，我们可以直接引用，提高开发效率。</p></li><li><p>区块链节点：智能合约正式部署的时候我们一般需要用到节点商 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.infura.io/zh">Infura</a> 或<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.alchemy.com/">Alchemy</a></p></li></ul><p>学习完上面的部分其实也已经跟着写了一些简单的智能合约项目了，比如如何<code>发币</code>或者<code>发NFT</code>其实我们已经知道了，我非常建议大家使用 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://remix.learnblockchain.cn/">Remix编辑器</a> 试着开发自己的智能合约，如<code>发币</code>或<code>发NFT</code>等(只完成智能合约部分)，这个编辑器包含了开发、部署、和测试等，建议大家一定要完成全流程哦！</p><h3 id="h-web3" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">四、Web3 对应的前端开发</h3><p>学习这块之前大家一定要有前端基础哦，没有的话建议先去学习学习！<code>Web3</code>对应的前端开发主要是学习前端如何通过钱包与我们的智能合约交互。相应的技术栈如下：</p><ul><li><p><code>Ether.js</code>，依然推荐<code>WTF Academy</code>的课程<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.wtf.academy/ethers-101">Ethers.js 101 入门</a>、<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.wtf.academy/ethers-102">Ethers.js 102 进阶</a></p></li><li><p><code>Ether.js</code>官网文档：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://learnblockchain.cn/ethers_v5/">ethers.js 中文文档</a></p></li><li><p><code>wagmi</code>，一个<code>React Hook</code>的集合，包含了一些和智能合约交互的<code>Hook</code>：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://wagmi.sh/zh-CN">wagmi</a></p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">五、实际项目推荐</h3><p>学习完以上技术其实我们就有开发一个<code>Web3应用</code>的能力了。但是我们还需要实际项目来练手，这里附上我之前练手写的两个项目：</p><ul><li><p><code>Web3</code>投票项目：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gitee.com/junkaione/web3-vote-server">智能合约链端</a>、<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gitee.com/junkaione/web3-vote-frontend">前端</a></p></li><li><p><code>Web3 TodoList</code>项目：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gitee.com/junkaione/web3-todo-server">智能合约链端</a>、<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://gitee.com/junkaione/web3-todo-frontend">前端</a></p></li></ul><p>进阶的话推荐学习下成熟的Defi项目：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://app.uniswap.org/#/swap">Uniswap</a>、<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://pancakeswap.finance/">Pancakeswap</a>、<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://app.sushi.com/swap">Sushiswap</a>，可以在<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://etherscan.io/">Etherscan</a>找对应项目的智能合约源码看。</p><blockquote><p>到此我们就有进行大型<code>Web3</code>开发的能力，后续还需要大家多多实践、学习、进步哦。有问题的话各位小伙伴也可以下面留言交流哦！</p></blockquote>]]></content:encoded>
            <author>blue-9@newsletter.paragraph.com (Blue)</author>
        </item>
    </channel>
</rss>