<?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>BoxChen</title>
        <link>https://paragraph.com/@boxchen</link>
        <description>Full stack Dev.</description>
        <lastBuildDate>Sat, 04 Apr 2026 19:06:14 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <image>
            <title>BoxChen</title>
            <url>https://storage.googleapis.com/papyrus_images/ae5378a6f837db0000143a2dad2796bea3f6ab5e53e0fed22177e9fb11a577f4.jpg</url>
            <link>https://paragraph.com/@boxchen</link>
        </image>
        <copyright>All rights reserved</copyright>
        <item>
            <title><![CDATA[zk-SNARKs 在 Solidity 中的使用]]></title>
            <link>https://paragraph.com/@boxchen/zk-snarks-solidity</link>
            <guid>3lIv3yFliDLvp61noU86</guid>
            <pubDate>Fri, 08 Sep 2023 07:08:18 GMT</pubDate>
            <description><![CDATA[zk-SNARKs 在 Solidity 中的使用文章作者: @BoxMrChen，欢迎转载，转载请注明出处。 文章 Github 仓库: https://github.com/nishuzumi/blog 欢迎Star。如果文章有误，欢迎提PR。 进入交流群：欢迎添加个人微信 Im3boxtech，备注进群，我会拉你进入交流群。本文章主要讲述了如何在 Solidity 中使用 zk-SNARKs，以及如何使用 ZoKrates 编译器来生成证明和验证合约。 这文章不会过于深入 zk-SNARKs 的技术原理，这文章目的是为让读者能够理解 sk-SNARKs 的技术能在 EVM 中达到什么效果，如何使用，并且能在代码中运用。zk-SNARKs 简介关于 zk-SNARKs 的简短描述为，我们需要在 zk 电路中编写一段代码，这段代码的输入是一些公开的数据，输出是一些私有的数据。zk-SNARKs 的验证算法可以验证这段代码的输出是否正确，但是验证算法不会泄露任何私有数据。而 Solidity 合约的主要目的是验证 zk-SNARKs 的验证算法的结果，如果验证算法的结果正确，那么...]]></description>
            <content:encoded><![CDATA[<h1 id="h-zk-snarks-solidity" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">zk-SNARKs 在 Solidity 中的使用</h1><blockquote><p>文章作者: @BoxMrChen，欢迎转载，转载请注明出处。 文章 Github 仓库: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/nishuzumi/blog">https://github.com/nishuzumi/blog</a> 欢迎Star。如果文章有误，欢迎提PR。 进入交流群：欢迎添加个人微信 Im3boxtech，备注<code>进群</code>，我会拉你进入交流群。</p></blockquote><p>本文章主要讲述了如何在 Solidity 中使用 zk-SNARKs，以及如何使用 ZoKrates 编译器来生成证明和验证合约。</p><p>这文章不会过于深入 zk-SNARKs 的技术原理，这文章目的是为让读者能够理解 sk-SNARKs 的技术能在 EVM 中达到什么效果，如何使用，并且能在代码中运用。</p><h2 id="h-zk-snarks" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">zk-SNARKs 简介</h2><p>关于 zk-SNARKs 的简短描述为，我们需要在 zk 电路中编写一段代码，这段代码的输入是一些公开的数据，输出是一些私有的数据。zk-SNARKs 的验证算法可以验证这段代码的输出是否正确，但是验证算法不会泄露任何私有数据。而 Solidity 合约的主要目的是验证 zk-SNARKs 的验证算法的结果，如果验证算法的结果正确，那么合约会执行一些操作。</p><p>也就是说，在 EVM 上，只是进行了结果的验证，并没有进行一些复杂的计算，这些计算都是在 zk 电路中进行的。而这部分 zk 电路，则是在链下进行的，然后将结果提交到链上。</p><h2 id="h-solidity-zk-snarks" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">在 Solidity 中使用 zk-SNARKs</h2><p>首先，我们需要知道 zk-SNARKs 可以完成什么功能，其实很简单，我们可以简单的认为，zk-SNARKs 可以完成对一个函数运算结果的校验，比如说，我们有一个函数，输入是三个数字，输出是一个数字，我们可以使用 zk-SNARKs 来校验这个函数的输出是否正确。但是我们并不需要知道输入的三个数字是什么，只需要知道这个函数的输出即可，也就是说，在一个函数完成计算时，我们可以知道确实是有这么三个数他能符合这个函数的输入，并且能输出正确结果，但是我们并不知道这三个数是什么。</p><p>在 Solidity 中，我们可以使用 zk-SNARKs 来完成对一个函数的校验，但是我们需要知道这个函数的输入和输出，然后我们可以使用 ZoKrates 编译器来生成 zk 电路，然后将 zk 电路的代码放到 Solidity 合约中，然后在合约中完成对 zk 电路的验证。</p><h3 id="h-zokrates" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">安装 ZoKrates 编译器</h3><p>安装 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Zokrates/ZoKrates">ZoKrates</a></p><pre data-type="codeBlock" text="curl -LSfs get.zokrat.es | sh
"><code>curl <span class="hljs-operator">-</span>LSfs get.zokrat.es <span class="hljs-operator">|</span> sh
</code></pre><p>也可以选择其他安装方式，具体选择查看他们的 Github 页面。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/220a7786c4d7d33e0765c51f2b974ac567eaef39399a59b86b2b134a4c9f50c4.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><strong>编写 zk 电路</strong></p><p>从上一章节我们浅显的知道，一个 zk-SNARKs 电路需要的最基本的东西为：</p><ul><li><p>一个函数 - 我们需要有一个函数对数据进行运算，也就是程序 C</p></li><li><p>lambda - 所谓的“有毒废料”，其实就是一个 root key，我们需要通过它来生成 pk 和 vk</p></li></ul><p>有了这两个基础条件，用户就可以通过 pk，目标值，输入值来生成证明 w。随后，我们的验证程序通过 vk，目标值，证明 w 来验证证明的正确性。</p><p>我们先假设有这么一个第三方，他可以安全的生成 lambda，然后安全的将程序和 lambda 进行运算生成 vk 和 pk。</p><p>那么现在有两个新的角色，user 和 project。user 是用户，他确确实实拥有着一些数据，project 是项目合约，他需要验证用户的数据是否正确。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">一个函数</h3><p>我们首先需要一个函数，但是我并不打算举一些简单例子，因为我觉得这样做非常没有意义，因为 zk-SNARKs 的主要目的是为了验证一些复杂的函数，而不是一些简单的函数。</p><p>比如，我们现在需要生成一个存款凭证，有这个凭证，我们可以在任何地方取出这笔钱，但是我们并不知道这笔钱是谁的，我们只知道这笔钱是谁存的，存了多少，以及存款的时间。</p><p>首先我们需要一个存款函数，这个函数的输入为存款的金额，和一个随机数，然后输出为一个存款凭证。任何拥有这个凭证的人都可以取出这笔资金。所以，实际上，我们只需要编写验证知道这个凭证的验证函数即可。</p><pre data-type="codeBlock" text="import &quot;hashes/sha256/512bit&quot; as sha256;
import &quot;utils/pack/u32/nonStrictUnpack256&quot; as unpack256;

// deposit_amount: 存款金额
// secret: 随机数
// returns: 用于取款的commitment
def main(field deposit_amount, private field secret) -&gt; u32[8] {
    return sha256(unpack256(deposit_amount), unpack256(secret));
}
"><code><span class="hljs-keyword">import</span> <span class="hljs-string">"hashes/sha256/512bit"</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title"><span class="hljs-built_in">sha256</span></span>;
<span class="hljs-keyword">import</span> <span class="hljs-string">"utils/pack/u32/nonStrictUnpack256"</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">unpack256</span>;

<span class="hljs-comment">// deposit_amount: 存款金额</span>
<span class="hljs-comment">// secret: 随机数</span>
<span class="hljs-comment">// returns: 用于取款的commitment</span>
def main(field deposit_amount, <span class="hljs-keyword">private</span> field secret) <span class="hljs-operator">-</span><span class="hljs-operator">></span> u32[<span class="hljs-number">8</span>] {
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">sha256</span>(unpack256(deposit_amount), unpack256(secret));
}
</code></pre><p>关于 Zok 的语法和用法这里不过多描述，具体可以参考官网，这里简单解释一下，这个函数的输入为两个数字，一个是存款金额，一个是随机数，然后输出为一个 u32[8]，实际上就是 uint256.同时我们注意一下，参数中 deposit_amount 没有 private 关键词，说明这个参数是公开数据。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">编译文件</h3><p>这部分内容在 zokrates 中有讲述方式为</p><pre data-type="codeBlock" text="# compile
zokrates compile -i deposit.zok
# perform the setup phase
zokrates setup
# execute the program
zokrates compute-witness -a 337 113569
# generate a proof of computation
zokrates generate-proof
# export a solidity verifier
zokrates export-verifier
# or verify natively
zokrates verify
"><code><span class="hljs-comment"># compile</span>
zokrates <span class="hljs-built_in">compile</span> -i deposit.zok
<span class="hljs-comment"># perform the setup phase</span>
zokrates setup
<span class="hljs-comment"># execute the program</span>
zokrates compute-witness -a <span class="hljs-number">337</span> <span class="hljs-number">113569</span>
<span class="hljs-comment"># generate a proof of computation</span>
zokrates generate-proof
<span class="hljs-comment"># export a solidity verifier</span>
zokrates export-verifier
<span class="hljs-comment"># or verify natively</span>
zokrates verify
</code></pre><p>运行完成后会生成一堆文件，我们需要的是 proof.json, proving.key, verification.key, verifier.sol, out。</p><p>大部分其实都是模版文件生成文件可能不一样的地方在于 Verifier 合约中 verifyingKey，当然，我们阅读这个文件其实意义也不大，因为这里面全是一大堆数字和运算。 实际上我们需要看的内容就是这些 ∑</p><pre data-type="codeBlock" text="function verifyTx(
        Proof memory proof, uint[8] memory input
    ) public view returns (bool r) {
    uint[] memory inputValues = new uintUnsupported embed;

    for(uint i = 0; i &lt; input.length; i++){
        inputValues[i] = input[i];
    }
    if (verify(inputValues, proof) == 0) {
        return true;
    } else {
        return false;
    }
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">verifyTx</span>(<span class="hljs-params">
        Proof <span class="hljs-keyword">memory</span> proof, <span class="hljs-keyword">uint</span>[<span class="hljs-number">8</span>] <span class="hljs-keyword">memory</span> input
    </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> r</span>) </span>{
    <span class="hljs-keyword">uint</span>[] <span class="hljs-keyword">memory</span> inputValues <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> uintUnsupported 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> input.<span class="hljs-built_in">length</span>; i<span class="hljs-operator">+</span><span class="hljs-operator">+</span>){
        inputValues[i] <span class="hljs-operator">=</span> input[i];
    }
    <span class="hljs-keyword">if</span> (verify(inputValues, proof) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">0</span>) {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;
    } <span class="hljs-keyword">else</span> {
        <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;
    }
}
</code></pre><p>可以看到，我们需要两个参数,proof 和 input。至于这两个参数是干嘛的，我们暂时不过多深究。不过我们需要注意的是，在 inputs 中，所有的共有参数都会被加入到这个数组中，在数字最开头部分被推入。</p><p>比如，自动生成的 proof.json 文件就是一个有效的数据。</p><pre data-type="codeBlock" text="{
  &quot;scheme&quot;: &quot;gm17&quot;,
  &quot;curve&quot;: &quot;bn128&quot;,
  &quot;proof&quot;: {
    &quot;a&quot;: [
      &quot;0x05a83e3c3b3ff9d59bdffdcf7aa655f42b941b0063f82cf26516846056d09aa6&quot;,
      &quot;0x018039b7de92979ef6251c877971888ae049d09a6b48e5aa98c23ef91550ed36&quot;
    ],
    &quot;b&quot;: [
      [
        &quot;0x1e88e783456a27e4f02dde8c742610339e395eb0bbf7f7efc1113815dcf0a16f&quot;,
        &quot;0x1cc9de9e60c6519ea69c9b3a71c0809ac7ae3389a598d66fc27d378738d5de29&quot;
      ],
      [
        &quot;0x0715544abbc18e741620ff7c76cb2a7d3558ee157d23f275ab65c43c25357d07&quot;,
        &quot;0x0344257236ba33a3ce7ce34b8d518f7572984036db6f77fc2fc13f51c548a837&quot;
      ]
    ],
    &quot;c&quot;: [
      &quot;0x177113e528c76661a03a8f3f072f29e684244297a62926a0000d3a7135c1441f&quot;,
      &quot;0x18cf275d0bc621473688848946584af771afca42e4f2bd0ef1e5d06e0adefd0f&quot;
    ]
  },
  &quot;inputs&quot;: [
    &quot;0x0000000000000000000000000000000000000000000000000000000000000151&quot;,
    &quot;0x00000000000000000000000000000000000000000000000000000000bb3eada7&quot;,
    &quot;0x000000000000000000000000000000000000000000000000000000004b704815&quot;,
    &quot;0x00000000000000000000000000000000000000000000000000000000cddda451&quot;,
    &quot;0x00000000000000000000000000000000000000000000000000000000ca701d2a&quot;,
    &quot;0x000000000000000000000000000000000000000000000000000000001f278e64&quot;,
    &quot;0x00000000000000000000000000000000000000000000000000000000ef16f074&quot;,
    &quot;0x0000000000000000000000000000000000000000000000000000000040e13298&quot;,
    &quot;0x0000000000000000000000000000000000000000000000000000000026c5da72&quot;
  ]
}
"><code>{
  "scheme": <span class="hljs-string">"gm17"</span>,
  <span class="hljs-string">"curve"</span>: <span class="hljs-string">"bn128"</span>,
  <span class="hljs-string">"proof"</span>: {
    "<span class="hljs-selector-tag">a</span>": [
      <span class="hljs-string">"0x05a83e3c3b3ff9d59bdffdcf7aa655f42b941b0063f82cf26516846056d09aa6"</span>,
      <span class="hljs-string">"0x018039b7de92979ef6251c877971888ae049d09a6b48e5aa98c23ef91550ed36"</span>
    ],
    <span class="hljs-string">"b"</span>: [
      [
        <span class="hljs-string">"0x1e88e783456a27e4f02dde8c742610339e395eb0bbf7f7efc1113815dcf0a16f"</span>,
        <span class="hljs-string">"0x1cc9de9e60c6519ea69c9b3a71c0809ac7ae3389a598d66fc27d378738d5de29"</span>
      ],
      [
        <span class="hljs-string">"0x0715544abbc18e741620ff7c76cb2a7d3558ee157d23f275ab65c43c25357d07"</span>,
        <span class="hljs-string">"0x0344257236ba33a3ce7ce34b8d518f7572984036db6f77fc2fc13f51c548a837"</span>
      ]
    ],
    <span class="hljs-string">"c"</span>: [
      <span class="hljs-string">"0x177113e528c76661a03a8f3f072f29e684244297a62926a0000d3a7135c1441f"</span>,
      <span class="hljs-string">"0x18cf275d0bc621473688848946584af771afca42e4f2bd0ef1e5d06e0adefd0f"</span>
    ]
  },
  "inputs": [
    <span class="hljs-string">"0x0000000000000000000000000000000000000000000000000000000000000151"</span>,
    <span class="hljs-string">"0x00000000000000000000000000000000000000000000000000000000bb3eada7"</span>,
    <span class="hljs-string">"0x000000000000000000000000000000000000000000000000000000004b704815"</span>,
    <span class="hljs-string">"0x00000000000000000000000000000000000000000000000000000000cddda451"</span>,
    <span class="hljs-string">"0x00000000000000000000000000000000000000000000000000000000ca701d2a"</span>,
    <span class="hljs-string">"0x000000000000000000000000000000000000000000000000000000001f278e64"</span>,
    <span class="hljs-string">"0x00000000000000000000000000000000000000000000000000000000ef16f074"</span>,
    <span class="hljs-string">"0x0000000000000000000000000000000000000000000000000000000040e13298"</span>,
    <span class="hljs-string">"0x0000000000000000000000000000000000000000000000000000000026c5da72"</span>
  ]
}
</code></pre><p>至此，我们可以写一个简单合约。</p><pre data-type="codeBlock" text="// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Verifier} from &quot;./verifier.sol&quot;;

contract Master {
    event Despoit(uint256 commitment, uint amount);

    mapping(uint =&gt; uint) public proofs;

    Verifier v;

    constructor() {
        v = new Verifier();
    }

    function deposit(uint commitment) public payable {
        proofs[commitment] = msg.value;
        emit Despoit(commitment, msg.value);
    }

    function withdraw(
        uint commitment,
        Verifier.Proof memory proof,
        uint[9] memory inputs
    ) public {
        uint amount = inputs[0];
        require(v.verifyTx(proof, inputs));
        require(proofs[commitment] == amount);

        payable(msg.sender).transfer(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.0;</span>

<span class="hljs-keyword">import</span> {<span class="hljs-title">Verifier</span>} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"./verifier.sol"</span>;

<span class="hljs-class"><span class="hljs-keyword">contract</span> <span class="hljs-title">Master</span> </span>{
    <span class="hljs-function"><span class="hljs-keyword">event</span> <span class="hljs-title">Despoit</span>(<span class="hljs-params"><span class="hljs-keyword">uint256</span> commitment, <span class="hljs-keyword">uint</span> amount</span>)</span>;

    <span class="hljs-keyword">mapping</span>(<span class="hljs-keyword">uint</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">uint</span>) <span class="hljs-keyword">public</span> proofs;

    Verifier v;

    <span class="hljs-function"><span class="hljs-keyword">constructor</span>(<span class="hljs-params"></span>) </span>{
        v <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> Verifier();
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">deposit</span>(<span class="hljs-params"><span class="hljs-keyword">uint</span> commitment</span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> <span class="hljs-title"><span class="hljs-keyword">payable</span></span> </span>{
        proofs[commitment] <span class="hljs-operator">=</span> <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>;
        <span class="hljs-keyword">emit</span> Despoit(commitment, <span class="hljs-built_in">msg</span>.<span class="hljs-built_in">value</span>);
    }

    <span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">withdraw</span>(<span class="hljs-params">
        <span class="hljs-keyword">uint</span> commitment,
        Verifier.Proof <span class="hljs-keyword">memory</span> proof,
        <span class="hljs-keyword">uint</span>[<span class="hljs-number">9</span>] <span class="hljs-keyword">memory</span> inputs
    </span>) <span class="hljs-title"><span class="hljs-keyword">public</span></span> </span>{
        <span class="hljs-keyword">uint</span> amount <span class="hljs-operator">=</span> inputs[<span class="hljs-number">0</span>];
        <span class="hljs-built_in">require</span>(v.verifyTx(proof, inputs));
        <span class="hljs-built_in">require</span>(proofs[commitment] <span class="hljs-operator">=</span><span class="hljs-operator">=</span> amount);

        <span class="hljs-keyword">payable</span>(<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>).<span class="hljs-built_in">transfer</span>(amount);
    }
}
</code></pre><p>要注意的是 Verifier 合约中会出现两个<code>pragma solidity</code>，记得删掉中间那一个，保留最上面的那个，否则编译无法通过。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">测试</h2><p>首先我们需要明白一下标准流程，我们需要先进行 compile，setup，然后再进行 compute-witness，然后再进行 generate-proof，最后再进行 export-verifier。</p><p>但是这套流程并不是每次都必须的，因为这个是一个完整流程。我们需要进行一下区分。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">必要条件</h3><ul><li><p>compile - 编译 zk 电路 - 只需要执行一次 这个功能会生成 out 文件和 abi.json 文件，这两个是编译后的程序。</p></li><li><p>setup - 生成 zk 电路的 pk 和 vk - 只需要执行一次 这个功能会生成 proving.key 和 verification.key 文件，这两个文件是 zk 电路的公钥和私钥。实际上在进行 setup 的时候会产生 lambda，但是这些过程我们不需要太过于关心。</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">提交证明条件</h3><ul><li><p>compute-witness - 生成证明 - 这个功能会生成 witness 文件，这个文件是一个中间文件。</p></li><li><p>generate-proof - 生成证明的 Proof - 这个功能会生成 proof.json 文件，这个文件是证明需要提交的内容，一般来说里面的内容就是我们需要提交到链上的参数。</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">接受证明条件</h3><ul><li><p>export-verifier - 生成 verifier.sol - 这个功能会生成 verifier.sol 文件，这个文件是一个合约，我们需要将这个合约部署到链上，然后在我们的合约中调用这个合约来验证证明的正确性。</p></li><li><p>verify - 本地验证 - 这个功能会验证证明的正确性，但是这个功能并不会生成任何文件。</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">编写文件</h3><p>根据上面内容，我们可以写出一些用于测试的单元测试逻辑。</p><pre data-type="codeBlock" text="import { expect } from &quot;chai&quot;;
import { ethers } from &quot;hardhat&quot;;
import { Verifier } from &quot;../typechain-types/Verifier&quot;;
import { CompilationArtifacts, ZoKratesProvider } from &quot;zokrates-js&quot;;
import { readFileSync } from &quot;fs&quot;;
import { Master } from &quot;../typechain-types&quot;;
import { resolve } from &apos;path&apos;

describe(&quot;Verifier&quot;, function () {
  let master: Master;
  let zokratesProvider: ZoKratesProvider

  const zokArtifacts: CompilationArtifacts = {
    program: readFileSync(resolve(__dirname, &apos;../zok/out&apos;)),
    abi: JSON.parse(readFileSync(resolve(__dirname, &apos;../zok/abi.json&apos;), &apos;utf-8&apos;))
  }

  const provingKey = readFileSync(resolve(__dirname, &apos;../zok/proving.key&apos;))

  beforeEach(async () =&gt; {
    const { initialize } = await import(&quot;zokrates-js&quot;);
    zokratesProvider = (await initialize()).withOptions({
      backend: &apos;ark&apos;,
      curve: &apos;bn128&apos;,
      scheme: &apos;gm17&apos;
    });

    const bn256 = await ethers.getContractFactory(&quot;BN256G2&quot;).then((f) =&gt; f.deploy());

    master = await ethers.getContractFactory(&quot;Master&quot;, {
      libraries: {
        &quot;contracts/verifier.sol:BN256G2&quot;: bn256.address,
      }
    }).then((f) =&gt; f.deploy());
  })

  it(&quot;should verify a proof&quot;, async () =&gt; {
    const { witness, output } = zokratesProvider.computeWitness(
      zokArtifacts,
      [`${ethers.constants.WeiPerEther}`, &apos;23&apos;],
    )

    const commitment = hexListToUint256BigEndian(JSON.parse(output)).toString();

    await master.deposit(
      commitment,
      { value: ethers.constants.WeiPerEther }
    )

    const proof = zokratesProvider.generateProof(
      zokArtifacts.program,
      witness,
      provingKey);


    const sender = (await ethers.getSigners())[0];
    expect(() =&gt; master.connect(sender).
      withdraw(commitment, proof.proof as Verifier.ProofStruct, proof.inputs)
    ).to.changeEtherBalance(sender, ethers.constants.WeiPerEther);
  })
});


function hexListToUint256BigEndian(hexList: string[]) {
  let uint256Data = &quot;0x&quot;;
  for (const hex of hexList) {
    const cleanedHex = hex.replace(&quot;0x&quot;, &quot;&quot;);
    uint256Data += cleanedHex;
  }
  const uint256BigNumber = ethers.BigNumber.from(uint256Data);
  return uint256BigNumber;
}
"><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>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">Verifier</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"../typechain-types/Verifier"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">CompilationArtifacts</span>, <span class="hljs-title">ZoKratesProvider</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"zokrates-js"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">readFileSync</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"fs"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">Master</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"../typechain-types"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">resolve</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'path'</span>

<span class="hljs-title">describe</span>(<span class="hljs-string">"Verifier"</span>, <span class="hljs-title"><span class="hljs-keyword">function</span></span> () {
  <span class="hljs-title">let</span> <span class="hljs-title">master</span>: <span class="hljs-title">Master</span>;
  let zokratesProvider: ZoKratesProvider

  const zokArtifacts: CompilationArtifacts <span class="hljs-operator">=</span> {
    program: readFileSync(resolve(__dirname, <span class="hljs-string">'../zok/out'</span>)),
    <span class="hljs-built_in">abi</span>: JSON.parse(readFileSync(resolve(__dirname, <span class="hljs-string">'../zok/abi.json'</span>), <span class="hljs-string">'utf-8'</span>))
  }

  const provingKey <span class="hljs-operator">=</span> readFileSync(resolve(__dirname, <span class="hljs-string">'../zok/proving.key'</span>))

  beforeEach(async () <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    const { initialize } <span class="hljs-operator">=</span> await <span class="hljs-keyword">import</span>(<span class="hljs-string">"zokrates-js"</span>);
    zokratesProvider <span class="hljs-operator">=</span> (await initialize()).withOptions({
      backend: <span class="hljs-string">'ark'</span>,
      curve: <span class="hljs-string">'bn128'</span>,
      scheme: <span class="hljs-string">'gm17'</span>
    });

    const bn256 <span class="hljs-operator">=</span> await ethers.getContractFactory(<span class="hljs-string">"BN256G2"</span>).then((f) <span class="hljs-operator">=</span><span class="hljs-operator">></span> f.deploy());

    master <span class="hljs-operator">=</span> await ethers.getContractFactory(<span class="hljs-string">"Master"</span>, {
      libraries: {
        <span class="hljs-string">"contracts/verifier.sol:BN256G2"</span>: bn256.<span class="hljs-built_in">address</span>,
      }
    }).then((f) <span class="hljs-operator">=</span><span class="hljs-operator">></span> f.deploy());
  })

  it(<span class="hljs-string">"should verify a proof"</span>, async () <span class="hljs-operator">=</span><span class="hljs-operator">></span> {
    const { witness, output } <span class="hljs-operator">=</span> zokratesProvider.computeWitness(
      zokArtifacts,
      [`${ethers.constants.WeiPerEther}`, <span class="hljs-string">'23'</span>],
    )

    const commitment <span class="hljs-operator">=</span> hexListToUint256BigEndian(JSON.parse(output)).toString();

    await master.deposit(
      commitment,
      { <span class="hljs-built_in">value</span>: ethers.constants.WeiPerEther }
    )

    const proof <span class="hljs-operator">=</span> zokratesProvider.generateProof(
      zokArtifacts.program,
      witness,
      provingKey);


    const sender <span class="hljs-operator">=</span> (await ethers.getSigners())[<span class="hljs-number">0</span>];
    expect(() <span class="hljs-operator">=</span><span class="hljs-operator">></span> master.connect(sender).
      withdraw(commitment, proof.proof <span class="hljs-keyword">as</span> Verifier.ProofStruct, proof.inputs)
    ).to.changeEtherBalance(sender, ethers.constants.WeiPerEther);
  })
});


<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">hexListToUint256BigEndian</span>(<span class="hljs-params">hexList: <span class="hljs-keyword">string</span>[]</span>) </span>{
  let uint256Data <span class="hljs-operator">=</span> <span class="hljs-string">"0x"</span>;
  <span class="hljs-keyword">for</span> (const hex of hexList) {
    const cleanedHex <span class="hljs-operator">=</span> hex.replace(<span class="hljs-string">"0x"</span>, <span class="hljs-string">""</span>);
    uint256Data <span class="hljs-operator">+</span><span class="hljs-operator">=</span> cleanedHex;
  }
  const uint256BigNumber <span class="hljs-operator">=</span> ethers.BigNumber.from(uint256Data);
  <span class="hljs-keyword">return</span> uint256BigNumber;
}
</code></pre><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8eb7df408b1507bbac6df14c5136810f50cb317c9e9d0ca448280a1036845c21.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>项目的基础文件都放在: <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/nishuzumi/blog/tree/main/sources/zk">https://github.com/nishuzumi/blog/tree/main/sources/zk</a> 中，欢迎 Star。</p>]]></content:encoded>
            <author>boxchen@newsletter.paragraph.com (BoxChen)</author>
        </item>
        <item>
            <title><![CDATA[MEV资料]]></title>
            <link>https://paragraph.com/@boxchen/mev</link>
            <guid>Uolrbk6hECJApyHFTQ8K</guid>
            <pubDate>Mon, 28 Aug 2023 08:14:13 GMT</pubDate>
            <description><![CDATA[原文链接：github.com/nishuzumi/blog/blob/main/ETH/MEV资料.mdMEV 资料总结了一点 MEV 的资料，欢迎补充。有用的链接Artemis：https://www.paradigm.xyz/2023/05/artemisParadigm 出品的 MEV 框架，由 Rust 编写。 在 crates/strategies 文件目录下，有两个范例Uniswap V2-V3 套利策略Opensea - Sudo 套利策略cake_sniper：https://github.com/Supercycled/cake_sniper一个抢跑 MEV 的机器人。由 Python 编写，代码比较老旧，可以用于学习思路。rusty-sando：https://github.com/mouseless-eth/rusty-sando用 Huff 和 Artemis 编写的套利机器人，具有 Uniswap V2/V3 的三明治攻击机器人。awesome-MEV-resources：https://github.com/0xalpharush/awesome-M...]]></description>
            <content:encoded><![CDATA[<blockquote><p>原文链接：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://github.com/nishuzumi/blog/blob/main/ETH/MEV%E8%B5%84%E6%96%99.md">github.com/nishuzumi/blog/blob/main/ETH/MEV资料.md</a></p></blockquote><h1 id="h-mev" class="text-4xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">MEV 资料</h1><p>总结了一点 MEV 的资料，欢迎补充。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">有用的链接</h2><h3 id="h-artemishttpswwwparadigmxyz202305artemis" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">Artemis：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://www.paradigm.xyz/2023/05/artemis">https://www.paradigm.xyz/2023/05/artemis</a></h3><p>Paradigm 出品的 MEV 框架，由 Rust 编写。</p><p>在 crates/strategies 文件目录下，有两个范例</p><ul><li><p>Uniswap V2-V3 套利策略</p></li><li><p>Opensea - Sudo 套利策略</p></li></ul><h3 id="h-cakesniperhttpsgithubcomsupercycledcakesniper" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">cake_sniper：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Supercycled/cake_sniper">https://github.com/Supercycled/cake_sniper</a></h3><p>一个抢跑 MEV 的机器人。由 Python 编写，代码比较老旧，可以用于学习思路。</p><h3 id="h-rusty-sandohttpsgithubcommouseless-ethrusty-sando" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">rusty-sando：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/mouseless-eth/rusty-sando">https://github.com/mouseless-eth/rusty-sando</a></h3><p>用 Huff 和 Artemis 编写的套利机器人，具有 Uniswap V2/V3 的三明治攻击机器人。</p><h3 id="h-awesome-mev-resourceshttpsgithubcom0xalpharushawesome-mev-resources" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">awesome-MEV-resources：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/0xalpharush/awesome-MEV-resources">https://github.com/0xalpharush/awesome-MEV-resources</a></h3><p>超多超全的 MEV 资料。</p>]]></content:encoded>
            <author>boxchen@newsletter.paragraph.com (BoxChen)</author>
        </item>
        <item>
            <title><![CDATA[zk-SNARKs 简介]]></title>
            <link>https://paragraph.com/@boxchen/zk-snarks</link>
            <guid>83b7o0itIjLON01TeTWz</guid>
            <pubDate>Wed, 23 Aug 2023 10:48:22 GMT</pubDate>
            <description><![CDATA[本文开源仓库地址 本文翻译自 Consensys 翻译者：@BoxMrChen在这篇文章中，我们的目标是从实用的角度对 zk-SNARKs 进行概述。我们将把实际的数学问题视为一个黑箱，并试图围绕我们如何使用它们来发展一些直觉。我们还将展示一项关于 在以太坊中集成 zk-SNARKs 的最近工作的简单应用 。零知识证明零知识证明的目标是让验证者能够确信证明者掌握了一个满足某种关系的秘密参数，也就是所谓的见证人，而无需向验证者或任何其他人揭示这个见证人。 我们可以更具体地将其想象为一个程序，标记为 C ，接收两个输入： C(x, w)。输入 x 是公开输入，而 w 是秘密见证输入。程序的输出是布尔值，即 true 或 false 。然后，目标是给定特定的公开输入 x ，证明证明者知道一个秘密输入 w ，使得 C(x,w) == true 。 我们将专门讨论非交互式零知识证明。这意味着证明本身是一块可以在无需证明者任何交互的情况下进行验证的数据。示例程序假设 Bob 得到了某个值的哈希 H ，他希望有证据证明 Alice 知道哈希为 H 的值 s 。通常，Alice 会通过给 Bob...]]></description>
            <content:encoded><![CDATA[<blockquote><p>本文<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="">开源仓库地址</a></p><p>本文翻译自 <a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://consensys.net/blog/developers/introduction-to-zk-snarks/">Consensys</a></p><p>翻译者：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/home">@BoxMrChen</a></p></blockquote><p>在这篇文章中，我们的目标是从实用的角度对 zk-SNARKs 进行概述。我们将把实际的数学问题视为一个黑箱，并试图围绕我们如何使用它们来发展一些直觉。我们还将展示一项关于<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://blog.ethereum.org/2017/01/19/update-integrating-zcash-ethereum/"> 在以太坊中集成 zk-SNARKs 的最近工作的简单应用 </a>。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">零知识证明</h2><p>零知识证明的目标是让验证者能够确信证明者掌握了一个满足某种关系的秘密参数，也就是所谓的见证人，而无需向验证者或任何其他人揭示这个见证人。</p><p>我们可以更具体地将其想象为一个程序，标记为 C ，接收两个输入： <code>C(x, w)</code>。输入 x 是公开输入，而 w 是秘密见证输入。程序的输出是布尔值，即 true 或 false 。然后，目标是给定特定的公开输入 x ，证明证明者知道一个秘密输入 w ，使得 <code>C(x,w) == true</code> 。</p><p>我们将专门讨论非交互式零知识证明。这意味着证明本身是一块可以在无需证明者任何交互的情况下进行验证的数据。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">示例程序</h2><p>假设 Bob 得到了某个值的哈希 H ，他希望有证据证明 Alice 知道哈希为 H 的值 s 。通常，Alice 会通过给 Bob s 来证明这一点，然后 Bob 会计算哈希并检查它是否等于 H 。</p><p>然而，假设 Alice 不想向 Bob 透露 s 的值，而只是想证明她知道这个值。她可以使用 <code>zk-SNARK</code> 来实现这一点。</p><p>我们可以使用以下程序来描述 Alice 的情况，这里以 Javascript 函数的形式编写：</p><pre data-type="codeBlock" text="function C(x, w) {  return ( sha256(w) == x );}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">C</span>(<span class="hljs-params">x, w</span>) </span>{  <span class="hljs-keyword">return</span> ( <span class="hljs-built_in">sha256</span>(w) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> x );}
</code></pre><blockquote><p>这个程序不涉及任何 ZK 内容，他只是用于表示我们想要达到的效果是什么样。我们可以将其视为一个黑盒，我们将在下一节中讨论如何使用 zk-SNARKs 来构建这样的程序。</p></blockquote><p>换句话说：该程序接收一个公共哈希 x 和一个秘密值 w ，如果 w 的 SHA-256 哈希等于 x ，则返回 true 。</p><p>将 Alice 的问题通过函数 <code>C(x,w)</code>进行翻译，我们可以看到 Alice 需要创建一个证明，证明她拥有 s ，使得 <code>C(H, s) == true</code> ，而无需揭示 s 。这就是 zk-SNARKs 解决的一般问题。</p><h2 id="h-zk-snark" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">zk-SNARK 的定义</h2><p>一个 zk-SNARK 由三个算法 G, P, V 组成，定义如下：</p><p><em>密钥生成器 G</em> 由一个秘密参数 <code>lambda</code> 和一个程序 C 组成 ，并生成两个公开可用的密钥，一个 <em>proving key</em> pk（证明密钥） ，和一个 <em>verification key</em> vk（验证密钥） 。这些密钥是公开参数，只需要使用程序 C 生成一次。</p><p><em>证明者 P</em> 将证明密钥 pk 、公共输入 x 和见证 w 作为输入。该算法生成一个证明 <code>prf = P(pk, x, w)</code> ，证明者知道一个见证 w ，并且该见证满足程序的要求。</p><p><em>验证器 V</em> 计算 <code>V(vk, x, prf)</code> ，如果证明是正确的，它将返回 true ，否则返回 false 。因此，如果证明者知道一个满足 C(x,w) == true 的见证 w ，这个函数就会返回真。</p><p>请注意在生成器中使用的秘密参数 lambda 。这个参数有时使得在现实世界的应用中使用 zk-SNARKs 变得棘手。原因在于，任何知道这个参数的人都可以生成假的证明。具体来说，给定任何程序 C 和公开输入 x ，知道 lambda 的人可以生成一个证明 fake_prf ，使得 <code>V(vk, x, fake_prf)</code> 评估为 true ，而无需知道秘密 w 。</p><p>因此，实际运行生成器需要一个非常安全的过程，以确保没有人了解并保存参数。这就是 Zcash 团队进行<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://electriccoin.co/blog/the-design-of-the-ceremony/">极其复杂的仪式</a>以生成证明密钥和验证密钥的原因，同时确保“有毒废料” lambda 在过程中被销毁。</p><h2 id="h-zk-snark" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">针对示例程序的 zk-SNARK</h2><p>在实际操作中， Alice 和 Bob 如何使用 zk-SNARK，以便 Alice 证明她知道上述示例中的秘密值？ 首先，如上所述，我们将使用由以下函数定义的程序：</p><pre data-type="codeBlock" text="function C(x, w) {
  return sha256(w) == x;
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">C</span>(<span class="hljs-params">x, w</span>) </span>{
  <span class="hljs-keyword">return</span> <span class="hljs-built_in">sha256</span>(w) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> x;
}
</code></pre><p>首先，Bob 需要运行生成器 G，以创建证明密钥 pk 和验证密钥 vk。首先，随机生成 lambda，并将其作为输入：</p><pre data-type="codeBlock" text="(pk, vk) = G(C, lambda)
"><code>(pk, vk) = <span class="hljs-built_in">G</span>(C, lambda)
</code></pre><p>请小心处理参数 lambda，因为如果 Alice 知道 lambda 的值，她将能够创建假的证明。Bob 将与 Alice 分享 pk 和 vk。</p><p>Alice 现在将扮演证明者的角色。她需要证明她知道哈希值为已知哈希 H 的值 s。她运行证明算法 P，使用输入 pk、H 和 s 来生成证明 prf：</p><pre data-type="codeBlock" text="prf = P(pk, H, s)
"><code><span class="hljs-attr">prf</span> = P(pk, H, s)
</code></pre><p>接下来，Alice 将证明 prf 呈现给 Bob ，Bob 运行验证函数 <code>V(vk, H, prf)</code>。在这种情况下，由于 Alice 正确地知道了秘密 s，所以会返回真值。Bob 可以确信 Alice 知道这个秘密，但 Alice 并不需要向 Bob 透露这个秘密。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">可重用的证明和验证密钥</h2><p>在我们上述的例子中，如果 Bob 想向 Alice 证明他知道一个秘密，那么他就不能使用 zk-SNARK，因为 Alice 无法知道 Bob 是否保存了 lambda 参数。Bob 完全有可能伪造证据。</p><p>如果一个程序对许多人有用（比如 Zcash 的例子），一个独立于 Alice 和 Bob 的可信赖的独立团队可以运行生成器，创建证明密钥 pk 和验证密钥 vk，而且这样做没有人会了解到 lambda。</p><p>任何相信该团队没有作弊的人，都可以在未来的互动中使用这些密钥。</p><h2 id="h-zk-snarks" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">以太坊中的 zk-SNARKs</h2><p>开发者已经开始将 zk-SNARKs 集成到以太坊中。这看起来是什么样子呢？具体来说，你可以将验证算法的构建模块以预编译合约的形式添加到以太坊中。具体操作如下：在链下运行生成器，生成证明密钥和验证密钥。然后，任何证明者都可以使用证明密钥创建证明，这也是在链下完成的。然后，你可以在智能合约内部运行通用验证算法，使用证明、验证密钥和公开输入作为输入参数。然后，你可以使用验证算法的结果来触发其他链上活动。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">示例：保密交易</h2><p>以下是一个简单的例子，说明了 zk-SNARKs 如何帮助提高以太坊的隐私性。假设我们有一个简单的代币合约。通常，一个代币合约的核心是将地址映射到余额：</p><pre data-type="codeBlock" text="mapping (address =&gt; uint256) balances;
"><code><span class="hljs-keyword">mapping</span> (<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">uint256</span>) balances;
</code></pre><p>我们将保留相同的基本核心，只是将余额替换为余额的哈希值：</p><pre data-type="codeBlock" text="mapping (address =&gt; bytes32) balanceHashes;
"><code><span class="hljs-keyword">mapping</span> (<span class="hljs-keyword">address</span> <span class="hljs-operator">=</span><span class="hljs-operator">></span> <span class="hljs-keyword">bytes32</span>) balanceHashes;
</code></pre><p>我们不会隐藏交易的发送者或接收者。但我们会隐藏余额和发送的金额。这种属性有时被称为保密交易。</p><p>我们将使用两个 zk-SNARKs 来将代币从一个账户发送到另一个账户。发送者和接收者各创建一个证明。</p><p>通常在一个代币合约中，为了使大小值的交易有效，我们需要验证以下内容：</p><pre data-type="codeBlock" text="balances[fromAddress] &gt;= value
"><code>balances[fromAddress] <span class="hljs-operator">></span><span class="hljs-operator">=</span> value
</code></pre><p>我们的 zk-SNARKs 需要证明这一点是成立的，以及更新后的哈希值与更新后的余额相匹配。</p><p>主要的思想是，发送者将使用他们的 <em>初始余额</em> 和 <em>值</em> 作为私有数据。使用 <em>初始余额</em>、<em>结束余额</em> 和 <em>值的哈希</em> 作为公开数据。同样，接收者将使用 <em>初始余额</em> 和 <em>值</em> 作为私有数据。使用 <em>初始余额</em> 、 <em>结束余额</em> 和 <em>值的哈希</em> 作为公开数据。</p><p>以下是发送者的 zk-SNARK 程序，其中如前所述，x 代表公开数据，w 代表私有数据。</p><pre data-type="codeBlock" text="/**
 * @param x 公开数据
 * @param w 私有数据
 */
function senderFunction(x, w) {
  return (
    w.senderBalanceBefore &gt; w.value &amp;&amp; // 确保发送者有足够的余额
    sha256(w.value) == x.hashValue &amp;&amp; // 确保发送的值与公开的哈希值匹配
    sha256(w.senderBalanceBefore) == x.hashSenderBalanceBefore &amp;&amp; // 确保发送者的初始余额与公开的哈希值匹配
    sha256(w.senderBalanceBefore - w.value) == x.hashSenderBalanceAfter // 确保发送者的结束余额与公开的哈希值匹配
  );
}
"><code><span class="hljs-comment">/**
 * @param x 公开数据
 * @param w 私有数据
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">senderFunction</span>(<span class="hljs-params">x, w</span>) </span>{
  <span class="hljs-keyword">return</span> (
    w.senderBalanceBefore <span class="hljs-operator">></span> w.<span class="hljs-built_in">value</span> <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-comment">// 确保发送者有足够的余额</span>
    <span class="hljs-built_in">sha256</span>(w.<span class="hljs-built_in">value</span>) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> x.hashValue <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-comment">// 确保发送的值与公开的哈希值匹配</span>
    <span class="hljs-built_in">sha256</span>(w.senderBalanceBefore) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> x.hashSenderBalanceBefore <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> <span class="hljs-comment">// 确保发送者的初始余额与公开的哈希值匹配</span>
    <span class="hljs-built_in">sha256</span>(w.senderBalanceBefore <span class="hljs-operator">-</span> w.<span class="hljs-built_in">value</span>) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> x.hashSenderBalanceAfter <span class="hljs-comment">// 确保发送者的结束余额与公开的哈希值匹配</span>
  );
}
</code></pre><p>接受者的 zk-SNARK 程序如下：</p><pre data-type="codeBlock" text="/**
 * @param x 公开数据
 * @param w 私有数据
 */
function receiverFunction(x, w) {
  return (
    sha256(w.value) == x.hashValue &amp;&amp;
    sha256(w.receiverBalanceBefore) == x.hashReceiverBalanceBefore &amp;&amp;
    sha256(w.receiverBalanceBefore + w.value) == x.hashReceiverBalanceAfter
  );
}
"><code><span class="hljs-comment">/**
 * @param x 公开数据
 * @param w 私有数据
 */</span>
<span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">receiverFunction</span>(<span class="hljs-params">x, w</span>) </span>{
  <span class="hljs-keyword">return</span> (
    <span class="hljs-built_in">sha256</span>(w.<span class="hljs-built_in">value</span>) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> x.hashValue <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span>
    <span class="hljs-built_in">sha256</span>(w.receiverBalanceBefore) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> x.hashReceiverBalanceBefore <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span>
    <span class="hljs-built_in">sha256</span>(w.receiverBalanceBefore <span class="hljs-operator">+</span> w.<span class="hljs-built_in">value</span>) <span class="hljs-operator">=</span><span class="hljs-operator">=</span> x.hashReceiverBalanceAfter
  );
}
</code></pre><p>这些程序会检查发送余额是否大于正在发送的值，并检查所有哈希是否匹配。一组受信任的人员将为我们的 zk-SNARKs 生成证明和验证密钥。我们称它们为 <em>confTxSenderPk</em>、<em>confTxSenderVk</em>、<em>confTxReceiverPk</em> 和 <em>confTxReceiverVk</em>。 <em>confTxSenderPk</em> 和 <em>confTxReceiverPk</em> 将被用于生成证明，而 <em>confTxSenderVk</em> 和 <em>confTxReceiverVk</em> 将被用于验证证明。</p><p>在代币合约中使用 zk-SNARKs 可能会是这样的：</p><pre data-type="codeBlock" text="function transfer(address _to, bytes32 hashValue, bytes32 hashSenderBalanceAfter, bytes32 hashReceiverBalanceAfter, bytes zkProofSender, bytes zkProofReceiver) {
  bytes32 hashSenderBalanceBefore = balanceHashes[msg.sender];
  bytes32 hashReceiverBalanceBefore = balanceHashes[_to];

  bool senderProofIsCorrect = zksnarkverify(confTxSenderVk, [hashSenderBalanceBefore, hashSenderBalanceAfter, hashValue], zkProofSender);

bool receiverProofIsCorrect = zksnarkverify(confTxReceiverVk, [hashReceiverBalanceBefore, hashReceiverBalanceAfter, hashValue], zkProofReceiver);

  if(senderProofIsCorrect &amp;&amp; receiverProofIsCorrect) {
    balanceHashes[msg.sender] = hashSenderBalanceAfter;
    balanceHashes[_to] = hashReceiverBalanceAfter;
  }
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">transfer</span>(<span class="hljs-params"><span class="hljs-keyword">address</span> _to, <span class="hljs-keyword">bytes32</span> hashValue, <span class="hljs-keyword">bytes32</span> hashSenderBalanceAfter, <span class="hljs-keyword">bytes32</span> hashReceiverBalanceAfter, <span class="hljs-keyword">bytes</span> zkProofSender, <span class="hljs-keyword">bytes</span> zkProofReceiver</span>) </span>{
  <span class="hljs-keyword">bytes32</span> hashSenderBalanceBefore <span class="hljs-operator">=</span> balanceHashes[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>];
  <span class="hljs-keyword">bytes32</span> hashReceiverBalanceBefore <span class="hljs-operator">=</span> balanceHashes[_to];

  <span class="hljs-keyword">bool</span> senderProofIsCorrect <span class="hljs-operator">=</span> zksnarkverify(confTxSenderVk, [hashSenderBalanceBefore, hashSenderBalanceAfter, hashValue], zkProofSender);

<span class="hljs-keyword">bool</span> receiverProofIsCorrect <span class="hljs-operator">=</span> zksnarkverify(confTxReceiverVk, [hashReceiverBalanceBefore, hashReceiverBalanceAfter, hashValue], zkProofReceiver);

  <span class="hljs-keyword">if</span>(senderProofIsCorrect <span class="hljs-operator">&#x26;</span><span class="hljs-operator">&#x26;</span> receiverProofIsCorrect) {
    balanceHashes[<span class="hljs-built_in">msg</span>.<span class="hljs-built_in">sender</span>] <span class="hljs-operator">=</span> hashSenderBalanceAfter;
    balanceHashes[_to] <span class="hljs-operator">=</span> hashReceiverBalanceAfter;
  }
}
</code></pre><p>因此，区块链上唯一更新的只是余额的哈希值，而不是余额本身。然而，我们可以知道所有的余额都被正确地更新了，因为我们可以自己检查证明已经被验证过了。</p><p>通过上面的例子，我们也可以看到，在区块链上我们只进行了余额的 Hash 存储，而没有暴露真实的余额，这就对数据进行了保密，我们除了知道两者之间发生了交易，但是我们不知道具体交易金额，这就保证了交易的隐私性。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">详细信息</h2><p>上述的保密交易方案主要是为了给出一个实际的例子，说明我们如何在以太坊上使用 zk-SNARKs。要创建一个健全的保密交易方案，我们需要解决一些问题：</p><ul><li><p>用户需要在客户端跟踪他们的余额，如果你失去余额数据，那么你就失去了这个账户的控制权。余额或许可以用来自签名密钥的密钥加密并存储在链上。</p></li><li><p>余额需要使用 32 字节的数据并熵编码，以防止反向解析哈希以计算余额。</p></li><li><p>需要处理向未使用地址发送的边缘情况。</p></li><li><p>发件人需要与收件人进行交互才能发送。我们可能会有一个系统，发件人可以使用他们的证明来启动交易。然后，收件人可以在区块链上看到他们有一个“待处理的入账交易”，并可以完成它。</p></li></ul>]]></content:encoded>
            <author>boxchen@newsletter.paragraph.com (BoxChen)</author>
        </item>
        <item>
            <title><![CDATA[ChatGPT Plus注册全攻略]]></title>
            <link>https://paragraph.com/@boxchen/chatgpt-plus</link>
            <guid>cpKB8DnWgZwPZHXouEWa</guid>
            <pubDate>Sat, 11 Feb 2023 08:06:18 GMT</pubDate>
            <description><![CDATA[简要这篇文章适合拥有ChatGPT账号的朋友，并且身边缺少美国VISA或者万事达卡。 如果你没有ChatGPT账户，问题不大，你可以看我上一篇文章： https://mirror.xyz/boxchen.eth/9O9CSqyKDj4BKUIil7NC1Sa1LJM-3hsPqaeW_QjfFBc 同时，如果你想要使用，你还需要有一定的加密货币知识，不过没关系，如果你没有加密货币知识，你可以直接加入群寻求群主帮助。 当然，如果你有账号需求，代收验证码，或者需要帮助开通Plus，也可以加群直接联系群主。现在提供服务：成品号 - 一人一号，包括邮箱和账号密码都可以修改，是目前市面上唯一一个提供邮箱密码的。代收验证码 - 你如果借不到验证码也可以找我代充Plus会员和depay如果你有问题或者有需要，可以直接进去找群主或者客服。切回正题，事前准备内容：一个可用的ChatGPT账号一个日本地区的IP（当然也不限制，不过我自己用的是日本IP）一些USDT（如果没有，可以加群找人代充）一个虚拟信用卡，点此注册第一步 检测资格第一步自然是检测是否有Plus资格，不过从2月11日开始，Plus资...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">简要</h2><p>这篇文章适合拥有ChatGPT账号的朋友，并且身边缺少美国VISA或者万事达卡。</p><p>如果你没有ChatGPT账户，问题不大，你可以看我上一篇文章：</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/boxchen.eth/9O9CSqyKDj4BKUIil7NC1Sa1LJM-3hsPqaeW_QjfFBc">https://mirror.xyz/boxchen.eth/9O9CSqyKDj4BKUIil7NC1Sa1LJM-3hsPqaeW_QjfFBc</a></p><p>同时，如果你想要使用，你还需要有一定的加密货币知识，不过没关系，如果<strong>你没有加密货币知识</strong>，你可以直接加入群寻求群主帮助。</p><p><strong>当然，如果你有账号需求，代收验证码，或者需要帮助开通Plus，也可以加群直接联系群主。</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/cadf2214ce5dd6906b9d6578b8cace1bd62c2f79583dbe658f98cf67dcec9fd0.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>现在提供服务：</p><ul><li><p>成品号 - 一人一号，包括邮箱和账号密码都可以修改，是目前市面上唯一一个提供邮箱密码的。</p></li><li><p>代收验证码 - 你如果借不到验证码也可以找我</p></li><li><p>代充Plus会员和depay</p></li></ul><p>如果你有问题或者有需要，可以直接进去找群主或者客服。切回正题，事前准备内容：</p><ul><li><p>一个可用的ChatGPT账号</p></li><li><p>一个日本地区的IP（当然也不限制，不过我自己用的是日本IP）</p></li><li><p>一些USDT（如果没有，可以加群找人代充）</p></li><li><p>一个虚拟信用卡，<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://depay.depay.one/web-app/register-h5?invitCode=808322&amp;lang=zh-cn">点此注册</a></p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">第一步 检测资格</h2><p>第一步自然是检测是否有Plus资格，不过从2月11日开始，Plus资格已经不用排队。</p><p>检测方法很简单，先看左侧是否有升级按钮。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/56de29a33f343c06c38a1c899023e408c2013de2ed74fe7331f9708fda94b59c.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>如果有，点击。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8592690562976a375deb5c282079179412a8d13ace18ae78aba77689f293859b.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>然后点击Upgrade Plan。如果出现下面这个页面，那么就证明你有资格。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5d0844a78d63eb365b9d1eee44abd0272921ec695a594c5cfb428c85d4e406ce.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-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">第二部 创建虚拟信用卡</h2><p>其实，如果你人在海外其实就没必要看这篇文章，所以我默认你并没有海外信用卡。</p><p>理论上来说，VISA，JCB，银联卡都可以，但是当你注册的时候你会发现原来都不行，支付端用的是Link进行支付，他们会校验发卡地区，如果是中国地区，包括香港省，那么都会进行拒绝。所以很多虚拟信用卡都无法使用</p><p>注意，以下两个方案都需要使用虚拟货币，USDT或者USDC。所以需要注册一个虚拟货币交易所。可以通过下面两个链接进行注册。</p><p>币安：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://accounts.binance.com/register?ref=GGVAE39W">accounts.binance.com/register?ref=GGVAE39W</a></p><p>OKX：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="">cnouyi.cards/join/2251590</a></p><h3 id="h-0-onekey-visa-card" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">方案0 OneKey Visa Card</h3><p>打开<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://card.onekey.so/?i=6519PX">OneKey网站</a> ：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="http://card.onekey.so/?i=6519PX">card.onekey.so/?i=6519PX</a> 按照流程注册即可，Onekey是大公司，注册流程非常简单。优先推荐。</p><h3 id="h-1-depay" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">方案1 Depay</h3><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://depay.depay.one/web-app/register-h5?invitCode=808322&amp;lang=zh-cn">Depay</a> 信用卡，打开<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://depay.depay.one/web-app/register-h5?invitCode=808322&amp;lang=zh-cn">depay</a>的网站。完成正常注册即可，注册成功后需要下载他们的APP。</p><p>IOS需要国外store账号，安卓可以直接下载APK。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5866fc0c5256f4013123c7b7c3df0c9a1b9d9fc2a81c15cb5ea7232944e4bc06.jpg" 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>可以看到有两种卡的选择，这取决于你自己了，他们之间开的应该没有差异（不过我用的是KYC版的）。</p><p>不过，你现在肯定是开不了的，因为你要先充值USDT才行（而且目前只支持USDT-TRC20）。点击钱包，然后点击USDT。进入下面这个页面，充值即可，我个人推荐用多少冲多少。我这里先冲个40U作为测试。</p><p><strong>注意⚠️：如果你不是Crypto玩家，或者你不知道如何购买USDT，没关系，你可以在上面的群聊里面找到群主，向他咨询代充问题。</strong></p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/4e98643335517ac3557fe7f4f04474e1ecbae32c2f1d038a819c5a7edc56ce51.jpg" 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>经过一系列的操作，卡已经办好了。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/6f5063ff2ebbdefe8e1cbfaa39a69e1c88de919ffe76e13abdced11187ab2421.jpg" 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-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">第三部 开通</h2><p>此时我们有了信用卡，就可以去开通啦。不过开通之前，我推荐：</p><ul><li><p>用一个人比较少的IP地址（这个就看你的代理有多少人在开通这玩意了。。）</p></li><li><p>日本和美国IP有限推荐</p></li><li><p>账单地址随便填，这个无所谓（如果是Nobepay，则需要填写对应的地址）。</p></li></ul><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7c105fdfafca702f6763c61f42685f9635fb52907911b5fad12a9b20dc2ae037.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>🎉🎉🎉🎉 铛铛铛 你现在就已经是高贵的ChatGPT Plus用户了。</p><p>你可以享受到超快的速度，极速的反应，和双模型回答。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/da529560a966dc17cb08344e8fab4ff635cd5a24b60f5f80ccb38699c55eb972.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-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">第四部 取消自动订阅（可选）</h2><p>当然，最后我们肯定是要取消自动订阅的，不然每个月20美金，要是不用了还框框扣，岂不是亏大发？</p><p>点击：My account</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/612f7dab75307380e458a9d42cf9d0d5d488b0677a9863bfc4370b9fe660ecfd.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>点击 Manage my subscription</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2e631c133a55364125ffc0a62b08dcd8c8a05a33c69e46a482a7f4a46338885d.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>然后点击Cancel plan就行了，当然取消后，你这个月还是正常使用的，如果以后你还想继续使用，重新订阅即可。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/467b220867d2e79055736259d3e470d52c8ff6066da5d1529cda39228cbbd26d.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-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结尾宣言</h2><p>本文作者 Box，推特@BoxMrChen。</p><p>如有问题，可以加群交流。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/7f65d7f7bd3dc18e5955da3adae3d78efc599e9f257445b4265f50737d78ad50.jpg" 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>]]></content:encoded>
            <author>boxchen@newsletter.paragraph.com (BoxChen)</author>
        </item>
        <item>
            <title><![CDATA[注册ChatGPT全攻略（账号购买）]]></title>
            <link>https://paragraph.com/@boxchen/chatgpt</link>
            <guid>x3SV20atORqgIMlR2Iih</guid>
            <pubDate>Fri, 02 Dec 2022 14:26:49 GMT</pubDate>
            <description><![CDATA[最近ChatGPT很火，但是有人注册会经常出现不服务当前地区问题，现在手把手教你解决。加个群8如果你有账号需求，代收验证码，或者需要帮助开通Plus，也可以加群直接联系群主。现在提供服务：成品号 - 一人一号，包括邮箱和账号密码都可以修改，是目前市面上唯一一个提供邮箱密码的。代收验证码 - 你如果借不到验证码也可以找我代充Plus会员和depay如果你有问题或者有需要，可以直接进去找群主或者客服。 ChatGPT Plus教程 Plus的申请教程也来了，快来成为高贵的Plus用户。 https://mirror.xyz/boxchen.eth/fi-WPM78UeTRwo_r3rNZesrZ_hfQfFltkOT2kb9k6Jk准备代理，要求是，韩国，日本，印度，新加坡，美国这几个地址。其他的不太清楚，反正香港肯定不行。准备一个国外手机号，如果没有用接码平台也行，有些接码平台是无法接的，所以我找了一个，sms-activate.org准备一个浏览器开始第一步 准备接码打开接码平台 sms-activate.org，注册一个账号注册充值，这里单位是卢布接码OpenAi的一次费用是大...]]></description>
            <content:encoded><![CDATA[<p>最近ChatGPT很火，但是有人注册会经常出现不服务当前地区问题，现在手把手教你解决。</p><h2 id="h-8" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">加个群8</h2><p>如果你有账号需求，代收验证码，或者需要帮助开通Plus，也可以加群直接联系群主。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/efed2736f38cb54be57f4d207dde2291301e49769878268d7c6bb16ab3438eb5.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>现在提供服务：</p><ul><li><p>成品号 - 一人一号，包括邮箱和账号密码都可以修改，是目前市面上唯一一个提供邮箱密码的。</p></li><li><p>代收验证码 - 你如果借不到验证码也可以找我</p></li><li><p>代充Plus会员和depay</p></li></ul><p>如果你有问题或者有需要，可以直接进去找群主或者客服。</p><p>ChatGPT Plus教程</p><p>Plus的申请教程也来了，快来成为高贵的Plus用户。</p><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://mirror.xyz/boxchen.eth/fi-WPM78UeTRwo_r3rNZesrZ_hfQfFltkOT2kb9k6Jk">https://mirror.xyz/boxchen.eth/fi-WPM78UeTRwo_r3rNZesrZ_hfQfFltkOT2kb9k6Jk</a></p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">准备</h2><ol><li><p>代理，要求是，韩国，日本，印度，新加坡，美国这几个地址。其他的不太清楚，反正香港肯定不行。</p></li><li><p>准备一个国外手机号，如果没有用接码平台也行，有些接码平台是无法接的，所以我找了一个，<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://sms-activate.org/?ref=2684538">sms-activate.org</a></p></li><li><p>准备一个浏览器</p></li></ol><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">开始</h2><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://sms-activate.org/?ref=2684538">sms-activate.org</a>，注册一个账号</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/2fce4c726bfb4fe793a9b435dc1ddfa845efee888d01f7d189146365fecefa64.png" alt="注册" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">注册</figcaption></figure><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d9e65118ec0415d7ac78d35acfb0fda779b88f382ea36e8f7c8fa0656932a22c.png" alt="充值，这里单位是卢布" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">充值，这里单位是卢布</figcaption></figure><p>接码OpenAi的一次费用是大概11卢布，人民币来看差不多是1块钱，不过只能充美金，就先充个1美金吧。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/ffb910742fdd27a8fdf9e6cd34bc5bd46853d1579df1d32c204ecd292b3173c5.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>充值完成需要等一会，就先放着，直接进行下一步。</p><h3 id="h-openai" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">第二部 注册OpenAI账号</h3><p>首先是打开<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://beta.openai.com/signup">ChatGPT的账户注册页面</a>。谷歌注册或者邮箱注册都可以，无所谓，这里用邮箱注册作为例子。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/26a6872147066277148cba609ca3f47118c7a8df1b7a2d6fc5ef122babf512a3.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>用邮箱注册后有个验证邮件，进去邮箱，点开链接。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/102ce38d413b022596d7bcc2f7eeeaf621ef0148ea136be26a12383fd4cf0cbc.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>现在开始一步步走就行了。</p><p>当然，有一些人会在这里遇到一个问题，会出现提示说不能在当前国家服务</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/29e2d3f6617a8b84290d13674320a18d8021a0ea12fdbf1749b7796102290d9e.png" alt="不用怕，我写这文章就是为了解决这个问题" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">不用怕，我写这文章就是为了解决这个问题</figcaption></figure><p>来，给你一招。一般你出现这种问题，就是因为你的代理没有全局，或者位置不对。香港的代理是100%无法通过的。</p><p>但是又有个非常神奇的问题，只要你出现了这个提示，那么你接下来怎么切换代理，都是没用的。现在教你一招解决。</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">解决地区问题 - 方法一</h4><p>首先，你要把你的代理切换到不是香港的地区，我这里选韩国。</p><p>然后，先复制下面这段代码</p><pre data-type="codeBlock" text="window.localStorage.removeItem(Object.keys(window.localStorage).find(i=&gt;i.startsWith(&apos;@@auth0spajs&apos;)))
"><code>window.localStorage.removeItem(Object.keys(window.localStorage).find(i<span class="hljs-operator">=</span><span class="hljs-operator">></span>i.startsWith(<span class="hljs-string">'@@auth0spajs'</span>)))
</code></pre><p>接着在地址栏里输入</p><pre data-type="codeBlock" text="javascript:
"><code><span class="hljs-section">javascript:</span>
</code></pre><p>注意，这里一定要输入，因为你复制的话是粘贴不了的。</p><p>然后再粘贴我们第一段复制的内容</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d02683accba8b597fae377ea7a57aaadd8adeb122e98c0754cd0cb16a22571c3.png" alt="最后结果是这样" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">最后结果是这样</figcaption></figure><p>然后按下回车键，接着刷新页面，如果你的代理没问题，就可以正常看到注册页面了。</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">解决地区问题 - 方法二</h4><p>开启无痕模式/隐身模式，重新尝试即可，记得在尝试前，可以打开ipip.net查看一下自己ip，如果还是国内，请检测代理问题。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">填写手机号</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5a5b7a84922f1d802cd8961945cd07b782811bb450d8c35160f00d7ff505fc37.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>这里我们选择印度，然后到我们的接码网站上去。在左侧搜索OpenAi，然后点击印度。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d967e07912d65b106c2d4acaf576831791acbe9828fdf5323d117eff0ac6a619.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>点击小黄车。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/68a7d5b31c35ff3f99ca7d97f87418c653336b20d6cf422f060ad6833d9418fb.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>然后我们复制这个号码，粘贴过去。然后我们点击发送验证码就完事了。</p><p>等一会网站会提示验证码，我们复制粘贴。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b2a537349ea20fddb825e2b75e1fd05c8cf5be549c2bb1f94fd1d57857d5ed55.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>这样就成功了，随便点一个进去完事。</p><h3 id="h-chatgpt" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">使用ChatGPT</h3><p>注册完后，我们去<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://chat.openai.com/auth/login">ChatGPT网站</a>去登陆。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/b67642ab381de46a091d41c1d9cca3e9fbc4b1e34fa5826443a626bd138b1835.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>在下面这个地方就可以开始你的奇思妙想了。</p><p>本位作者:</p><p>Box，推特@BoxMrChen。有事私聊</p><div data-type="embedly" src="https://twitter.com/BoxMrChen" data="{&quot;provider_url&quot;:&quot;https://twitter.com&quot;,&quot;version&quot;:&quot;1.0&quot;,&quot;title&quot;:&quot;JavaScript is not available.&quot;,&quot;url&quot;:&quot;https://twitter.com/BoxMrChen&quot;,&quot;html&quot;:&quot;&lt;a class=\&quot;twitter-timeline\&quot; href=\&quot;https://twitter.com/BoxMrChen?ref_src=twsrc%5Etfw\&quot;&gt;Tweets by BoxMrChen&lt;/a&gt;\n&lt;script async src=\&quot;https://platform.twitter.com/widgets.js\&quot; charset=\&quot;utf-8\&quot;&gt;&lt;/script&gt;&quot;,&quot;provider_name&quot;:&quot;Twitter&quot;,&quot;cache_age&quot;:3153600000,&quot;type&quot;:&quot;rich&quot;}" format="iframe"></div><div data-type="subscribeButton" class="center-contents"><a class="email-subscribe-button" href="null">Subscribe</a></div><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">常见问题</h2><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">问题一</h3><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/956cd76bea9a50d279638ace77497c8f0c671447bfb504c68a15c4630c29a7ec.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>解答：切换代理，刷新页面。</p>]]></content:encoded>
            <author>boxchen@newsletter.paragraph.com (BoxChen)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/7beb29eab17a7732477e9e9a88620370c0615e44e6804746b45143ccba6a0d1c.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Web3企业级工程-初级篇：4. 部署完整版UniswapV3]]></title>
            <link>https://paragraph.com/@boxchen/web3-4-uniswapv3</link>
            <guid>aJxUSV0KAatm8X69Q0Uz</guid>
            <pubDate>Sat, 08 Oct 2022 07:30:07 GMT</pubDate>
            <description><![CDATA[前言文接上回，我们已经部署了Uniswap的核心合约，接下来我们将部署完整版的UniswapV3。完整版的UniswapV3包含了以下合约：UniswapV3Factory (Pool生成合约)UniswapV3SwapRouter (包装类交易合约)NonfungiblePositionManager (NFT管理合约)NonfungibleTokenPositionDescriptor (NFT仓位描述合约)NFTDescriptor (NFT描述合约)WETH9 (WrapperETH)我们上一章节已经部署完成了前两个合约，那么我们这一章节就需要开始部署后续合约。其实部署方法都是一样的。只不过有一点点需要注意的地方是，NonfungibleTokenPositionDescriptor需要进行Library链接，将NFTDescriptor部署后作为Library链接到NonfungibleTokenPositionDescriptor中。接下来我们会继续完善我们的部署脚本，将这些合约都部署出来。完善部署脚本我们继续完善我们的部署脚本，将上一章节中部署的合约和这一章节中需要...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">前言</h2><p>文接上回，我们已经部署了Uniswap的核心合约，接下来我们将部署完整版的UniswapV3。完整版的UniswapV3包含了以下合约：</p><ul><li><p>UniswapV3Factory (Pool生成合约)</p></li><li><p>UniswapV3SwapRouter (包装类交易合约)</p></li><li><p>NonfungiblePositionManager (NFT管理合约)</p></li><li><p>NonfungibleTokenPositionDescriptor (NFT仓位描述合约)</p></li><li><p>NFTDescriptor (NFT描述合约)</p></li><li><p>WETH9 (WrapperETH)</p></li></ul><p>我们上一章节已经部署完成了前两个合约，那么我们这一章节就需要开始部署后续合约。其实部署方法都是一样的。只不过有一点点需要注意的地方是，<code>NonfungibleTokenPositionDescriptor</code>需要进行Library链接，将<code>NFTDescriptor</code>部署后作为Library链接到<code>NonfungibleTokenPositionDescriptor</code>中。接下来我们会继续完善我们的部署脚本，将这些合约都部署出来。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">完善部署脚本</h2><p>我们继续完善我们的部署脚本，将上一章节中部署的合约和这一章节中需要部署的合约都部署出来。因为部署方式和上一章节中的部署方式一样，所以我们只需要在<code>00_deploy_univ3.ts</code>中添加一些代码即可。 不过我们还需要添加一个WETH9的合约。我们上一章节中，在填写WETH9的位置直接使用了0地址代替，现在可不行了。所以我们先打开网址<code>https://github.com/gnosis/canonical-weth/blob/master/contracts/WETH9.sol</code>，复制里面的代码，然后在contracts文件夹下新建一个<code>WETH9.sol</code>文件，将代码粘贴进去。然后我们运行一下<code>yarn hardhat compile</code>，将合约编译一下。可以不出所料的出现一个错误。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/38f37a1b5b4a2d5f4abb9c60080dd47898e8ab5b6a027837454fa7c14ed58621.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>这是因为我们的编译器版本不对，因为WETH9的合约是用<code>&gt;=0.4.22 &lt;0.6</code>的版本进行编译，而我们默认的是<code>^0.8.0</code>的版本，所以我们需要修改编译器的版本。不过得益于Hardhat的强大功能，我们可以设定多个版本的编译器，这样我们就能在多个版本的编译器下编译我们的合约了。 我们打开<code>hardhat.config.ts</code>，在<code>solidity</code>的配置项中添加一个<code>compilers</code>的配置项，将其设置为一个数组，数组中包含我们需要的编译器版本。</p><pre data-type="codeBlock" text="const config: HardhatUserConfig = {
  solidity: {
    compilers: [
      {
        version: &apos;0.8.0&apos;,
      },
      {
        version: &apos;0.4.22&apos;,
      },
    ],
  },
};
"><code>const config: HardhatUserConfig = {
  solidity: {
    compilers: [
      {
        version: <span class="hljs-string">'0.8.0'</span>,
      },
      {
        version: <span class="hljs-string">'0.4.22'</span>,
      },
    ],
  },
};
</code></pre><p>修改完成后，我们再次编译。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/455c4309b95509b2d0f4eea97e56553dfc42d41a77effe08be82a75f5f50650f.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-deploy" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">修改deploy文件</h2><p>我们打开<code>00_deploy_univ3.ts</code>，在<code>main</code>函数中添加一些代码。</p><pre data-type="codeBlock" text="import { utils } from &apos;ethers&apos;
import { DeployFunction } from &quot;hardhat-deploy/types&quot;;
import {
    abi as FACTORY_ABI,
    bytecode as FACTORY_BYTECODE,
} from &apos;@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json&apos;

import {
    abi as SWAP_ROUTER_ABI,
    bytecode as SWAP_ROUTER_BYTECODE,
} from &apos;@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json&apos;
import { HardhatRuntimeEnvironment } from &quot;hardhat/types&quot;;

import {
    abi as NFTDescriptor_ABI, bytecode as NFTDescriptor_BYTECODE
} from &apos;@uniswap/v3-periphery/artifacts/contracts/libraries/NFTDescriptor.sol/NFTDescriptor.json&apos;

import {
    abi as NFTPositionManager_ABI, bytecode as NFTPositionManager_BYTECODE
} from &apos;@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json&apos;

import {
    abi as NFTPositionDescriptor_ABI, bytecode as NFTPositionDescriptor_BYTECODE
} from &apos;@uniswap/v3-periphery/artifacts/contracts/NonfungibleTokenPositionDescriptor.sol/NonfungibleTokenPositionDescriptor.json&apos;


const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
    const { deployments, ethers } = hre
    const [deployer] = await ethers.getSigners()
    const factory = await deployments.deploy(&quot;UniV3Factory&quot;, {
        from: deployer.address,
        contract: {
            bytecode: FACTORY_BYTECODE,
            abi: FACTORY_ABI
        },
    })

    const WETH9 = await deployments.deploy(&quot;WETH9&quot;, {
        from: deployer.address
    })

    await deployments.deploy(&quot;UniV3SwapRouter&quot;, {
        from: deployer.address,
        contract: {
            abi: SWAP_ROUTER_ABI,
            bytecode: SWAP_ROUTER_BYTECODE
        },
        args: [factory.address, WETH9.address]
    })

    const NFTDescriptorlibrary = await deployments.deploy(&apos;NFTDescriptorLibrary&apos;, {
        from: deployer.address,
        contract: {
            abi: NFTDescriptor_ABI,
            bytecode: NFTDescriptor_BYTECODE
        }
    })

    const linkedBytecode = linkLibrary(NFTPositionDescriptor_BYTECODE,
        {
            [&apos;contracts/libraries/NFTDescriptor.sol:NFTDescriptor&apos;]: NFTDescriptorlibrary.address
        }
    )

    const positionDescriptor = await deployments.deploy(&apos;NFTPositionDescriptor&apos;, {
        from: deployer.address,
        contract: {
            abi: NFTPositionDescriptor_ABI,
            bytecode: linkedBytecode
        },
        args: [
            WETH9.address,
            // &apos;ETH&apos; as a bytes32 string
            &apos;0x4554480000000000000000000000000000000000000000000000000000000000&apos;
        ]
    })

    await deployments.deploy(&apos;NFTPositionManager&apos;, {
        from: deployer.address,
        contract: {
            abi: NFTPositionManager_ABI,
            bytecode: NFTPositionManager_BYTECODE
        },
        args: [factory.address, WETH9.address, positionDescriptor.address]
    })
}

function linkLibrary(bytecode: string, libraries: {
    [name: string]: string
} = {}): string {
    let linkedBytecode = bytecode
    for (const [name, address] of Object.entries(libraries)) {
        const placeholder = `__\$${utils.solidityKeccak256([&apos;string&apos;], [name]).slice(2, 36)}\$__`
        const formattedAddress = utils.getAddress(address).toLowerCase().replace(&apos;0x&apos;, &apos;&apos;)
        if (linkedBytecode.indexOf(placeholder) === -1) {
            throw new Error(`Unable to find placeholder for library ${name}`)
        }
        while (linkedBytecode.indexOf(placeholder) !== -1) {
            linkedBytecode = linkedBytecode.replace(placeholder, formattedAddress)
        }
    }
    return linkedBytecode
}

export default func;
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">utils</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'ethers'</span>
<span class="hljs-title"><span class="hljs-keyword">import</span></span> { <span class="hljs-title">DeployFunction</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"hardhat-deploy/types"</span>;
<span class="hljs-keyword">import</span> {
    <span class="hljs-title"><span class="hljs-built_in">abi</span></span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">FACTORY_ABI</span>,
    <span class="hljs-title">bytecode</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">FACTORY_BYTECODE</span>,
} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json'</span>

<span class="hljs-title"><span class="hljs-keyword">import</span></span> {
    <span class="hljs-title"><span class="hljs-built_in">abi</span></span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">SWAP_ROUTER_ABI</span>,
    <span class="hljs-title">bytecode</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">SWAP_ROUTER_BYTECODE</span>,
} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json'</span>
<span class="hljs-title"><span class="hljs-keyword">import</span></span> { <span class="hljs-title">HardhatRuntimeEnvironment</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"hardhat/types"</span>;

<span class="hljs-keyword">import</span> {
    <span class="hljs-title"><span class="hljs-built_in">abi</span></span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">NFTDescriptor_ABI</span>, <span class="hljs-title">bytecode</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">NFTDescriptor_BYTECODE</span>
} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@uniswap/v3-periphery/artifacts/contracts/libraries/NFTDescriptor.sol/NFTDescriptor.json'</span>

<span class="hljs-title"><span class="hljs-keyword">import</span></span> {
    <span class="hljs-title"><span class="hljs-built_in">abi</span></span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">NFTPositionManager_ABI</span>, <span class="hljs-title">bytecode</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">NFTPositionManager_BYTECODE</span>
} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@uniswap/v3-periphery/artifacts/contracts/NonfungiblePositionManager.sol/NonfungiblePositionManager.json'</span>

<span class="hljs-title"><span class="hljs-keyword">import</span></span> {
    <span class="hljs-title"><span class="hljs-built_in">abi</span></span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">NFTPositionDescriptor_ABI</span>, <span class="hljs-title">bytecode</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">NFTPositionDescriptor_BYTECODE</span>
} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@uniswap/v3-periphery/artifacts/contracts/NonfungibleTokenPositionDescriptor.sol/NonfungibleTokenPositionDescriptor.json'</span>


<span class="hljs-title">const</span> <span class="hljs-title">func</span>: <span class="hljs-title">DeployFunction</span> <span class="hljs-operator">=</span> <span class="hljs-title">async</span> <span class="hljs-title"><span class="hljs-keyword">function</span></span> (<span class="hljs-title">hre</span>: <span class="hljs-title">HardhatRuntimeEnvironment</span>) {
    <span class="hljs-title">const</span> { <span class="hljs-title">deployments</span>, <span class="hljs-title">ethers</span> } <span class="hljs-operator">=</span> <span class="hljs-title">hre</span>
    <span class="hljs-title">const</span> [<span class="hljs-title">deployer</span>] <span class="hljs-operator">=</span> <span class="hljs-title">await</span> <span class="hljs-title">ethers</span>.<span class="hljs-title">getSigners</span>()
    <span class="hljs-title">const</span> <span class="hljs-title">factory</span> <span class="hljs-operator">=</span> <span class="hljs-title">await</span> <span class="hljs-title">deployments</span>.<span class="hljs-title">deploy</span>(<span class="hljs-string">"UniV3Factory"</span>, {
        <span class="hljs-title"><span class="hljs-keyword">from</span></span>: <span class="hljs-title">deployer</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>,
        <span class="hljs-title"><span class="hljs-keyword">contract</span></span>: {
            <span class="hljs-title">bytecode</span>: <span class="hljs-title">FACTORY_BYTECODE</span>,
            <span class="hljs-title"><span class="hljs-built_in">abi</span></span>: <span class="hljs-title">FACTORY_ABI</span>
        },
    })

    <span class="hljs-title">const</span> <span class="hljs-title">WETH9</span> <span class="hljs-operator">=</span> <span class="hljs-title">await</span> <span class="hljs-title">deployments</span>.<span class="hljs-title">deploy</span>(<span class="hljs-string">"WETH9"</span>, {
        <span class="hljs-title"><span class="hljs-keyword">from</span></span>: <span class="hljs-title">deployer</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>
    })

    <span class="hljs-title">await</span> <span class="hljs-title">deployments</span>.<span class="hljs-title">deploy</span>(<span class="hljs-string">"UniV3SwapRouter"</span>, {
        <span class="hljs-title"><span class="hljs-keyword">from</span></span>: <span class="hljs-title">deployer</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>,
        <span class="hljs-title"><span class="hljs-keyword">contract</span></span>: {
            <span class="hljs-title"><span class="hljs-built_in">abi</span></span>: <span class="hljs-title">SWAP_ROUTER_ABI</span>,
            <span class="hljs-title">bytecode</span>: <span class="hljs-title">SWAP_ROUTER_BYTECODE</span>
        },
        <span class="hljs-title">args</span>: [<span class="hljs-title">factory</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>, <span class="hljs-title">WETH9</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>]
    })

    <span class="hljs-title">const</span> <span class="hljs-title">NFTDescriptorlibrary</span> <span class="hljs-operator">=</span> <span class="hljs-title">await</span> <span class="hljs-title">deployments</span>.<span class="hljs-title">deploy</span>(<span class="hljs-string">'NFTDescriptorLibrary'</span>, {
        <span class="hljs-title"><span class="hljs-keyword">from</span></span>: <span class="hljs-title">deployer</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>,
        <span class="hljs-title"><span class="hljs-keyword">contract</span></span>: {
            <span class="hljs-title"><span class="hljs-built_in">abi</span></span>: <span class="hljs-title">NFTDescriptor_ABI</span>,
            <span class="hljs-title">bytecode</span>: <span class="hljs-title">NFTDescriptor_BYTECODE</span>
        }
    })

    <span class="hljs-title">const</span> <span class="hljs-title">linkedBytecode</span> <span class="hljs-operator">=</span> <span class="hljs-title">linkLibrary</span>(<span class="hljs-title">NFTPositionDescriptor_BYTECODE</span>,
        {
            [<span class="hljs-string">'contracts/libraries/NFTDescriptor.sol:NFTDescriptor'</span>]: <span class="hljs-title">NFTDescriptorlibrary</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>
        }
    )

    <span class="hljs-title">const</span> <span class="hljs-title">positionDescriptor</span> <span class="hljs-operator">=</span> <span class="hljs-title">await</span> <span class="hljs-title">deployments</span>.<span class="hljs-title">deploy</span>(<span class="hljs-string">'NFTPositionDescriptor'</span>, {
        <span class="hljs-title"><span class="hljs-keyword">from</span></span>: <span class="hljs-title">deployer</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>,
        <span class="hljs-title"><span class="hljs-keyword">contract</span></span>: {
            <span class="hljs-title"><span class="hljs-built_in">abi</span></span>: <span class="hljs-title">NFTPositionDescriptor_ABI</span>,
            <span class="hljs-title">bytecode</span>: <span class="hljs-title">linkedBytecode</span>
        },
        <span class="hljs-title">args</span>: [
            <span class="hljs-title">WETH9</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>,
            <span class="hljs-comment">// 'ETH' as a bytes32 string</span>
            <span class="hljs-string">'0x4554480000000000000000000000000000000000000000000000000000000000'</span>
        ]
    })

    <span class="hljs-title">await</span> <span class="hljs-title">deployments</span>.<span class="hljs-title">deploy</span>(<span class="hljs-string">'NFTPositionManager'</span>, {
        <span class="hljs-title"><span class="hljs-keyword">from</span></span>: <span class="hljs-title">deployer</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>,
        <span class="hljs-title"><span class="hljs-keyword">contract</span></span>: {
            <span class="hljs-title"><span class="hljs-built_in">abi</span></span>: <span class="hljs-title">NFTPositionManager_ABI</span>,
            <span class="hljs-title">bytecode</span>: <span class="hljs-title">NFTPositionManager_BYTECODE</span>
        },
        <span class="hljs-title">args</span>: [<span class="hljs-title">factory</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>, <span class="hljs-title">WETH9</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>, <span class="hljs-title">positionDescriptor</span>.<span class="hljs-title"><span class="hljs-keyword">address</span></span>]
    })
}

<span class="hljs-title"><span class="hljs-keyword">function</span></span> <span class="hljs-title">linkLibrary</span>(<span class="hljs-title">bytecode</span>: <span class="hljs-title"><span class="hljs-keyword">string</span></span>, <span class="hljs-title">libraries</span>: {
    [<span class="hljs-title">name</span>: <span class="hljs-title"><span class="hljs-keyword">string</span></span>]: <span class="hljs-title"><span class="hljs-keyword">string</span></span>
} <span class="hljs-operator">=</span> {}): <span class="hljs-title"><span class="hljs-keyword">string</span></span> {
    <span class="hljs-title">let</span> <span class="hljs-title">linkedBytecode</span> <span class="hljs-operator">=</span> <span class="hljs-title">bytecode</span>
    <span class="hljs-title"><span class="hljs-keyword">for</span></span> (<span class="hljs-title">const</span> [<span class="hljs-title">name</span>, <span class="hljs-title"><span class="hljs-keyword">address</span></span>] <span class="hljs-title">of</span> <span class="hljs-title">Object</span>.<span class="hljs-title">entries</span>(<span class="hljs-title">libraries</span>)) {
        <span class="hljs-title">const</span> <span class="hljs-title">placeholder</span> <span class="hljs-operator">=</span> `<span class="hljs-title">__</span>\<span class="hljs-title">$$</span>{<span class="hljs-title">utils</span>.<span class="hljs-title">solidityKeccak256</span>([<span class="hljs-string">'string'</span>], [<span class="hljs-title">name</span>]).<span class="hljs-title">slice</span>(2, 36)}\<span class="hljs-title">$__</span>`
        <span class="hljs-title">const</span> <span class="hljs-title">formattedAddress</span> <span class="hljs-operator">=</span> <span class="hljs-title">utils</span>.<span class="hljs-title">getAddress</span>(<span class="hljs-title"><span class="hljs-keyword">address</span></span>).<span class="hljs-title">toLowerCase</span>().<span class="hljs-title">replace</span>(<span class="hljs-string">'0x'</span>, <span class="hljs-string">''</span>)
        <span class="hljs-title"><span class="hljs-keyword">if</span></span> (<span class="hljs-title">linkedBytecode</span>.<span class="hljs-title">indexOf</span>(<span class="hljs-title">placeholder</span>) <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-operator">-</span>1) {
            <span class="hljs-title"><span class="hljs-keyword">throw</span></span> <span class="hljs-title"><span class="hljs-keyword">new</span></span> <span class="hljs-title"><span class="hljs-built_in">Error</span></span>(`<span class="hljs-title">Unable</span> <span class="hljs-title">to</span> <span class="hljs-title">find</span> <span class="hljs-title">placeholder</span> <span class="hljs-title"><span class="hljs-keyword">for</span></span> <span class="hljs-title"><span class="hljs-keyword">library</span></span> <span class="hljs-title">$</span>{<span class="hljs-title">name</span>}`)
        }
        <span class="hljs-title"><span class="hljs-keyword">while</span></span> (<span class="hljs-title">linkedBytecode</span>.<span class="hljs-title">indexOf</span>(<span class="hljs-title">placeholder</span>) <span class="hljs-operator">!</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-operator">-</span>1) {
            <span class="hljs-title">linkedBytecode</span> <span class="hljs-operator">=</span> <span class="hljs-title">linkedBytecode</span>.<span class="hljs-title">replace</span>(<span class="hljs-title">placeholder</span>, <span class="hljs-title">formattedAddress</span>)
        }
    }
    <span class="hljs-title"><span class="hljs-keyword">return</span></span> <span class="hljs-title">linkedBytecode</span>
}

<span class="hljs-title">export</span> <span class="hljs-title">default</span> <span class="hljs-title">func</span>;
</code></pre><p>其实上面很多都是些重复的代码，我们需要关注两段内容。第一段是这个<code>linkLibrary</code>函数。</p><pre data-type="codeBlock" text="function linkLibrary(bytecode: string, libraries: {
    [name: string]: string
} = {}): string {
    let linkedBytecode = bytecode
    for (const [name, address] of Object.entries(libraries)) {
        const placeholder = `__\$${utils.solidityKeccak256([&apos;string&apos;], [name]).slice(2, 36)}\$__`
        const formattedAddress = utils.getAddress(address).toLowerCase().replace(&apos;0x&apos;, &apos;&apos;)
        if (linkedBytecode.indexOf(placeholder) === -1) {
            throw new Error(`Unable to find placeholder for library ${name}`)
        }
        while (linkedBytecode.indexOf(placeholder) !== -1) {
            linkedBytecode = linkedBytecode.replace(placeholder, formattedAddress)
        }
    }
    return linkedBytecode
}
"><code><span class="hljs-function"><span class="hljs-keyword">function</span> <span class="hljs-title">linkLibrary</span>(<span class="hljs-params">bytecode: <span class="hljs-keyword">string</span>, libraries: {
    [name: <span class="hljs-keyword">string</span>]: <span class="hljs-keyword">string</span>
} = {}</span>): <span class="hljs-title"><span class="hljs-keyword">string</span></span> </span>{
    let linkedBytecode <span class="hljs-operator">=</span> bytecode
    <span class="hljs-keyword">for</span> (const [name, <span class="hljs-keyword">address</span>] of Object.entries(libraries)) {
        const placeholder <span class="hljs-operator">=</span> `__\$${utils.solidityKeccak256([<span class="hljs-string">'string'</span>], [name]).slice(<span class="hljs-number">2</span>, <span class="hljs-number">36</span>)}\$__`
        const formattedAddress <span class="hljs-operator">=</span> utils.getAddress(<span class="hljs-keyword">address</span>).toLowerCase().replace(<span class="hljs-string">'0x'</span>, <span class="hljs-string">''</span>)
        <span class="hljs-keyword">if</span> (linkedBytecode.indexOf(placeholder) <span class="hljs-operator">=</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">-1</span>) {
            <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-built_in">Error</span>(`Unable to find placeholder <span class="hljs-keyword">for</span> <span class="hljs-class"><span class="hljs-keyword">library</span> <span class="hljs-title">$</span></span>{name}`)
        }
        <span class="hljs-keyword">while</span> (linkedBytecode.indexOf(placeholder) <span class="hljs-operator">!</span><span class="hljs-operator">=</span><span class="hljs-operator">=</span> <span class="hljs-number">-1</span>) {
            linkedBytecode <span class="hljs-operator">=</span> linkedBytecode.replace(placeholder, formattedAddress)
        }
    }
    <span class="hljs-keyword">return</span> linkedBytecode
}
</code></pre><p>他的作用是将文件中<code>library</code>的链接到Bytecode里面去，但是一般情况下，我们不会采用这种方式。因为从源码部署的时候，我们可以借助工具来简化这个过程。而<code>library</code>是一种极为有效的减少合约字节码的方式，在后续的文章中，我们会专门讲解这个问题。第二段是这个<code>NFTDescriptorlibrary</code>的部署。</p><pre data-type="codeBlock" text="const linkedBytecode = linkLibrary(NFTTokenPositionDescriptor_BYTECODE,
    {
        [&apos;contracts/libraries/NFTDescriptor.sol:NFTDescriptor&apos;]: NFTDescriptorlibrary.address
    }
)

const positionDescriptor = await deployments.deploy(&apos;NFTPositionDescriptor&apos;, {
    from: deployer.address,
    contract: {
        abi: NFTPositionDescriptor_ABI,
        bytecode: linkedBytecode
    },
    args: [
        WETH9.address,
        // &apos;ETH&apos; as a bytes32 string
        &apos;0x4554480000000000000000000000000000000000000000000000000000000000&apos;
    ]
})
"><code>const linkedBytecode <span class="hljs-operator">=</span> linkLibrary(NFTTokenPositionDescriptor_BYTECODE,
    {
        [<span class="hljs-string">'contracts/libraries/NFTDescriptor.sol:NFTDescriptor'</span>]: NFTDescriptorlibrary.<span class="hljs-built_in">address</span>
    }
)

const positionDescriptor <span class="hljs-operator">=</span> await deployments.deploy(<span class="hljs-string">'NFTPositionDescriptor'</span>, {
    <span class="hljs-keyword">from</span>: deployer.<span class="hljs-built_in">address</span>,
    <span class="hljs-class"><span class="hljs-keyword">contract</span>: </span>{
        <span class="hljs-built_in">abi</span>: NFTPositionDescriptor_ABI,
        bytecode: linkedBytecode
    },
    args: [
        WETH9.<span class="hljs-built_in">address</span>,
        <span class="hljs-comment">// 'ETH' as a bytes32 string</span>
        <span class="hljs-string">'0x4554480000000000000000000000000000000000000000000000000000000000'</span>
    ]
})
</code></pre><p>其实这里就是借助<code>linkLibrary</code>函数，将<code>NFTDescriptor</code>的地址链接到<code>NFTTokenPositionDescriptor</code>的Bytecode中。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">部署合约</h2><p>到此，我们可以验证一下我们的部署脚本是否有问题。我们运行<code>yarn hardhat deploy</code>，如果一切顺利，我们可以看到如下的输出。</p><pre data-type="codeBlock" text="Nothing to compile
No need to generate any newer typings.
✨  Done in 1.45s.
"><code>Nothing to compile
No need to generate any newer typings.
✨  Done in <span class="hljs-number">1</span>.45s.
</code></pre><p>简单来说就是没有什么特别的输出，如果这里出现了报错，那么就证明我们的部署脚本出现了问题。有人可能会有疑问，我这里什么参数都没有填，我这合约究竟部署到了什么地方去了？其实，如果我们直接执行deploy脚本，那么他会默认部署到一个本地的网络中。我们也可以通过<code>yarn hardhat node</code>来启动一个本地的网络。但是，因为我们没有启动一个本地网络，所以在deploy时会自动启动一个本地网络用于部署，当部署完成后又会自动的关闭这个网络。所以我们基本看不到任何反馈输入，不过，没有反馈其实就是最好的反馈了。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结语</h2><p>这一个章节我们把UniswapV3进行了完整部署，有了这个部署，我们就可以在后续的章节中进行一些测试了。当然，有能力的朋友也可以在这上面进行一些初步的合约开发工作。不过我并不是很推荐使用这种方式，因为用这种方式，当合约报错时，你是无法进行精确到行的Debug的。不过这个问题我们将在后面的章节进行解决。在下一章，我们将会部署一个Uniswap的前端进行测试。</p><blockquote><p>文章首发于: ee.web3box.dev 作者:<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/BoxMrChen">https://twitter.com/BoxMrChen</a> 由<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://twitter.com/SafeHouseDao">SafeHouseDAO</a>出品</p></blockquote>]]></content:encoded>
            <author>boxchen@newsletter.paragraph.com (BoxChen)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/8a2efe318f86c228ace482fe184407b46d8af529d9951ffb90be690f4033e81e.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Web3企业级工程-初级篇：3. 部署一个UniswapV3]]></title>
            <link>https://paragraph.com/@boxchen/web3-3-uniswapv3</link>
            <guid>tL567Fp2aGOalNVwoEvW</guid>
            <pubDate>Wed, 21 Sep 2022 04:40:57 GMT</pubDate>
            <description><![CDATA[前沿既然是一个循序渐进的教程，那么肯定不能一上来就开始写代码。就好比你想造一台永动机，你至少要知道这个永动机动起来是什么样子，你才能更好的去分解他。 所以这一章节我们主要是部署一个UniswapV3，所以这章节的目的是快速并且简单的部署好一个UniswapV3，用于未来我们程序和Uni的联调。安装Uni包不过，在安装之前，我推荐你把第二章配置好的模版保存起来，以后就不用重新配置了。当然，这是可选内容。 首先安装UniswapV3，我们要知道一件事。Uniswap的合约分为两个，一个是v3-periphery 另外一个是v3-core 。现在简单介绍一下这两个仓库的合约是代表什么。v3-corecore合约是uniswap中负责掌管pool和factory的仓库。 pool：是资金存储和交换运算的合约。 factory：用于批量创造Pool的合约。 这两个合约是整个uniswap的核心。就算在没有periphery的情况下，也能正常运行的最小合约。v3-peripheryperiphery存放的是外围合约，这些合约是给用户和开发者一个统一的接口或者是便捷的通证。核心合约有NFTMa...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">前沿</h2><p>既然是一个循序渐进的教程，那么肯定不能一上来就开始写代码。就好比你想造一台永动机，你至少要知道这个永动机动起来是什么样子，你才能更好的去分解他。</p><p>所以这一章节我们主要是部署一个UniswapV3，所以这章节的目的是快速并且简单的部署好一个UniswapV3，用于未来我们程序和Uni的联调。</p><h2 id="h-uni" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">安装Uni包</h2><p>不过，在安装之前，我推荐你把第二章配置好的模版保存起来，以后就不用重新配置了。当然，这是<strong>可选内容</strong>。</p><p>首先安装UniswapV3，我们要知道一件事。Uniswap的合约分为两个，一个是<code>v3-periphery</code> 另外一个是<code>v3-core</code> 。现在简单介绍一下这两个仓库的合约是代表什么。</p><h3 id="h-v3-core" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">v3-core</h3><p>core合约是uniswap中负责掌管pool和factory的仓库。</p><p>pool：是资金存储和交换运算的合约。</p><p>factory：用于批量创造Pool的合约。</p><p>这两个合约是整个uniswap的核心。就算在没有periphery的情况下，也能正常运行的最小合约。</p><h3 id="h-v3-periphery" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">v3-periphery</h3><p>periphery存放的是外围合约，这些合约是给用户和开发者一个统一的接口或者是便捷的通证。核心合约有NFTManager和SwapRouter。</p><p>NFTManager：一个用于记录用户创建的流动性各类数据的合约。</p><p>SwapRouter：包装类，将交换的各种逻辑进行包装抽象。</p><p>这里我们不过度展开UniswapV3的逻辑。我们重点是如何去部署他。</p><p>通过执行</p><pre data-type="codeBlock" text="yarn add @uniswap/v3-core @uniswap/v3-periphery@1.4.1
"><code>yarn <span class="hljs-keyword">add</span> <span class="hljs-variable">@uniswap</span><span class="hljs-operator">/</span>v3<span class="hljs-operator">-</span>core <span class="hljs-variable">@uniswap</span><span class="hljs-operator">/</span>v3<span class="hljs-operator">-</span>periphery<span class="hljs-variable">@1</span><span class="hljs-number">.4</span><span class="hljs-number">.1</span>
</code></pre><p>先安装好两个必要的合约仓库。要注意的是，<code>@uniswap/v3-periphery@1.4.1</code> 是带了版本号的，这是因为在编写本文的时候，1.4.2版本缺少artifacts文件夹，相关issue如下。<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://github.com/Uniswap/v3-periphery/issues/313">github-issue</a>, 如果当你看到这篇文章时，这个issue已经被解决了，那么就不用加上末尾的版本号指定了。</p><h2 id="h-bytecode" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">通过ByteCode部署</h2><p>部署合约其实只有一种方式，就是从bytecode进行部署，但是不同的工具会提供不同等级的封装。以至于封装到极致后，你只需要输入一个合约名称就可以部署。不过为了方便了解原理，我们就使用最为基础的部署方式进行部署。</p><p>我们先创建一个deploy文件。在deploy文件夹下创建一个名为00_deploy_univ3.ts的文件。</p><p>代码内容如下。</p><pre data-type="codeBlock" text="import { DeployFunction } from &quot;hardhat-deploy/types&quot;;

const func: DeployFunction = async function () {
    
}

export default func;
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title class_">DeployFunction</span> } <span class="hljs-keyword">from</span> <span class="hljs-string">"hardhat-deploy/types"</span>;

<span class="hljs-keyword">const</span> <span class="hljs-attr">func</span>: <span class="hljs-title class_">DeployFunction</span> = <span class="hljs-keyword">async</span> <span class="hljs-keyword">function</span> (<span class="hljs-params"></span>) {
    
}

<span class="hljs-keyword">export</span> <span class="hljs-keyword">default</span> func;
</code></pre><p>这是一个deploy文件最基础的框架函数，我们首先要部署的是core中的factory合约。所以我们修改代码为。</p><pre data-type="codeBlock" text="import { DeployFunction } from &quot;hardhat-deploy/types&quot;;
import {
    abi as FACTORY_ABI,
    bytecode as FACTORY_BYTECODE,
} from &apos;@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json&apos;
import { HardhatRuntimeEnvironment } from &quot;hardhat/types&quot;;

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
    const { deployments,ethers } = hre
    const [deployer] = await ethers.getSigners()
    await deployments.deploy(&quot;UniV3Factory&quot;, {
        from: deployer.address,
        contract: {
            bytecode: FACTORY_BYTECODE,
            abi: FACTORY_ABI
        },
    })
}

export default func;
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">DeployFunction</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"hardhat-deploy/types"</span>;
<span class="hljs-keyword">import</span> {
    <span class="hljs-title"><span class="hljs-built_in">abi</span></span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">FACTORY_ABI</span>,
    <span class="hljs-title">bytecode</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">FACTORY_BYTECODE</span>,
} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json'</span>
<span class="hljs-title"><span class="hljs-keyword">import</span></span> { <span class="hljs-title">HardhatRuntimeEnvironment</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"hardhat/types"</span>;

const func: DeployFunction <span class="hljs-operator">=</span> async <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">hre: HardhatRuntimeEnvironment</span>) </span>{
    const { deployments,ethers } <span class="hljs-operator">=</span> hre
    const [deployer] <span class="hljs-operator">=</span> await ethers.getSigners()
    await deployments.deploy(<span class="hljs-string">"UniV3Factory"</span>, {
        <span class="hljs-keyword">from</span>: deployer.<span class="hljs-built_in">address</span>,
        <span class="hljs-class"><span class="hljs-keyword">contract</span>: </span>{
            bytecode: FACTORY_BYTECODE,
            <span class="hljs-built_in">abi</span>: FACTORY_ABI
        },
    })
}

export default func;
</code></pre><p>加了”一点点”细节后，我们的代码丰富了起来。这里先解释一下关键内容。</p><pre data-type="codeBlock" text="import {
    abi as FACTORY_ABI,
    bytecode as FACTORY_BYTECODE,
} from &apos;@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json&apos;
"><code><span class="hljs-keyword">import</span> {
    abi <span class="hljs-keyword">as</span> <span class="hljs-type">FACTORY_ABI</span>,
    bytecode <span class="hljs-keyword">as</span> <span class="hljs-type">FACTORY_BYTECODE</span>,
} from '<span class="hljs-meta">@uniswap</span><span class="hljs-regexp">/v3-core/</span>artifacts<span class="hljs-regexp">/contracts/</span><span class="hljs-type">UniswapV3Factory</span>.sol<span class="hljs-operator">/</span><span class="hljs-type">UniswapV3Factory</span>.json'
</code></pre><p>这段代码的内容是从uniswap提供的官方包中，引入bytecode和abi数据，这样我们就能直接在下面进行内容填充。</p><pre data-type="codeBlock" text="await deployments.deploy(&quot;UniV3Factory&quot;, {
    from: deployer.address,
    contract: {
        bytecode: FACTORY_BYTECODE,
        abi: FACTORY_ABI
    },
})
"><code>await deployments.deploy(<span class="hljs-string">"UniV3Factory"</span>, {
    <span class="hljs-keyword">from</span>: deployer.<span class="hljs-built_in">address</span>,
    <span class="hljs-class"><span class="hljs-keyword">contract</span>: </span>{
        bytecode: FACTORY_BYTECODE,
        <span class="hljs-built_in">abi</span>: FACTORY_ABI
    },
})
</code></pre><p>这段代码是部署V3Factory合约的代码。其中deploy是hardhat-deploy插件提供的函数，我们在第二个参数中指定了bytecode和abi——这两个必须都要有。</p><p>当然，我们还要修改tsconfig.json文件，添加 &quot;resolveJsonModule&quot;: true的选项。修改后文件如下。</p><pre data-type="codeBlock" text="{
  &quot;compilerOptions&quot;: {
    &quot;target&quot;: &quot;es2020&quot;,
    &quot;module&quot;: &quot;commonjs&quot;,
    &quot;esModuleInterop&quot;: true,
    &quot;forceConsistentCasingInFileNames&quot;: true,
    &quot;strict&quot;: true,
    &quot;skipLibCheck&quot;: true,
    &quot;resolveJsonModule&quot;: true
  },
}
"><code><span class="hljs-punctuation">{</span>
  <span class="hljs-attr">"compilerOptions"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span>
    <span class="hljs-attr">"target"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"es2020"</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"module"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"commonjs"</span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"esModuleInterop"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"forceConsistentCasingInFileNames"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"strict"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"skipLibCheck"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span><span class="hljs-punctuation">,</span>
    <span class="hljs-attr">"resolveJsonModule"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">true</span></span>
  <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span>
<span class="hljs-punctuation">}</span>
</code></pre><p>接下来是SwapRouter合约，我们如法炮制，修改代码如下。当然你也可以尝试自己动手试试。</p><pre data-type="codeBlock" text="import { DeployFunction } from &quot;hardhat-deploy/types&quot;;
import {
    abi as FACTORY_ABI,
    bytecode as FACTORY_BYTECODE,
} from &apos;@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json&apos;

import {
    abi as SWAP_ROUTER_ABI,
    bytecode as SWAP_ROUTER_BYTECODE,
} from &apos;@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json&apos;
import { HardhatRuntimeEnvironment } from &quot;hardhat/types&quot;;
import { constants } from &quot;ethers&quot;;

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
    const { deployments, ethers } = hre
    const [deployer] = await ethers.getSigners()
    const factory = await deployments.deploy(&quot;UniV3Factory&quot;, {
        from: deployer.address,
        contract: {
            bytecode: FACTORY_BYTECODE,
            abi: FACTORY_ABI
        },
    })

    await deployments.deploy(&quot;UniV3SwapRouter&quot;, {
        from: deployer.address,
        contract: {
            abi: SWAP_ROUTER_ABI,
            bytecode: SWAP_ROUTER_BYTECODE
        },
        args:[factory.address,constants.AddressZero]
        // 上面是部署合约的参数，第一个参数为factory地址，第二个为WETH地址，这里为了图方便就直接用0地址啦😁
    })
}

export default func;
"><code><span class="hljs-keyword">import</span> { <span class="hljs-title">DeployFunction</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"hardhat-deploy/types"</span>;
<span class="hljs-keyword">import</span> {
    <span class="hljs-title"><span class="hljs-built_in">abi</span></span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">FACTORY_ABI</span>,
    <span class="hljs-title">bytecode</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">FACTORY_BYTECODE</span>,
} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@uniswap/v3-core/artifacts/contracts/UniswapV3Factory.sol/UniswapV3Factory.json'</span>

<span class="hljs-title"><span class="hljs-keyword">import</span></span> {
    <span class="hljs-title"><span class="hljs-built_in">abi</span></span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">SWAP_ROUTER_ABI</span>,
    <span class="hljs-title">bytecode</span> <span class="hljs-title"><span class="hljs-keyword">as</span></span> <span class="hljs-title">SWAP_ROUTER_BYTECODE</span>,
} <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">'@uniswap/v3-periphery/artifacts/contracts/SwapRouter.sol/SwapRouter.json'</span>
<span class="hljs-title"><span class="hljs-keyword">import</span></span> { <span class="hljs-title">HardhatRuntimeEnvironment</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"hardhat/types"</span>;
<span class="hljs-keyword">import</span> { <span class="hljs-title">constants</span> } <span class="hljs-title"><span class="hljs-keyword">from</span></span> <span class="hljs-string">"ethers"</span>;

const func: DeployFunction <span class="hljs-operator">=</span> async <span class="hljs-function"><span class="hljs-keyword">function</span> (<span class="hljs-params">hre: HardhatRuntimeEnvironment</span>) </span>{
    const { deployments, ethers } <span class="hljs-operator">=</span> hre
    const [deployer] <span class="hljs-operator">=</span> await ethers.getSigners()
    const factory <span class="hljs-operator">=</span> await deployments.deploy(<span class="hljs-string">"UniV3Factory"</span>, {
        <span class="hljs-keyword">from</span>: deployer.<span class="hljs-built_in">address</span>,
        <span class="hljs-class"><span class="hljs-keyword">contract</span>: </span>{
            bytecode: FACTORY_BYTECODE,
            <span class="hljs-built_in">abi</span>: FACTORY_ABI
        },
    })

    await deployments.deploy(<span class="hljs-string">"UniV3SwapRouter"</span>, {
        <span class="hljs-keyword">from</span>: deployer.<span class="hljs-built_in">address</span>,
        <span class="hljs-class"><span class="hljs-keyword">contract</span>: </span>{
            <span class="hljs-built_in">abi</span>: SWAP_ROUTER_ABI,
            bytecode: SWAP_ROUTER_BYTECODE
        },
        args:[factory.<span class="hljs-built_in">address</span>,constants.AddressZero]
        <span class="hljs-comment">// 上面是部署合约的参数，第一个参数为factory地址，第二个为WETH地址，这里为了图方便就直接用0地址啦😁</span>
    })
}

export default func;
</code></pre><p>那么我们的部署脚本到这里就编写完成了。通过命令<code>yarn hardhat deploy</code> 即可将代码部署到任何网络上。</p><p>不过，通过bytecode直接部署会有一些不好的地方，比如在出错的时候，你无法获得源码级别的错误追踪。并且，我们部署的也是一个残缺的版本，因为我们还缺少NonfungiblePositionManager，NonfungibleTokenPositionDescriptor，NFTDescriptor合约，不过，它们的部署方式都是一样的。你可以试着用上述的方法添加它们。就当是课后作业啦。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">结语</h2><p>这一个章节只构建了通过bytecode部署uniswap的基础文件，但是我们的内容其实是不够一个实际的uniswap运行的。所以在下一章节，我们会使用另外一种方式部署uniswapv3，并且编写测试用例。等测试用例通过后，我们再用源码编译的方式对UniswapV3进行部署。</p><p>PS：我发现mirror不太适合写这种技术类文章，后续会切换到其他平台。</p>]]></content:encoded>
            <author>boxchen@newsletter.paragraph.com (BoxChen)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/6524340facc4bfed0efeaf425c1633e6e3b8fffcf6283ac5f725f78c513f8ae9.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Web3企业级工程-初级篇：2. Solidity环境]]></title>
            <link>https://paragraph.com/@boxchen/web3-2-solidity</link>
            <guid>n0JXjnncFldebc61jtIU</guid>
            <pubDate>Mon, 12 Sep 2022 18:00:31 GMT</pubDate>
            <description><![CDATA[前言本篇会介绍常用的几种Solidity开发环境，同时会推荐一种最为合适的开发环境并且安装必要的内容。环境介绍目前Solidity开发环境大致分为2类线上类：依赖于浏览器的一体式开发环境，可以方便的进行debug，管理账户余额，配置环境等等操作。线下类：适用于本地开发的环境。线上类线上类产品主要是Remxi <https://remix.ethereum.org/ > Remix是ETH官方提供的线上开发环境，是一个非常强大的整合云环境开发IDE。提供了文件管理，在线编译，静态分析，部署以及运行交易，测试等功能。并且内置了强大的插件系统。 Remix在各个方面都非常强大，甚至能直接修改读取本地文件。美中不足的是不支持多版本编译器。在面对一些简单开发，或者临时对链上进行一些分析或数据调用操作是非常合适的。线下类Truffle Suite网址：https://trufflesuite.com/ Truffle Suite是一个套件名称，其中包含了truffle，ganache，drizzle等工具。 Truffle：一个适用于EVM的开发环境，包含测试等工具套件。 Ganache：一...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">前言</h2><p>本篇会介绍常用的几种Solidity开发环境，同时会推荐一种最为合适的开发环境并且安装必要的内容。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">环境介绍</h2><p>目前Solidity开发环境大致分为2类</p><ul><li><p>线上类：依赖于浏览器的一体式开发环境，可以方便的进行debug，管理账户余额，配置环境等等操作。</p></li><li><p>线下类：适用于本地开发的环境。</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">线上类</h3><p>线上类产品主要是Remxi &lt;<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://remix.ethereum.org/">https://remix.ethereum.org/</a> &gt;</p><p>Remix是ETH官方提供的线上开发环境，是一个非常强大的整合云环境开发IDE。提供了文件管理，在线编译，静态分析，部署以及运行交易，测试等功能。并且内置了强大的插件系统。</p><p>Remix在各个方面都非常强大，甚至能直接修改读取本地文件。美中不足的是不支持多版本编译器。在面对一些简单开发，或者临时对链上进行一些分析或数据调用操作是非常合适的。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">线下类</h3><h4 id="h-truffle-suite" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Truffle Suite</h4><p>网址：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://trufflesuite.com/">https://trufflesuite.com/</a></p><p>Truffle Suite是一个套件名称，其中包含了truffle，ganache，drizzle等工具。</p><p>Truffle：一个适用于EVM的开发环境，包含测试等工具套件。</p><p>Ganache：一个用于测试开发的evm本地节点，可用于合约的部署开发测试等功能。</p><p>Drizzle：基于 Redux的前端组建，方便在前端进行交互。</p><h4 id="h-hardhat" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Hardhat</h4><p>网址：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://hardhat.org/">https://hardhat.org/</a></p><p>前身Builder，是一个功能超级齐全的Solidity开发框架，几乎涵盖了所有的Solidity开发需要的功能。自带智能合约的测试，编译，以及本地节点，是一个高整合的开发框架，并且具有强大的插件系统，使得开发更为简单。</p><h4 id="h-foundry" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Foundry</h4><p>网址：<a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://getfoundry.sh/">https://getfoundry.sh/</a></p><p>基于Rust开发的一个solidity开发框架，以速度为主打。同样也是涵盖了开发，测试，本地节点功能。与Hardhat不同的是，foundry的测试代码使用的是solidity编写，但是配合vm cheat code也能和本地环境进行互动。但是相比于Hardhat还是缺少了一些功能。</p><p>还有一些使用python开发的框架，本文并不一一阐述，有兴趣的朋友可以自行搜索。</p><h2 id="h-hardhat" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">Hardhat的环境搭建</h2><p>在三种主流的开发环境下，<code>Hardhat</code>其强大的功能让他成为了开发框架的标杆，如果有人还在推荐<code>truffle</code>作为你的开发框架，请自豪的搬出Hardhat并且让他也加入Hardhat的大家族。</p><p>当然，这并不是代表Hardhat没有缺点，只不过在优点和缺点进行权衡利弊后显得微不足道。当然Foundry也是一个非常强大的框架，但是他的功能依然没有Hardhat全面，不过，将Hardhat和Foundry一起使用会有意想不到的效果。</p><p>不过，我们暂时不去做这种事情，因为Hardhat目前已经足以应对大部分情况。</p><p>废话不多说，直接进入下一步。</p><h3 id="h-hardhat" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">创建项目，安装Hardhat</h3><p>找到一个你喜欢的目录，新建文件夹后，用vscode打开这个文件夹。你也可以使用终端，输入code \path\to\your\project，即可用vscode一键打开项目目录。</p><p>打开VSCode后，我们按下<code>Command+J</code> 这个组合键即可打开VSCode的Terminal栏。在这里我们进行安装Hardhat。</p><p>输入<code>npx hardhat</code> 等待提示按下回车，稍等片刻（如果比较慢请开启科学上网）。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/eb28c3ed0d1d657a54f89912e5f5d6518423f75715dbc34bbda365d927e61979.png" alt="Hardhat安装" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">Hardhat安装</figcaption></figure><p>这里我们按一下 下方向键，选择<code>Create a TypeScript project</code> 当然你完全可以选择<code>Create a Javascript project</code> 只不过并不是很推荐，因为TS在多人协作比JS具有更多的优势。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d780fd2ebc18a76828a351105029726c367faafd0458d5dd28ad729084b8c8af.png" alt="完成初步设定" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">完成初步设定</figcaption></figure><p>按照提示完成初步设定，不过注意要在<code>Do you want to install this sample project&apos;s dependencies with npm</code> 时输入n。因为我们需要用<code>Yarn</code>来管理依赖。</p><p>然后我们输入<code>yarn add &quot;hardhat@^2.11.1&quot; @nomicfoundation/hardhat-toolbox @nomicfoundation/hardhat-network-helpers @nomicfoundation/hardhat-chai-matchers @nomiclabs/hardhat-ethers @nomiclabs/hardhat-etherscan chai ethers hardhat-gas-reporter solidity-coverage @typechain/hardhat typechain @typechain/ethers-v5 @ethersproject/abi @ethersproject/providers</code> 进行依赖安装。</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">注意⚠️</h4><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/d5968b43fe039a02227a63200a19e93f5b73b0808a4ee4a0773164e2f9e922e1.png" alt="安装依赖" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">安装依赖</figcaption></figure><p>如果你出现了这个错误。不用慌，这在JS的世界是再常见不过的事情了，将来还会遇到各种各样的问题，比如缺少python编译环境啦，架构不对啦等等。</p><p>这里遇到的问题是当前的node版本并不符合要求。可以看到，需要node的版本为 ^14,^16,^18。所以这里我们需要重新安装node到符合条件的版本，这个问题可能是因为你安装的node版本不对，或者是你使用了<code>brew install nvm</code> ，如果是，你可以输入<code>brew uninstall nvm</code> 然后使用以下命令安装：<code>curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash</code> 。然后重启终端。</p><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">安装完成</h4><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0451523925ca3ed107c925765153d59ac46446f6e2da459dc92fccfaf1415ca9.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>安装完成后，你的目录结构应该是这样。</p><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">安装其他必要依赖</h3><p>当然，我们教程的目的是企业级工程，所以只安装本体的Hardhat是完完全全不够的，因为这还缺少一个工程可持续升级的功能。</p><h4 id="h-typescript" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Typescript</h4><p>因为我们创建的是typescript项目，所以我们需要安装typescript的依赖。</p><pre data-type="codeBlock" text="yarn add ts-node typescript chai @types/node @types/mocha @types/chai
"><code>yarn <span class="hljs-keyword">add</span> ts<span class="hljs-operator">-</span>node typescript chai <span class="hljs-variable">@types</span><span class="hljs-operator">/</span>node <span class="hljs-variable">@types</span><span class="hljs-operator">/</span>mocha <span class="hljs-variable">@types</span><span class="hljs-operator">/</span>chai
</code></pre><h4 id="h-hardhat-deploy" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Hardhat deploy</h4><p>这是hardhat工程化必要的插件，这个插件用来帮忙管理代码部署，你能轻松的在代码中根据名称获取到你部署的合约地址，同时也能根据上下文来决定什么合约需要部署。还能和migration一样设定只能部署一次的合约。同时也给测试提供了非常方便的函数来进行灵活的合约部署，比如你可以只部署带有某些tag的合约。</p><p>安装<code>Hardhat deploy</code>只需要输入以下命令</p><pre data-type="codeBlock" text="yarn add hardhat-deploy @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers ethers
"><code>yarn add hardhat-deploy <span class="hljs-variable">@nomiclabs</span>/hardhat-ethers<span class="hljs-variable">@npm</span><span class="hljs-symbol">:hardhat-deploy-ethers</span> ethers
</code></pre><p>然后在<code>hardhat.config.ts</code> 文件中添加<code>import &quot;hardhat-deploy&quot;</code> 。</p><p>到此，deploy就已经安装完成。我们在根目录下建立deploy文件夹，以后的deploy文件都会放在这个文件夹下。</p><h4 id="h-dotenv" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">Dotenv</h4><p>dotenv是可以把<code>.env</code> 文件导入到系统环境变量中给程序使用的一个js包，这个包非常有用，因为我们在工程化开发代码的时候，一定会使用git或者其他的版本管理工具。对于私有项目来说，一些机密的变量上传到版本库可能问题不太大，但是如果是公有项目，那将会或多或少的有一些安全问题。所以我们需要把一些变量放到不会被上传到版本控制的文件。</p><p>输入</p><pre data-type="codeBlock" text="yarn add dotenv
"><code>yarn <span class="hljs-keyword">add</span> dotenv
</code></pre><p>然后修改<code>hardhat.config.ts</code> 在最上面添加<code>import &apos;dotenv/config&apos;</code> 。</p><p>到此为止，我们必要的项目工程包就已经安装完成，你可以把这个包做成一个模版，保存起来以后使用。</p><p>我们运行一下测试命令来看看我们的整合包是否正常。</p><pre data-type="codeBlock" text="yarn hardhat test
"><code>yarn hardhat <span class="hljs-built_in">test</span>
</code></pre><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/5aaeeeb44fce1302059badddc77de56535b2d9023b608b9cc6abbf7c6da77f7b.png" alt="正常运行图" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">正常运行图</figcaption></figure><p>当你看到以上内容时，那么恭喜你，一个符合工程标准的环境就搭建好了。</p><h2 id="h-vscode" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">VSCode插件（可选内容）</h2><p>当然，给我们的VSCode装点插件可以更好的帮助我们开发。我这里推荐几个插件。</p><ul><li><p><a target="_blank" rel="noopener noreferrer nofollow ugc" class="dont-break-out" href="https://marketplace.visualstudio.com/items?itemName=NomicFoundation.hardhat-solidity">Hardhat Solidity</a> - 这个是Hardhat团队开发的Solidity插件，非常好用</p></li><li><p>Copilot - Github的智能补全代码插件，也是非常好用</p></li><li><p>Material Theme Icons - 美化插件</p></li><li><p>Material Theme - 还是个美化插件</p></li></ul><p>最后安装效果如下。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/039825d9cf02c774d5f5f2989cffcaa7cf6cc710bbada0b5a8777892e9ed1a14.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>当然啦，每个人自己对美都有一种定义，所以如果你决定不好看，请随意的更换。</p><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>本章讲述了如何安装Hardhat的工程环境，在后续的教程中，会通过实现一个与Uniswap进行交互的程序来开展后面的内容。</p><p>subscribe://</p>]]></content:encoded>
            <author>boxchen@newsletter.paragraph.com (BoxChen)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/6524340facc4bfed0efeaf425c1633e6e3b8fffcf6283ac5f725f78c513f8ae9.png" length="0" type="image/png"/>
        </item>
        <item>
            <title><![CDATA[Web3企业级工程-初级篇：1. 前期准备]]></title>
            <link>https://paragraph.com/@boxchen/web3-1</link>
            <guid>KMwHhnrVzw1nz9Ish7qK</guid>
            <pubDate>Sun, 11 Sep 2022 16:25:50 GMT</pubDate>
            <description><![CDATA[前言在文章开始前，先明确好几个阅读要点。文章中会多次出现，必要条件，可选内容，一定不要，注意⚠️ 等词。他们的意思分别为。必要条件：属于一定需要的内容，缺少后将会无法进行教程。可选内容：属于补充性内容，并不会对教程产生影响一定不要：属于强制规范内容，请不要做此内容下的行为注意⚠️：属于提示性内容，该内容中会进行指引，并且指出什么应该，什么不应该。目标人群本文适合以下用户阅读。必要条件：具备理解能力和英语阅读能力。计算机专业相关人员Web2转向Web3的有基础开发者有开发简单项目，但是缺少开发大型项目经验的开发者在校大学生，需要有自制力和自学能力还有较为出色的综合能力进来图一乐本文不适合以下用户阅读。毫无编程基础的用户顶尖高手开发环境本文将全程用Macos进行开发，如果你是使用Windows系统，推荐两个解决方案。更换Mac自行搜索一些无法在Windows上执行的命令如何用其他方案替代，比如安装Node环境，在Unix类系统下可以使用nvm，但是win只能下载安装包必要软件科学上网工具软件环境安装一般情况下，开发一个Dapp（EVM）需要一些基础软件，下面是必要的基础软件在Maco...]]></description>
            <content:encoded><![CDATA[<h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">前言</h2><p>在文章开始前，先明确好几个阅读要点。文章中会多次出现，<code>必要条件</code>，<code>可选内容</code>，<code>一定不要</code>，<code>注意⚠️</code> 等词。他们的意思分别为。</p><ul><li><p><code>必要条件</code>：属于一定需要的内容，缺少后将会无法进行教程。</p></li><li><p><code>可选内容</code>：属于补充性内容，并不会对教程产生影响</p></li><li><p><code>一定不要</code>：属于强制规范内容，请不要做此内容下的行为</p></li><li><p><code>注意⚠️</code>：属于提示性内容，该内容中会进行指引，并且指出什么应该，什么不应该。</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">目标人群</h2><p>本文适合以下用户阅读。<code>必要条件</code>：具备理解能力和英语阅读能力。</p><ul><li><p>计算机专业相关人员</p></li><li><p>Web2转向Web3的有基础开发者</p></li><li><p>有开发简单项目，但是缺少开发大型项目经验的开发者</p></li><li><p>在校大学生，需要有自制力和自学能力还有较为出色的综合能力</p></li><li><p>进来图一乐</p></li></ul><p>本文不适合以下用户阅读。</p><ul><li><p>毫无编程基础的用户</p></li><li><p>顶尖高手</p></li></ul><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">开发环境</h2><p>本文将全程用<code>Macos</code>进行开发，如果你是使用<code>Windows</code>系统，推荐两个解决方案。</p><ul><li><p>更换<code>Mac</code></p></li><li><p>自行搜索一些无法在<code>Windows</code>上执行的命令如何用其他方案替代，比如安装<code>Node</code>环境，在<code>Unix</code>类系统下可以使用<code>nvm</code>，但是<code>win</code>只能下载安装包</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">必要软件</h3><ul><li><p>科学上网工具</p></li></ul><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">软件环境安装</h3><p>一般情况下，开发一个Dapp（EVM）需要一些基础软件，下面是必要的基础软件在Macos下安装的方式。请注意全程开启科学上网，如果没有科学上网工具，请自行搜索替代源。</p><p><code>必要软件</code>有：</p><ul><li><p>Git</p></li><li><p>VSCode</p></li><li><p>Nodejs（v16+）</p></li></ul><p>如果你知道如何手动安装这些，可以跳过接下来的教程。</p><p>不过，为了方便安装，我们会先安装一个<code>可选软件</code>，Brew。</p><h4 id="h-brew" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">安装Brew</h4><p>输入命令到终端</p><pre data-type="codeBlock" text="/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;
"><code><span class="hljs-operator">/</span>bin<span class="hljs-operator">/</span>bash <span class="hljs-operator">-</span>c <span class="hljs-string">"$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"</span>
</code></pre><p>安装过程中需要输入一次系统密码，并且后续需要按一次回车键。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8f34c3929ca90636fbcf493673af4b3ab793c7428799787326b6721aa7f59f97.png" alt="注意安装后的提示" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">注意安装后的提示</figcaption></figure><p>如果出现以上提示，执行下方命令即可。</p><h4 id="h-gitnvm" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">安装Git,Nvm</h4><p>输入命令</p><pre data-type="codeBlock" text="brew install git
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash
"><code>brew install git
curl <span class="hljs-operator">-</span>o<span class="hljs-operator">-</span> https:<span class="hljs-comment">//raw.githubusercontent.com/nvm-sh/nvm/v0.39.1/install.sh | bash</span>
</code></pre><h4 id="h-nodejs" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">安装Nodejs</h4><p>输入命令</p><pre data-type="codeBlock" text="nvm install --lts
"><code>nvm install <span class="hljs-operator">-</span><span class="hljs-operator">-</span>lts
</code></pre><h4 id="h-vscode" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">安装VSCode</h4><p>输入命令</p><pre data-type="codeBlock" text="brew install --cask visual-studio-code
"><code>brew install <span class="hljs-operator">-</span><span class="hljs-operator">-</span>cask visual<span class="hljs-operator">-</span>studio<span class="hljs-operator">-</span>code
</code></pre><h4 id="h-" class="text-xl font-header !mt-6 !mb-3 first:!mt-0 first:!mb-0">检验</h4><p>至此，我们已经安装了必要软件中的node，vscode，git。但是我们可以输入以下命令进行检验</p><pre data-type="codeBlock" text="git -v
node -v
code -v
"><code>git <span class="hljs-operator">-</span>v
node <span class="hljs-operator">-</span>v
code <span class="hljs-operator">-</span>v
</code></pre><p>结果应该如下。</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/0cf3292a8fddd29fb610ffa9664cad3dfbbcf0dd2fd195f0d1b1e488ae0f789c.png" alt="检验结果" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">检验结果</figcaption></figure><h3 id="h-" class="text-2xl font-header !mt-6 !mb-4 first:!mt-0 first:!mb-0">可选安装</h3><p>不过，为了更好的开发体验，我们还需要安装一个<code>yarn</code>包管理工具。</p><p>输入命令</p><pre data-type="codeBlock" text="npm install yarn -g
######
yarn -v
"><code>npm install yarn -g
<span class="hljs-meta prompt_">#</span><span class="bash"><span class="hljs-comment">#####</span></span>
yarn -v
</code></pre><p>结果应该包含以下内容</p><figure float="none" data-type="figure" class="img-center" style="max-width: null;"><img src="https://storage.googleapis.com/papyrus_images/8e7ba81b3738ce37aec248c6b77f359cba7fbbfa481e5f20bf0efb92b5c0d812.png" alt="yarn安装成功" blurdataurl="data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACwAAAAAAQABAAACAkQBADs=" nextheight="600" nextwidth="800" class="image-node embed"><figcaption HTMLAttributes="[object Object]" class="">yarn安装成功</figcaption></figure><h2 id="h-" class="text-3xl font-header !mt-8 !mb-4 first:!mt-0 first:!mb-0">总结</h2><p>自此，我们已经进行了最初步的环境环境安装，这一章节并没有难度和太大的意义，但是这是对能否学习这门课程的一个初步检验，也可以认为是一个门槛。如果在这一章都遇到较大的问题，推荐静下心来仔细研究后再进行学习。</p><p>subscribe://</p>]]></content:encoded>
            <author>boxchen@newsletter.paragraph.com (BoxChen)</author>
            <enclosure url="https://storage.googleapis.com/papyrus_images/6524340facc4bfed0efeaf425c1633e6e3b8fffcf6283ac5f725f78c513f8ae9.png" length="0" type="image/png"/>
        </item>
    </channel>
</rss>