数字商品指南系列第三篇:编写智能合约并编译部署

Posted 洋滔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数字商品指南系列第三篇:编写智能合约并编译部署相关的知识,希望对你有一定的参考价值。

文章目录

前言

智能合约为数字藏品提供技术支持,它可以定义数字藏品的简称、全称、发行数量、铸造方法、版权税等等,所有关于数字藏品的现实信息都离不开智能合约。

这篇教程我们开始编写智能合约,智能合约标准我们使用的是ERC-721(非同质化代币),接着使用HardHat来编译,编译是为部署到区块链上做准备,最后我们会把智能合约部署到Rinkeby测试网络上。

警告

本指南仅供学习交流使用,不得用于违法用途,如果侵犯了国家法律,责任自负。

完善项目结构

为了让我们的项目看起来有迹可循,需要在我们的项目根目录下创建两个文件夹,一个文件夹存放智能合约代码,一个文件夹存放执行合约相关任务的脚本文件,使用cmd命令进入项目根目录,然后输入命令

mkdir contracts

按下回车键执行命令

mkdir scripts

按下回车键执行命令
contracts将存放合约代码文件,scripts将存放执行合约的脚本文件

编写合约代码

选择一款编辑器打开项目文件夹my-contract,在contracts文件夹下新建个文件,名字叫MyContract.sol,下边是我们的合约代码,我们使用了OpenZeppelin的ERC-721标准库,复制并粘贴到你的MyContract.sol文件

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
contract MyContract is ERC721URIStorage 
    using Counters for Counters.Counter;
    Counters.Counter private currentTokenId;

    constructor() ERC721("OAXTOKEN", "OAX") 

    function mintTo(address recipient, string memory tokenURI)
    public
    returns (uint256)
    
        currentTokenId.increment();
        uint256 newItemId = currentTokenId.current();
        _safeMint(recipient, newItemId);
        _setTokenURI(newItemId, tokenURI);
        return newItemId;
    

看起来有点难以理解?别慌,让我们一行接着一行看下去。

  • 第一行我们定义了使用的Solidity(智能合约语言)版本号。
  • 接下来从第4-6行引入了三个openzeppelin的关键类文件,来快速实现ERC-721的标准,而不需要我们再自己去一行一行的写合约代码。
    ERC721.sol,包含了实现以太坊ERC-721标准的所有方法,我们的合约将会继承它,这样就保证了我们的合约是有效的。
  • Counters.sol,提供计数器,只能递增或递减一次。我们的智能合约使用一个计数器来跟踪铸造的数字藏品总数,并在我们的新数字藏品上上设置唯一的ID。(使用智能合约创建的每个数字藏品都必须分配一个唯一的ID,我们的唯一ID仅由存在的NFT总数决定,例如,我们使用智能合约创建的第一个的ID为“1”,第二个的ID就是“2”,等等。)
  • ERC721URIStorage,为我们提供了公共方法setTokenURI来设置每个藏品对应的元数据信息,从而让我们的数字藏品有了现实意义,比如一张门票、一个视频、一张画。
  • 下边就是我们合约代码的正文了,只有一个计数器(currentTokenId),一个构造函数方法(constructor),和一个铸造方法(mintTo)。构造函数共有两个变量,第一个是我们数字藏品的全称(OAXTOKEN),第二个是简称(OAX),给你的数字藏品起一个你喜欢的全称和简称,改动这两项就可以。铸造方法也有两个变量,第一个是个以太坊地址,用以接收将要铸造成的藏品,第二个是元数据信息,一般的做法是设置一个网址,网址里存储信息,设置完成这两个参数后,会返回藏品的唯一ID给调用者。

在写完合约代码后,我们的项目结构会是这个样子

编译合约

在编译合约前,请你确保按照第二篇教程已经完成所有配置,我们需要更新hardhat.config.js,代码如下,复制粘贴到你的文件里。

/**
 * @type import('hardhat/config').HardhatUserConfig
 */

require('dotenv').config();
require("@nomiclabs/hardhat-ethers");
const  API_URL, PRIVATE_KEY  = process.env;

module.exports = 
  solidity: "0.8.1",
  defaultNetwork: "rinkeby",
  networks: 
    hardhat: ,
    rinkeby: 
      url: API_URL,
      accounts: [`0x$PRIVATE_KEY`]
    ,
    ethereum: 
      chainId: 1,
      url: API_URL,
      accounts: [`0x$PRIVATE_KEY`]
    ,
  ,

通过设置配置项,我们定义了编译合约的solidity版本,区块链网络类型,接口地址和付费的地址私钥。
现在我们只需要执行编译命令就可以了,打开项目根目录my-contract,输入以下命令

npx hardhat compile

如果命令运行正常,会有如下提示

C:\\Users\\ThinkPad\\my-contract>npx hardhat compile
Compiling 1 file with 0.8.1
Solidity compilation finished successfully

部署合约

最后一步就是把合约部署到区块链网络上,我们需要使用javascript写一个简单的部署脚本,然后使用HardHat运行它。
在scripts目录下,新建文件deploy.js,复制下边代码到文件中

async function main() 
    const [deployer] = await ethers.getSigners();
    const NFT = await ethers.getContractFactory("MyContract");
    await NFT.deploy();
    console.log('合约地址:',NFT.address);

main()
    .then(() => process.exit(0))
    .catch((error) => 
        console.error(error);
        process.exit(1);
    );

部署脚本将会从MyContract.sol拿到编译过的合约代码,然后部署到网络上,如果部署成功我们会看到部署后的合约地址。
在项目根目录下,使用HardHat执行部署脚本

npx hardhat --network rinkeby run scripts/deploy.js
C:\\Users\\ThinkPad\\my-contract>npx hardhat --network rinkeby run scripts/deploy.js
合约地址: 0x116Ca6682A03547BffF8Fa8E12Efd301872a1383

复制合约地址到以太坊rinkeby区块浏览器去查看刚才我们部署的合约,如果可以查到恭喜你,顺利为你的数字藏品部署了智能合约。

总结

目前为止,我们完成了智能合约的编写、编译、部署,在下篇教程里我们会开始铸造我们的数字藏品,方法是通过调用合约的mintTo方法,与此同时使用IPFS(去中心化的文件存储)绑定元数据到每个铸造出的数字藏品。

捐赠渠道

本教程是由本人自发的项目,如果你要支持我,请通过支付宝发送口令红包,将口令发送到我的邮箱351107490@qq.com,捐赠仅凭个人意愿,感谢支持。

Web3 开发系列教程—创建你的第一个智能合约部署第一个智能合约

如果你是区块链开发的新手并且不知道从哪里开始,或者你只是想了解如何部署智能合约并与之交互,那么本指南适合你。 我们将介绍使用虚拟钱包 (Metamask)、Solidity、Hardhat 和 Alchemy 在 Goerli 测试网络上创建和部署一个简单的智能合约(如果你还不明白其中的任何含义,请不要担心,我们将 解释一下!)。

创建和部署智能合约

第 1 步:连接到以太坊网络

有很多方法可以向以太坊链发出请求。 为简单起见,我们将在 Alchemy 上使用免费帐户(如果你还没有 Alchemy 帐户,请点击在此处免费注册),这是一个区块链开发平台和 API,允许我们与以太坊链进行通信,而无需运行我们自己的节点。 该平台还具有用于监视和分析的开发人员工具,我们将在本教程中利用这些工具来了解我们的智能合约部署中的幕后情况。

第 2 步:创建应用和 API 密钥

创建 Alchemy 帐户后,你可以通过创建应用程序来生成 API 密钥。 这将允许我们向 Goerli 测试网络发出请求。 如果你不熟悉测试网,请查看官方给出的指南

将鼠标悬停在导航栏中的“应用程序”上并单击“创建应用程序”,导航到 Alchemy 仪表板中的“创建应用程序”页面:

将你的应用命名为“Hello World”,提供简短描述,为环境选择“Staging”(用于你的应用记账),然后为你的网络选择“Goerli”。

仔细检查你是否选择了 Goerli 测试网!

点击“创建应用程序”,就是这样! 你的应用程序应显示在下表中。

第 3 步:创建以太坊账户(地址)

我们需要一个以太坊账户来发送和接收交易。 在本教程中,我们将使用 Metamask,这是浏览器中的一个虚拟钱包,用于管理你的以太坊账户地址。 如果你想了解更多关于以太坊交易如何运作的信息,请查看以太坊基金会的这个页面。

你可以在此处免费下载和创建 Metamask 帐户。 当你正在创建帐户时,或者如果你已经有帐户,请确保切换到右上角的“Goerli 测试网络”(这样我们就不会处理真钱)。

第 4 步:从 Faucet 中添加 ether

为了将我们的智能合约部署到测试网络,我们需要一些虚假的 Eth。 要获取 Eth,你可以前往 Goerli 水龙头并输入你的 Goerli 帐户地址,然后单击“Send Me Eth”。 由于网络流量,可能需要一些时间才能收到你的虚假 Eth。 (在撰写本文时,大约需要 30 分钟。)不久之后,你应该会在你的 Metamask 帐户中看到 Eth!

第 5 步:检查余额

为了仔细检查我们的余额,让我们使用 Alchemy 的 composer 工具发出一个 eth_getBalance 请求。 这将返回我们钱包中的 Eth 数量。

输入你的 Metamask 帐户地址并单击“发送请求”后,应该会看到如下所示的响应:

"jsonrpc": "2.0", "id": 0, "result": "0x2B5E3AF16B1880000"

注意:这个结果是 wei 而不是 eth。 魏被用作以太的最小面额。 wei到eth的换算为:1 eth = 10^18 wei。 因此,如果我们将 0x2B5E3AF16B1880000 转换为十进制,我们会得到 5*10^18,它等于 5 eth。 呸! 我们的假钱就在那里🤑。

第 6 步:初始化我们的项目

mkdir hello-world
cd hello-world

首先,我们需要为我们的项目创建一个文件夹。 导航到你的命令行并键入:

现在我们在项目文件夹中,我们将使用 npm init 来初始化项目。 如果你还没有安装 npm,请按照这些说明进行操作(我们还需要 Node.js,所以也下载它!)。

npm init # (or npm init --yes)

你如何回答安装问题并不重要,以下是我们的做法以供参考:

package name: (hello-world)
version: (1.0.0)
description: hello world smart contract
entry point: (index.js)
test command:
git repository:
keywords:
author:
license: (ISC)

About to write to /Users/.../.../.../hello-world/package.json:


   "name": "hello-world",
   "version": "1.0.0",
   "description": "hello world smart contract",
   "main": "index.js",
   "scripts": 
      "test": "echo \\"Error: no test specified\\" && exit 1"
   ,
   "author": "",
   "license": "ISC"

第 7 步:下载 Hardhat

Hardhat 是一个用于编译、部署、测试和调试以太坊软件的开发环境。 在部署到实时链之前,它可以帮助开发人员在本地构建智能合约和 dApp。

在我们的 hello-world 项目中运行:

npm install --save-dev hardhat

第 8 步:创建 Hardhat 项目

在我们的 hello-world 项目文件夹中,运行:

npx hardhat

然后,你应该会看到一条欢迎消息和用于选择你想要执行的操作的选项。 选择“创建一个空的 hardhat.config.js”:

888    888                      888 888               888
888    888                      888 888               888
888    888                      888 888               888
8888888888  8888b.  888d888 .d88888 88888b.   8888b.  888888
888    888     "88b 888P"  d88" 888 888 "88b     "88b 888
888    888 .d888888 888    888  888 888  888 .d888888 888
888    888 888  888 888    Y88b 888 888  888 888  888 Y88b.
888    888 "Y888888 888     "Y88888 888  888 "Y888888  "Y888

👷 Welcome to Hardhat v2.0.11 👷‍

What do you want to do? …
Create a sample project
❯ Create an empty hardhat.config.js
Quit

这将为我们生成一个 hardhat.config.js 文件,我们将在其中为我们的项目指定所有设置(在第 13 步中)。

第 9 步:添加项目文件夹

为了让我们的项目井井有条,我们将创建两个新文件夹。 在命令行中导航到 hello-world 项目的根目录并键入:

mkdir contracts
mkdir scripts
  • contract: 是我们保存 hello world 智能合约代码文件的地方
  • scripts: 是我们保存脚本以部署和与我们的合约交互的地方

第 10 步:编写我们的合约

你可能会问自己,我们到底什么时候要编写代码? 好吧,我们到了,第 10 步😄

在你最喜欢的编辑器中打开 hello-world 项目(我喜欢 VSCode)。 智能合约是用一种称为 Solidity 的语言编写的,我们将使用它来编写我们的 HelloWorld.sol 智能合约。‌

  1. 导航到“contracts”文件夹并创建一个名为 HelloWorld.sol 的新文件
  2. 下面是我们将在本教程中使用的来自以太坊基金会的 Hello World 智能合约示例。 将以下内容复制并粘贴到你的 HelloWorld.sol 文件中,并确保阅读注释以了解此合约的作用:
// 使用语义版本控制指定 Solidity 的版本。
// 了解更多:https://solidity.readthedocs.io/en/v0.5.10/layout-of-source-files.html#pragma
pragma solidity >=0.7.3;

// 定义一个名为“HelloWorld”的合约。
// 合约是功能和数据(其状态)的集合。 部署后,合约将驻留在以太坊区块链上的特定地址。 了解更多:https://solidity.readthedocs.io/en/v0.5.10/structure-of-a-contract.html
contract HelloWorld 

   // 调用更新函数时发出
   // 智能合约事件是你的合约将区块链上发生的事情传达给你的应用程序前端的一种方式,它可以“监听”某些事件并在它们发生时采取行动。
   event UpdatedMessages(string oldStr, string newStr);

   // 声明一个`string`类型的状态变量`message`。
   // 状态变量是其值永久存储在合约存储中的变量。 关键字 `public` 使变量可以从合约外部访问,并创建一个函数,其他合约或客户端可以调用该函数来访问该值。
   string public message;

   // 与许多基于类的面向对象语言类似,构造函数是一种特殊函数,仅在合约创建时执行。
   // 构造函数用于初始化合约的数据。 了解更多:https://solidity.readthedocs.io/en/v0.5.10/contracts.html#constructors
   constructor(string memory initMessage) 

      // 接受字符串参数 `initMessage` 并将值设置到合约的 `message` 存储变量中)。
      message = initMessage;
   

   // 一个接受字符串参数并更新“消息”存储变量的公共函数。
   function update(string memory newMessage) public 
      string memory oldMsg = message;
      message = newMessage;
      emit UpdatedMessages(oldMsg, newMessage);
   

这是一个超级简单的智能合约,它在创建时存储一条消息,并且可以通过调用更新函数来更新。

第 11 步:将 Metamask 和 Alchemy 连接到你的项目

我们已经创建了一个 Metamask 钱包、Alchemy 账户,并编写了我们的智能合约,现在是时候连接这三者了。

从你的虚拟钱包发送的每笔交易都需要使用你唯一的私钥进行签名。 为了向我们的程序提供此权限,我们可以将我们的私钥(和 Alchemy API 密钥)安全地存储在环境文件中。

首先,在你的项目目录中安装 dotenv 包:

npm install dotenv --save

这是一个超级简单的智能合约,它在创建时存储一条消息,并且可以通过调用更新函数来更新。

你的环境文件必须命名为 .env,否则将不会被识别为环境文件。请勿将其命名为 process.env 或 .env-custom 或其他任何名称。

你的 .env 应如下所示:

API_URL = "https://eth-goerli.alchemyapi.io/v2/your-api-key"
PRIVATE_KEY = "your-metamask-private-key"

PRIVATE_KEY 私钥的获取方式参考这篇文章

API_URL 的获取方式可以参考下图:

为了将这些连接到我们的代码,我们将在第 13 步的 hardhat.config.js 文件中引用这些变量。

第 12 步:安装 Ethers.js

Ethers.js 是一个库,它通过使用更友好的方法包装标准** JSON-RPC **方法,使交互和向以太坊发出请求变得更加容易。

Hardhat 使的集成插件变得非常容易,以获得额外的工具和扩展功能。 我们将利用 Ethers 插件进行合约部署(Ethers.js 有一些超级干净的合约部署方法)。
在你的项目目录的命令行中输入如下命令:

npm install --save-dev @nomiclabs/hardhat-ethers "ethers@^5.0.0"

在下一步中,我们还需要在我们的 hardhat.config.js 中使用以太币。

第 13 步:更新 hardhat.config.js

到目前为止,我们已经添加了几个依赖项和插件,现在我们需要更新 hardhat.config.js 以便我们的项目了解所有这些。

将你的 hardhat.config.js 更新为如下所示内容:

/**
* @type import('hardhat/config').HardhatUserConfig
*/

require('dotenv').config();
require("@nomiclabs/hardhat-ethers");

const  API_URL, PRIVATE_KEY  = process.env;

module.exports = 
   solidity: "0.7.3",
   defaultNetwork: "goerli",
   networks: 
      hardhat: ,
      goerli: 
         url: API_URL,
         accounts: [`0x$PRIVATE_KEY`]
      
   ,

第 14 步:编译我们的合约

为了确保到目前为止一切正常,让我们编译我们的合约。 编译任务是内置安 hardhat 的任务之一。

从命令行运行:

npx hardhat compile

你可能会收到有关源文件中未提供 SPDX 许可证标识符的警告,但无需担心!

第 15 步:编写我们的部署脚本

现在我们的合约已经写好并且我们的配置文件已经准备好了,是时候编写我们的合约部署脚本了。

导航到 /scripts 文件夹并创建一个名为 deploy.js 的新文件,向其中添加以下内容:

async function main() 
   const HelloWorld = await ethers.getContractFactory("HelloWorld");

   // 开始部署,返回一个解析为合约对象的 promise
   const hello_world = await HelloWorld.deploy("Hello World!");
   console.log("Contract deployed to address:", hello_world.address);


main()
  .then(() => process.exit(0))
  .catch(error => 
    console.error(error);
    process.exit(1);
  );

其中:

const HelloWorld = await ethers.getContractFactory("HelloWorld");

ethers.js 中的 ContractFactory 是用于部署新智能合约的抽象,因此 HelloWorld 这里是我们的 hello world 合约实例的工厂。 使用 hardhat-ethers 为 ContractFactory 和 Contract 添加插件时,实例默认连接到第一个签名者(所有者)。

const hello_world = await HelloWorld.deploy();

在 ContractFactory 上调用 deploy() 将启动部署,并返回解析为 Contract 对象的 Promise。 这是为我们的每个智能合约功能提供方法的对象。

第 16 步:部署我们的合约

我们终于准备好部署我们的智能合约了! 切换到命令行并运行:

npx hardhat run scripts/deploy.js --network goerli

然后,你应该会看到如下内容:

Contract deployed to address: 0x6cd7d44516a20882cEa2DE9f205bF401c0d23570

请复制并粘贴此地址以将其保存在某处,因为我们将在以后的教程中使用此地址,因此你不想丢失它。

如果我们去 Goerli etherscan 并搜索我们的合约地址,我们应该能够看到它已经成功部署。 交易将如下所示:

发件人地址应该与你的 Metamask 帐户地址匹配,收件人地址将显示“Contract Creation”,但如果我们点击交易,我们将在收件人字段中看到我们的合同地址:

至此,恭喜你! 你已经在以太坊链上部署了一个智能合约🎉

要了解幕后发生的事情,让我们导航到 Alchemy 仪表板中的 Explorer 选项卡。 如果你有多个 Alchemy 应用程序,请确保按应用程序过滤并选择“Hello World”。

在这里,你将看到当我们调用 deploy() 函数时,Hardhat/Ethers 在后台为我们进行的一些 JSON-RPC 调用。 这里要提到的两个重要的问题是 eth_sendRawTransaction,它是实际将我们的合约写入 Ropsten 链的请求,以及 eth_getTransactionByHash,它是一个请求读取我们的交易信息给定哈希(发送交易时的典型模式)。

要了解有关发送交易的更多信息,你可以从这里查看有关使用 Web3 发送交易的教程。

以上是关于数字商品指南系列第三篇:编写智能合约并编译部署的主要内容,如果未能解决你的问题,请参考以下文章

Web3 开发系列教程—创建你的第一个智能合约部署第一个智能合约

Web3 开发系列教程—创建你的第一个智能合约部署第一个智能合约

如何使用remix编写solidity智能合约并部署上链

用python如何实现智能合约?

区块链-智能合约工程师第三篇:Solidity进阶

智能合约语言 Solidity 教程系列3 - 函数类型