# Truffle部署Solidity合约教程（etherscan多文件自动开源）

By [dami.eth](https://paragraph.com/@yidakoumi) · 2022-06-12

---

前言
--

最近一直在折腾 Solidity 合约的学习，虽然现在普通的合约代码能看懂了，但是一直好奇如何把合约部署到链上。

最开始学习的方式是通过官方 Remix 的网站进行部署，单文件还好说，多文件开源起来非常麻烦。

后来得知了两个部署框架，一个是以 Python 为主语言的开发框架 Brownie ，还有一个是以 Js 为主语言的开发框架 Truffle ，还有是 Js 的一个 Hardhat，还没有尝试用过。

Truffle 和 Brownie 在流程体验上，可以说学会了一个，另一个也大概就明白什么意思了，但得益于 Truffle 初始化工程项目后，生成的配置文件 truffle-config.js 比 Brownie 默认的配置文件友好许多。

如下，起码会有很多默认的注释掉的配置，让新手看起来就通俗易懂：

![](https://storage.googleapis.com/papyrus_images/eeeadc86ba389922305dcb21d896541a768fa07418391948063680ac36c330d8.png)

所以，分享下今天一天的踩坑经验，就来讲讲如何把一个合约通过 Truffle 从零部署到测试网上，同时使用代码自动验证开源，不需要手动去 etherscan 去复制代码，贴代码进行验证发布并开源。

正文
--

### 一、初始化工程

这里的安装依赖，假设你已经安装好了 nodejs 。

1、通过 nodejs 安装 truffle 框架：

    // 1. 安装 truffle
    npm install -g truffle
    

2、新建项目，切到项目目录下，执行 truffle init：

     // 2. 使用 init 命令对项目进行初始化 
    truffle init
    

![](https://storage.googleapis.com/papyrus_images/215a23c72c940af6f51f830625d29844f3a6b8e25ba4234df20c9cf8e12f533a.png)

我这里新建了一个目录，叫 my-first-truffle，执行完命令后，目录如下：

![](https://storage.googleapis.com/papyrus_images/8cdb32b2f4fb059f283c6b440e37c5996b6716992088142ad9d1328541924720.png)

但一般当我们编译 solidity 文件和引入依赖后，往往项目会变成这样：

![](https://storage.googleapis.com/papyrus_images/5e609fc578058e6449f6ef2d8a95b69ea3f78bfe4d1f86eff4aa4e36464c866f.png)

解释：

    --build: 存放编译后文件的目录
    --contracts: solidity合约代码的目录
    --migrations: 用 js 语言写的部署用的文件
    --node_modules: js的项目依赖
    --test: 用 js 语言写的单元测试目录
      --package.json: 依赖的配置文件
      --truffle-config.js: truffle框架的配置文件 以上就是一个新工程初始化后的样子。
    

别忘记，回到命令行，对 npm 进行工程初始化，我这里用的 cnpm ，cnpm init，然后疯狂回车，一路默认即可：

![](https://storage.googleapis.com/papyrus_images/c023bf1224b2647c52f0e39c9df8dc1a9703d939419127e7b19fe1508eeca7ef.png)

### 二、编写用例合约

我这里编写一个自己的 token 用例，这里继承了 ERC20 ，为的就是让合约文件本身有 import 的操作，这样便可以验证在后面的 truffle 自动部署并且验证开源到 etherscan 上。

代码注释写的很清楚了，有基础的自然看的懂，代码不是本文的重点，部署流程才是。

新建一个合约文件 DaMiToken.sol ，新建一个部署合约的 js 文件 2\_deploy\_mitoken.js ：

![](https://storage.googleapis.com/papyrus_images/b672818bad32a7e1b8cf46715d5626e1c4df6f39dac7f40394f14a58044432ef.png)

需要注意的是，在 migrations 目录下的部署文件名是有规则的，前缀以数字下划线命名 1\_ 、2\_ ...，这样每次 truffle 部署就可以根据数字去维护合约是否重复部署过（具体可以看 Migrations.sol 合约文件是如何实现的），如果之前部署过，下次在执行部署命令就不会重复执行了。

合约代码如下 ：

    import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
    import "@openzeppelin/contracts/access/Ownable.sol";
    import "@openzeppelin/contracts/utils/math/SafeMath.sol";
    //SPDX-License-Identifier: MIT
    pragma solidity 0.8.11;
    
    contract DaMiToken is ERC20, Ownable {
    
        using SafeMath for uint256;
    
        //项目方地址
        address public projectAddress;
        //手续费千分比 txFeeRatio/1000
        uint256 public txFeeRatio;
        //销毁千分比 burnRatio/1000
        uint256 public burnRatio;
    
        /**
         * - 阅读 openzeppelin 中的 ERC20 源码，在标准 ERC20 基础上，开发以下功能的 ERC20 合约：
         * - 支持项目方增发的功能
         * - 支持销毁的功能
         * - 支持交易收取手续费至项目方配置的地址
         */
        constructor(
            string memory name_,
            string memory symbol_
        ) ERC20(name_, symbol_) {
        }
    
        //增发代币功能,空投
        function mint(address _account ,uint256 _amount) public onlyOwner {
            //新增多少的数量，进行相加
            _mint(_account, _amount);
        }
    
        //销毁代币
        function burn(uint256 _amount) public {
            require(balanceOf(msg.sender) >= _amount,"burn amount exceeds  balances");
            _burn(msg.sender, _amount);
        }
    
        //交易收取手续费至项目方配置的地址
        function transfer(address _to, uint256 _amount) public virtual override returns (bool) {
            //交易数量
            uint256 txFee = _amount.mul(txFeeRatio).div(1000);
            //燃烧掉的数量
            uint256 burnAmount = _amount.mul(burnRatio).div(1000);
            uint256 amount = _amount.sub(txFee).sub(burnAmount);
            //给项目方设置的地址转交易费
            _transfer(msg.sender,projectAddress ,txFee);
            _transfer(msg.sender, _to,amount);
            if (burnAmount > 0) {
                _burn(msg.sender, burnAmount);
            }
            return true;
        }
    
        //设置地址和交易费比例
        function setProjectAddress(address _projectWallet, uint256 _txFeeRatio,uint256 _burnRatio)
            external
            onlyOwner
        {
            projectAddress = _projectWallet;
            txFeeRatio = _txFeeRatio;
            burnRatio = _burnRatio;
        }
    }
    

部署 js 文件代码：

    const DaMiToken = artifacts.require("DaMiToken");
    
    //通过合约部署设置构造函数的 name 和 symbol
    module.exports = function (deployer) {
      deployer.deploy(DaMiToken,"DaMiToken","DaMi-Token");
    };
    

### 三、修改 truffle 配置文件

点开 truffle-config.js 文件，可以看到默认生成了很多配置，非常友好：

![](https://storage.googleapis.com/papyrus_images/a2fa8ce51ab18e38c1f400db3a806b9784cde80644c43f6bf9dd430567e1c5a8.png)

在配置文件中，默认的测试网使用的是 ropsten 网络，但我这里习惯用 rinkeby 测试网，因为最早学习就是接触的 rinkeby ，所以 ETH 也是在这个网上比较多。

接下来，我们以 rinkeby 为例，按照默认的文件，仿照修改一下配置文件：

![](https://storage.googleapis.com/papyrus_images/bfbbb2c6b087dcb70095d21a4ba09f0fd9b9a5e3b4b5be0e8ccf416752c8f6e6.png)

除了上图的配置外，这里提几点需要注意的，以及配置的含义：

第一步，引入依赖 HDWalletProvider

    //安装依赖 @truffle/hdwallet-provider
     cnpm install @truffle/hdwallet-provider
    

![](https://storage.googleapis.com/papyrus_images/dd65b47e7d40d364f53438eea04c2fb02d068f1f5320c18703970eb5a89ad647.png)

这个是通过钱包和链上节点的交互配置，第一个需要传入你部署合约的钱包私钥 privatekey。我们可以通过环境变量对私钥设置，以防上传代码的时候泄露出去。

    //环境变量私钥
    var privateKey = process.env.privateKey;
    

![](https://storage.googleapis.com/papyrus_images/6c2c9af4e7bb42a5cdec8a0c30841c37c4f41e07451c935283471f7ccb9be898.png)

    export privateKey="你的钱包私钥"
    

第二步，需要传入你 infura 的节点 id。 infura 是一个区块链节点的服务商，其实 metamask 背后的默认 rpc 网络节点，也是它家提供的。 官网： [https://infura.io/](https://infura.io/)

自行注册，然后进入后台，点击右上角的 create project ：

![](https://storage.googleapis.com/papyrus_images/9bc513ffeadfe2ed033ca3efa4bc1a5a180db4ea9f7749b9b9fad092ae0c494e.png)

把这个 url 和 project id 复制到我们的配置文件中：

![](https://storage.googleapis.com/papyrus_images/2e9efaab57b7db206b5a4873879b1b2462c03392954e614ee2a50046eb7cf220.png)

切忌，这里的 url 不要用 https 的，因为 rinkeby 会不定时出现以下错误： Error: PollingBlockTracker - encountered an error while attempting to update latest

![](https://storage.googleapis.com/papyrus_images/67899f02fa87d219bda5baffe95dd3e5df23ac6aa0da147d8b24492d1649494e.png)

github给的解决方案：[https://github.com/trufflesuite/truffle/issues/3357](https://github.com/trufflesuite/truffle/issues/3357)

同理，我们这里的 infuraid 也可以采用和私钥的配置形式，进行 export 到系统的环境变量中去。这里不重复赘述了。

到此，完整的配置文件：

    
    const HDWalletProvider = require('@truffle/hdwallet-provider');
    //环境变量私钥
    var privateKey = process.env.privateKey;
    //环境变量 infuraid
    var infuraId = process.env.infuraId;
    
    module.exports = {
    
      networks: {
        rinkeby: {
          // 钱包的节点提供服务
          provider: () => new HDWalletProvider(privateKey, "wss://rinkeby.infura.io/ws/v3/" + infuraId),
          gas: 10000000,    //部署接受的最大消耗 gas 
          gasPrice: 15000000000, //gas的价格
          network_id: 4,   //rinkeby的网络id
          timeoutBlocks: 40000, //读取区块链的数据超时时间
        },
      },
    };
    

除了以上测试网的配置，还可以把优化的配置打开，编译器的版本设置对，根据自己合约的语法设定，设置成下面的默认就好，enabled 从 false 改成 true：

![](https://storage.googleapis.com/papyrus_images/ae1ef1b86317779578d8a4eddb283e988e2e3d3afe9f047ff8a543fb9a64aff2.png)

这里的 runs ，值越大，编译出来的文件越大，部署的成本越高，但执行效率相对好一些。越小则相反。默认值即可。

完整的配置文件：

    
    const HDWalletProvider = require('@truffle/hdwallet-provider');
    //环境变量私钥
    var privateKey = process.env.privateKey;
    //环境变量 infuraid
    var infuraId = process.env.infuraId;
    
    module.exports = {
    
      networks: {
        rinkeby: {
          // 钱包的节点提供服务
          provider: () => new HDWalletProvider(privateKey, "wss://rinkeby.infura.io/ws/v3/" + infuraId),
          gas: 10000000,    //部署接受的最大消耗 gas 
          gasPrice: 15000000000, //gas的价格
          network_id: 4,   //rinkeby的网络id
          timeoutBlocks: 40000, //读取区块链的数据超时时间
        },
      },
      mocha: {
        // timeout: 100000
      },
    
      compilers: {
        solc: {
          version: "0.8.11",      // Fetch exact version from solc-bin (default: truffle's version)
          // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
          settings: {          // See the solidity docs for advice about optimization and evmVersion
            optimizer: {
              enabled: true,
              runs: 200        //值越大，编译出来的文件越大，部署的成本越高，但执行效率相对好一些。越小则相反。默认值即可
            },
            evmVersion: "london"
          }
        }
      },
    };
    

好，到这里，我们就可以尝试部署合约了，过程中肯定会遇到问题，来逐一解决。

### 四、truffle 部署合约

回到命令行中，我们只需要输入以下命令，指定 truffle 部署网络，便可以开始部署：

    truffle migrate --network rinkeby
    

![](https://storage.googleapis.com/papyrus_images/104590ccfd3a2b73527c6d81b2e59d11d616476f4c5d3b1d5d2908687c3cc407.png)

可以看到，缺少依赖了，npm安装一下，然后继续安装：

![](https://storage.googleapis.com/papyrus_images/9b80ba1a6aa9b1c68fb222bb0bc8628c5501de6d6292278dbf1d982235fe4139.png)

这里就不一个个截图了，最终安装的依赖如下：

    //涉及到 web3 的
    npm install web3-provider-engine
    npm install ethereumjs-abi
    //这个是我自己引用的 openzeppelin 依赖 
    npm install @openzeppelin/contracts
    

当依赖问题解决，再次执行 truffle migrate 后：

![](https://storage.googleapis.com/papyrus_images/4e41aaeb420e2ad98b53a146b98b3ed647a87d11427876b4aa7df25d07595eeb.png)

这里提示一下，如果你的部署合约名称没变，但是合约代码有变化，还执行 truffle migrate 后，不会对合约重新部署：

![](https://storage.googleapis.com/papyrus_images/25c1e46ea91af18945cd134698bce0cd05c026aea1c1f33e393858c5316e503c.png)

此时，如果需要重新部署，需要通过一下命令进行部署：

    truffle migrate --network rinkeby --reset
    

成功：

![](https://storage.googleapis.com/papyrus_images/4e41aaeb420e2ad98b53a146b98b3ed647a87d11427876b4aa7df25d07595eeb.png)

我们通过 etherscan 查看下部署的合约：

![](https://storage.googleapis.com/papyrus_images/8519a1b137c7b6a87d966d3b589bf1577747f0898be3d3cd5e03f4b253db6c80.png)

合约成功部署成功了，但此时的代码是未开源的，点击 contract ，可以看到如下，都是 16 进制码：

![](https://storage.googleapis.com/papyrus_images/a2c160cd465b3cfbeb49d3e4394fdc0af8c7d355b8a4371152725ee525417bbe.png)

### 五、truffle 开源的两种方式

**1、solidity contract flattener 开源**

![](https://storage.googleapis.com/papyrus_images/ed8cec73d85e784901942a94670d750b90e24b0e3877879bd71a987c0c1a0670.png)

安装好这个插件，我们可以选中已经部署的合约源文件，点击下面的生成文件：

![](https://storage.googleapis.com/papyrus_images/9facb00d4a1951573c1dc0c234ad9caf8fd4cf453c6321cb0cc735f0cf7d24e1.png)

它会自动帮你把合约的 import 文件自动压缩到一个 solidity 文件中：

![](https://storage.googleapis.com/papyrus_images/92f3d953a83be589494d804e1005f36e39604b57030bf56957ad34fe8ff51c54.png)

然后我们可以复制右侧的所有文件，到 etherscan 上进行验证开源。

点击下图的验证并发布：

![](https://storage.googleapis.com/papyrus_images/f628f31d7e4cf66ea9e38a7238efaeebb1e4d3e1a225b766682ea02e27879484.png)

按照大家自己的合约进行配置

![](https://storage.googleapis.com/papyrus_images/df46d7e229125dfa6fed23fc659957eefcb0fcc53b6ddf17f04dcbdb8a7b72f2.png)

选择：

把刚才生成的多文件压缩成一个文件的源码粘贴

![](https://storage.googleapis.com/papyrus_images/178085aee1986ca99433eaa20ab74f7231b6cfc44d63e6a5ad8fb8c2a1c74120.png)

过来：

点击验证并发布：

![](https://storage.googleapis.com/papyrus_images/eb707ef39959cd3081bfc4584ed6724a1d517b708c07885511d5ab22dcfc6c4e.png)

以上便是用 vscode 插件的全部开源验证流程。别忘了，优化的点一定要和 truffle 配置文件中的对应，否则有可能验证不通过。

**2、truffle-plugin-verify 开源**

上面的流程很繁琐，每次还需要手动上传，而且最终生成的开源代码在 etherscan 上是以单文件显示的，阅读并不友好。

既然用了 truffle ，就有自动化的方式来进行验证开源，且生成的文件还是多文件的。接下来，展示！用到的插件官网 github： [https://github.com/rkalis/truffle-plugin-verify](https://github.com/rkalis/truffle-plugin-verify)

先安装好这个插件 truffle-plugin-verify ：

    npm install truffle-plugin-verify@latest
    

![](https://storage.googleapis.com/papyrus_images/6ec50376f74ad664413374404dca1929d24a49491cb24914b93a84d80f8e4170.png)

安装完成后，继续修改我们的 truffle 配置文件：

![](https://storage.googleapis.com/papyrus_images/28581a576a523aef0730049158f1104799949a1f3e5fda8cb998343d5abdccc4.png)

这个插件的原理，是用了 etherscan 的 api 进行了自动化部署并且验证，所以加上如上图所示后，你需要知道 2 件事。

第一点，关于 proxy ，是否要配置，这里决定着你访问 [etherscan.io](http://etherscan.io) 这个网站是否走代理，国内的网络，必然是需要配置的，不配置本地代理访问，最终会报错： Failed to connect to Etherscan API at url [https://api-rinkeby.etherscan.io/api](https://api-rinkeby.etherscan.io/api)

![](https://storage.googleapis.com/papyrus_images/039820e18f52490bd0261306f2e482e89796f739e03e85015d804c04845faaa1.png)

配置完代理后，由于源码依赖于 tunnel ，所以还需要补个依赖，要不还是访问不了：

![](https://storage.googleapis.com/papyrus_images/3e6a81a1b45af3f24395e085d48d4bcd5fd853296dc53cdf870a566b8c7e0f41.png)

第二点，关于 etherscan 的 api key 申请，可以自行去官网申请： [https://etherscan.io/myapikey](https://etherscan.io/myapikey)

![](https://storage.googleapis.com/papyrus_images/9ece37773e64b40260421f1915a5e3a3d8022c2fdde5f646488041e4ead2346e.png)

依然是通过环境变量的方式，把 key 导入。

最终 truffle 配置文件如下：

    
    const HDWalletProvider = require('@truffle/hdwallet-provider');
    //环境变量私钥
    var privateKey = process.env.privateKey;
    //环境变量 infuraid
    var infuraId = process.env.infuraId;
    //环境变量 etherscan 的 apikey 
    var etherscanApiKey = process.env.etherscanApiKey;
    module.exports = {
    
      networks: {
        rinkeby: {
          // 钱包的节点提供服务
          provider: () => new HDWalletProvider(privateKey, "wss://rinkeby.infura.io/ws/v3/" + infuraId),
          gas: 10000000,    //部署接受的最大消耗 gas 
          gasPrice: 15000000000, //gas的价格
          network_id: 4,   //rinkeby的网络id
          timeoutBlocks: 40000, //读取区块链的数据超时时间
        },
      },
      mocha: {
        // timeout: 100000
      },
    
      compilers: {
        solc: {
          version: "0.8.11",      // Fetch exact version from solc-bin (default: truffle's version)
          // docker: true,        // Use "0.5.1" you've installed locally with docker (default: false)
          settings: {          // See the solidity docs for advice about optimization and evmVersion
            optimizer: {
              enabled: true,
              runs: 200        //值越大，编译出来的文件越大，部署的成本越高，但执行效率相对好一些。越小则相反。默认值即可
            },
            evmVersion: "london"
          }
        }
      },
      
      plugins: ['truffle-plugin-verify'],
    
      verify: {
        proxy: {
          host: '127.0.0.1',
          port: '41091'
        }
      },
    
      api_keys: {
        etherscan: etherscanApiKey
      }
    };
    

以上，配置完成后，回到命令行，执行以下命令：

    //DaMiToken是合约名称，--debug可以看到具体错误信息
    truffle run verify  DaMiToken --network rinkeby --debug
    

执行完毕后，可以看到成功：

![](https://storage.googleapis.com/papyrus_images/002c4f679a9bfd2fc541f0e2dd944f8bccae8b60ff631834b289e94f374a7555.png)

打开 etherscan 的地址：

![](https://storage.googleapis.com/papyrus_images/89957801713cf3b24c85e7738c753d768142ad07a867736636c6dd2a0b3a879a.png)

可以成功看到，合约已经被整整齐齐的分成了多个文件，并且成功开源！

至此，教程完毕。

好，以上就是完整的分享了....希望大家可以有所收获，有问题也欢迎随时交流探讨！ 💎 |币圈萌新|NFT学习中|成为科学家的路上|💎

我的Twitter：

[@dami](https://twitter.com/dami16z)

---

*Originally published on [dami.eth](https://paragraph.com/@yidakoumi/truffle-solidity-etherscan)*
