智能合约Smart Contract技术详解

news2024/11/17 0:28:08

文章目录

  • 合约编写
    • 基本介绍
    • 构造方法
    • ipfs
    • mint
    • 提现
    • 白名单
      • 合约
      • 前端
  • 部署
  • 验证合约代码
  • 前端和合约交互
    • 准备工作
    • 获取已经mint了的数量
    • mint

合约编写

建议读者先了解下solidity,这里推荐CryptoZombies,还是比较详细的。
ok当你大概知道自己在做什么之后,我们就可以开始编写智能合约了,首先我们需要一个编译器,我是用的web ide remix,当然他也有桌面版,使用起来都是一样的,web版本的话记得做备份,如果仅靠浏览器缓存来做备份的话,很容易吃亏找不到代码了等会。

基本介绍

先看几个关键常量

	uint public constant MAX_TOKENS = 2000;
    uint private constant TOKENS_RESERVED = 4;
    
    //normal whitelist price
    uint public white_price = 0.008 ether;
    //normal price
    uint public price = 0.015 ether;

    uint256 public constant MAX_MINT_PER_TX = 4;

    bool public isSaleActive = false;
    uint256 public totalSupply;
    mapping(address => uint256) private mintedPerWallet;

    string public baseUri;
    string public baseExtension = ".json";

MAX_TOKENS指的是该合约最大能mint的数量
white_price指的是白名单价格(如果你的合约有白名单的话),注意这里价格会带上ether关键字后缀,表示每一个nft的单价
price指的是普通价格
MAX_MINT_PER_TX表示一个账户能mint的数量(如果你的合约有这个需求的话)
isSaleActive表示当前合约是否可以mint的状态
mintedPerWallet是一个map,记录了每一个账户mint的数量,对应MAX_MINT_PER_TX.

构造方法

    constructor() ERC721("the smart contract's name", "SYMBOL") {
        baseUri = "ipfs://xxxxxxxxxxxxxxxxx/";
        whiteRootOG = xxxxxxxx;
        whiteRootNormal = xxxxxxxxx;
        // for(uint256 i = 1; i <= TOKENS_RESERVED; ++i) {
        //     _safeMint(msg.sender, i);
        // }
        // totalSupply = TOKENS_RESERVED;
    }

构造方法第一个参数为合约名字,baseUri为ipfs的json地址,白名单稍后会讲解,构造方法这里有些合约会直接构造出来几个给团队自己使用,看自己需求。

ipfs

ipfs全名InterPlanetary File System, 是一个分布式的web,实现了点到点超媒体协议,可以让我们的互联网速度更快,更加安全, 并且更加开放。 理论上的话未来可以取代http。如果我们传上去一个相同的图片,得到的ipfs链接是一样的,所以ipfs比http更能确保文件的安全性,而且由于是p2p的形式去下载,所以下载速度相较http也会快速很多。
ok,简单介绍了下ipfs,那么我们该如何使用呢?
ipfs上传工具目前还是比较多的,我这里建议使用ipfs desktop,像pinata也很方便,但普通用户都有存储限制。
首先我们上传一个包含图片的文件夹以后获取到一个ipfs的cid地址,然后我们就得生成一个json去告诉用户,你的nft的图片,描述,名字等。
类似:
在这里插入图片描述

tips:如果你要查看你的ipfs上传的文件,你可以使用这个链接:https://ipfs.io/ipfs/your-ipfs-cid/
把your-ipfs-cid换成你的文件cid即可。

当然一般nft使用场景里会有很多很多nft,那么这里就需要把生成json文件脚本化比较方便了,其实就是一个string字符串写入生成文件,可以用java,python等,这里就不贴了。

然后刚才生成的json文件夹必须取名为metadata,然后这个metadata文件夹的ipfs cid即是我们合约里要用到的baseUri,当然这个baseUri也是可以动态替换的,这个后面会详解,主要用在一些一开始给到用户的nft是未揭秘,然后解密后的这种场景。

mint

function mint(uint256 _numTokens, bytes32[] calldata whitelist_og_proof, bytes32[] calldata whitelist_normal_proof) external payable {
        require(isSaleActive, "The sale is paused.");
        require(_numTokens <= MAX_MINT_PER_TX, "You cannot mint that many in one transaction.");
        require(mintedPerWallet[msg.sender] + _numTokens <= MAX_MINT_PER_TX, "You cannot mint that many total.");
        uint256 curTotalSupply = totalSupply;
        require(curTotalSupply + _numTokens <= MAX_TOKENS, "Exceeds total supply.");
		require(_numTokens * price <= msg.value, "Insufficient funds.");

        for(uint256 i = 1; i <= _numTokens; ++i) {
            _safeMint(msg.sender, curTotalSupply + i);
        }
        mintedPerWallet[msg.sender] += _numTokens;
        totalSupply += _numTokens;
    }

这里注意函数后缀加了一个关键词payable,意思就是这个是支付函数。require方法有点类似其他语言中的捕捉异常,如果条件为false的话,则直接报错,错误信息为后面的string。
那么我们来简单的看下这个mint函数,忽略白名单相关形参,第一行示意如果合约现在状态isSaleActive为false的话,那么现在无法交易。
第二行就是控制交易数量,如果用户申请了超过每个人最大能mint的数量的话,直接报错。
第三行是控制每个人能mint的数量,会去map里去读取每个人mint的数量,不能超哥一个人能mint的最大数量。
下面就是控制最大数量了,如果你发行200个nft,现在已经被mint了199个,这个时候你还要去mint2个话,就会直接报错Exceeds total supply。
当然以上这些情况都需要根据你实际合约的需求去自定义。

然后就是去判断价格,这里要注意一点,_safeMint方法是可以直接去mint的,msg.sender指的是发起该交易的用户的account,所以如果你要去给nft设置价格的话,必须在_safeMint前去做一道价格的关卡来控制价格,如果有白名单等价格不一样的话,这里都要去做价格限制。合约和支付宝、微信支付等不一样的地方就是他的设置价格是在这里进行条件判断设置的。

提现

    function withdrawAll() external payable onlyOwner {
        uint256 balance = address(this).balance;
        uint256 balanceOne = balance * 0.5;
        uint256 balanceTwo = balance * 0.5;
        ( bool transferOne, bool transferTwo ) = payable(msg.sender1, msg.sender2).call{value: balanceOne, balanceTwo}("");
        require(transferOne, "Transfer failed.");
    }

这里注意有一个关键字onlyOwner,意思就是只有创建合约的账号可以调用的方法。这里我们把收益五五分成,分给了msg.sender1和msg.sender2,当然这里也可以改成只给一个正好,这里根据自己的需求来自定义即可。

白名单

在一些nft中,会有一部分用户的mint价格和普通用户的mint价格不一样,所以我们要存下这部分用户的account id,然后如果是这个用户群体的话,那么前面控制价格那里可以针对这些用户进行不一样的价格控制操作。
正常逻辑,我们放一个数组来存放这些account,但是如果智能合约发布以后,我们要去修改这个白名单账号群体的增删改查,如果是一个数组的话,那就很麻烦,而且批量操作如果写的不当的话,就会多出很多gas费,而且数组的话存放空间也会变大,完全不适合这种动态化的case,所以我们只能另外寻找方法来解决这个case。
区块链技术中有一个概念叫做默克尔树,也就是Merkle树。
默克尔树是一种哈希树,其中每个叶子节点都标有数据块的加密哈希值,而每个非叶子节点都标有其子节点的加密哈希值的标签。大多数哈希树的实现是二进制的(每个节点有两个子节点),但它们也可以有更多的子节点。它允许你验证某些数据是否存在于树中,而不需要去轮训啊遍历啊。
在这里插入图片描述
我的理解是这样,有多少数据就有多少叶子结点,叶子结点的数据是该数据的hash值,两个叶子结点会生成对应的父节点,然后以此往上推,会有一个唯一的根结点,数据不同根结点也会不同,所以其实可以根据根节点的hash和叶子结点的hash来类推出这个叶子结点是否是该数据集中。
ok,原理介绍的差不多了,我们来简单介绍下具体该如何实操。

合约

import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";

constructor() ERC721("smart contract's name", "SYMBOL") {
	baseUri = "ipfs://xxxxxxxxxx/";
	whiteRootOG = 0xad8403ee270f9d5d3aae410de98f923e33c6e9c57df0f1c986119fa61192e14c;
	//.,.........
}

function isVerifyMerkleNormal(bytes32[] calldata proof) view public returns (bool) {
	bytes32 leaf = keccak256(abi.encodePacked(msg.sender));
    return MerkleProof.verify(proof, whiteRootNormal, leaf);
}

首先你需要import,然后合约的角色是去验证,传入proof,然后生成leaf对象,verify方法会根据root去做校验,这里会在构造方法里先初始化一个root,后面如果有白名单的增删改查的话,只要去修改这个root就行了,如果有修改只需要修改一次gas费即可。

前端

前端这里的流程是这样的,有一个accounts的数组,根据这个数组去生成默克尔树的roots,如果是部署合约的时候,直接把这个roots写进合约里就行了,但如果是增删改查白名单的话,就需要在合约里的write contract方法里去更新这个roots了。
这里注意,如果非数组里的账户生成的proof为空,前端如果要测试的话就可以这么来测试是否为白名单。

npm i -D merkletreejs keccak256

首先我们要npm install需要的插件。

  //生成白名单
  const generateWhiteOGProofs = () => {

    //buffer化叶子结点
    const leafNodes = whitelistAddressesOG.map(addr => keccak256(addr));
    //实例化默克尔树
    const merkleTree = new MerkleTree(leafNodes, keccak256, { sortPairs: true });
    setMerkleTreeOG(merkleTree);
    //获取根哈希值
    const rootHash = merkleTree.getRoot();
    console.log(rootHash);
    console.log('Whitelist Merkle Tree og\n', merkleTree.toString());
  }

部署

在这里插入图片描述
选择compile 合约文件,也就是编译检查,编译通过后,我们就可以去做发布操作了。
在这里插入图片描述
我这里选择的是Injected Provider,用的是Goerli测试网络,如果你账号里没有币的话,推荐这里,每天可以提取0.5eth。然后deploy按钮就可以直接付了gas费后直接测试发布。可以在etherscan里直接view。
在这里插入图片描述
这里,合约就部署成功了。

验证合约代码

由于我们先mint了几个,所以opensea上直接可以查看了。这里要特别注意,opensea上json数据出来比较慢,如果你的图片或者视频比较大的话,也可能会出现过了很久也出不来的情况,在这里建议图片或者视频小一点。如果数据没刷出来,可以在opensea上点击refresh data按钮。
在这里插入图片描述
目前合约部署成功,但contract的方法并没有显示。所有如果我们要在etherscan上直接读取合约的一些数据,或者对合约进行了一些修改操作,比如修改价格,修改白名单等,就需要对contract方法进行验证。
可以直接在remix上操作,插件里搜索flattener,点击activate。
在这里插入图片描述
然后直接保存sol文件直接在etherscan里保存进去进行验证即可。
在这里插入图片描述
成功以后我们就可以直接在etherscan上read和write contract了。

前端和合约交互

前端和合约的交互的话主要分为两类,对应contact里的read和write方法,这里我分别以read里的获取已经mint了多少个和mint方法去对应read合约方法和write合约方法为例子。

准备工作

首先我们在remix里目录contracts/artifacts目录下找到对应的合约名字的json文件,在你的前端项目中新建一个contract文件夹,将这个json文件拷贝过来,并且记录下你的合约地址进行替换,我们需要根据这个地址去获取合约地址对象。

npm install ethers

npm下载ethers插件。

import { MerkleTree } from 'merkletreejs';
import { keccak256 } from 'ethers/lib/utils';
import { ethers } from 'ethers';
import { message } from 'antd';

import contract from "./../../../../contracts/NFT.json";

//............

const contractAddress = "your contract address";
const abi = contract.abi;

获取已经mint了的数量

const getTotalSupply = async () => {
    try {
      const { ethereum } = window;

      if (ethereum) {
        const provider = new ethers.providers.Web3Provider(ethereum);
        const signer = provider.getSigner();
        const nftContract = new ethers.Contract(contractAddress, abi, signer);

        //获取总共多少币了
        nftContract.totalSupply().then(c => {
          console.log('已经mint了: ' + parseInt(c));
          setAlreadyMint(parseInt(c));

          if (alreaderMint == maxSale) {
            setStatus('SoleOut');
          }
        });
      }
    } catch (err) {
      console.log(err);
    }
  }

异步方法,获取到nftContract对象后,直接可以调用totalSupply方法即可,totalSupply方法为编写合约的时候写的read方法,如果你要在前端查看价格等其他read方法,道理相同。

mint

const mintNftHandler = async () => {
    try {

      if (currentAccount == null) {
        connectWalletHandler();

        return;
      } else {
        if (minNum != "1" && minNum != "2" && minNum != "3" && minNum != "4") {
          // alert("Up to 5 can be minted");
          message.open({ content: 'Up to 4 can be minted' });
        } else {

          if (alreaderMint == maxSale) {
            message.open({ content: 'Sold Out' });
          }

          const { ethereum } = window;

          if (ethereum) {
            const provider = new ethers.providers.Web3Provider(ethereum);
            const signer = provider.getSigner();
            const nftContract = new ethers.Contract(contractAddress, abi, signer);

            let normalCost = 0;
            if (checkIsWhiteListOG()) {
              if (isCurrentAccountMinted) {
                normalCost = parseInt(minNum) * whitelistOGPriceLast3;
              } else {
                normalCost = whitelistOGPrice + (parseInt(minNum) - 1) * whitelistOGPriceLast3;
              }
            } else if (checkIsWhiteListNormal()) {
              if (isCurrentAccountMinted) {
                normalCost = parseInt(minNum) * whitelistNormalPriceLast3;
              } else {
                normalCost = whitelistNormalPrice + (parseInt(minNum) - 1) * whitelistNormalPriceLast3;
              }
            } else {
              normalCost = parseInt(minNum) * normalPrice;
            }

            const errAddress = keccak256(currentAccount);
            console.log(merkleTreeOG.toString());

            //取得默克尔证明
            const hexProofOG = merkleTreeOG.getHexProof(errAddress);
            const hexProofNormal = merkleTreeNormal.getHexProof(errAddress);
            let nftTxn = await nftContract.mint(minNum, hexProofOG, hexProofNormal,
              { value: ethers.utils.parseEther(normalCost + "") });

            message.open({ content: 'Transaction in progress, Please wait...' });
            await nftTxn.wait();

            message.open({ content: 'mint successful' });
          } else {
            message.open({ content: 'Ethereum object does not exist' });
          }
        }
      }
    } catch (err) {
      console.log(err + "");
    }
  }

注意这是一个异步方法,首先根据用户类型去做判断,需要支付的价格,然后就直接调用nftContract.mint方法直接去mint就可以了,这里参数直接参照合约里的mint方法,加入了一个value参数也就是算好的价格。这里有一个安全性考虑,如果用户在前端代码反编译了以后去修改了价格,因为我们合约里是有做价格保护的,所以会直接报错价格不够。

ok,至此,一个智能合约的基本流程就通了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/139242.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【概率论】期末复习笔记:假设检验

假设检验目录一、假设检验的基本概念1. 假设检验的基本原理2. 两类错误3. 假设检验的一般步骤4. ppp值二、正态总体参数的假设检验σ2已知&#xff0c;检验μ与μ0的关系\color{dodgerblue}\sigma^2\text{已知&#xff0c;检验}\mu\text{与}\mu_0\text{的关系}σ2已知&#xff…

上海华清远见

解析设备树节点信息实例1获取属性数值实例2获取u32类型的值将获取到的u32类型的值存放在array数组中

什么是许可式邮件营销?

邮件是很多企业日常中用到的信息传播工具&#xff0c;并且它还具备了成本低、长期性等优点&#xff0c;所以很多企业选择使用邮件作为载体进行营销推广。而在进行邮件营销的时候&#xff0c;不同的方式和技巧也会影响到最终的营销效果。为了达到较好的营销效果&#xff0c;很多…

Seata应用

下载seata-server 下载地址&#xff1a;Tags seata/seata GitHub 配置Seata-server 第一步&#xff1a;配置seata-server数据源 E:\seata-server-1.4.2\seata\seata-server-1.4.2\conf\file.conf 第二步&#xff1a;创建seata数据库 create database seata 第三步&#xf…

单元测试-SpringBoot Test和Mock

单元测试-SpringBoot Test和Mock “单元测试” “junit&#xff0c;mock&#xff0c;桩” 1. 什么是单元测试 定义&#xff1a;是指对软件中的最小可测试单元进行检查和验证。 Java里单元指一个方法。单元测试是在软件开发过程中要进行的最低级别的测试活动&#xff0c;软件的…

玻纤效应对skew的影响(三)

玻纤效应对skew的影响&#xff08;一&#xff09;玻纤效应对skew的影响&#xff08;二&#xff09;对内skew对32Gbps NRZ和64Gbps PAM-4的影响这一篇中&#xff0c;玻纤效应造成的对内skew将会加入到32Gbps NRZ和64Gbps PAM-4 SerDes全链路分析中。PCIe 5.0代表32Gbps NRZ&…

C++GUI之wxWidgets(11)-编写应用涉及的类和方法(6)-事件处理(5)

目录自定义事件wxPostEvent()wxQueueEvent()PopEventHandler()Bind()GetEventUserData()Connect()Unbind()定义自己的事件类事件处理程序与虚拟方法自定义事件 wxPostEvent() void wxPostEvent ( wxEvtHandler * dest,const wxEvent & event ) 在GUI应用程序中&am…

云开发项目中如何管理用户和管理授权?

管理用户 在项目中添加用户后&#xff0c;才能为用户授予对应的资产管理权限。支持修改已创建用户的密码和删除用户。 本文中的 用户 是指在云项目下创建的 B 端子账号&#xff0c;可以和资产授权配合使用&#xff0c;管理 B 端设备和资产。这些 B 端用户账号可以在 智慧行业…

IOS开发基础 · SwiftUI · CS193p Lecture1-2

IOS开发Lecture 1TextRoundedRectangleZstackLecture 2HStackstruct整合组件ContentViewstruct 中创建变量var&letSwiftUI刷新重建点击效果ArrayForeachButtonSpacervar整合小组件SF-symbol上下界限制简化ButtonLecture 1 Text import SwiftUIstruct ContentView: View {…

Node.js 中 cookie的验证登录

认识 cookie 在讲cookie的登录验证之前&#xff0c;先来了解一下cookie是什么&#xff1f;cookie本质是存储在浏览器中的一小段文本信息&#xff08;不超过4kb&#xff09;&#xff0c;是由服务器生成发送到浏览器&#xff08;客户端&#xff09;&#xff0c;浏览器将其保存在…

虚拟化技术学习笔记2

1、虚拟机与容器对比&#xff1a; 2、Hypervisor管理工具对比&#xff1a; 3、QEMU&#xff1a; 软件模拟虚拟化、可以模拟多种硬件&#xff0c;包括X86架构处理器、AMD64架构处理器、ARM、SPARC与PowerPC、AIX架构等&#xff0c;效率低、一般用于研究测试场景。QEMU可以模拟一…

偏微分题目的解法

介绍偏微分是考研数学里的小重点&#xff0c;通常在题干中就能很明显看到偏导数。这种题目一般会有两个小题&#xff0c;且第一题往往送分题&#xff0c;通常是求某个复合函数的偏导&#xff0c;直接用复合函数的求导法则即可得到答案。第二题通常是求原函数&#xff0c;一般来…

NVIDIA 在 WeNet 中开源 Noisy Student Training 方案

为了改进 Noisy Student Training 在非目标领域 ASR 上的性能&#xff0c;英伟达提出新型数据筛选方法 LM Filter。其利用不同解码方式的识别文本之间的差异来作为数据筛选条件&#xff0c;是一个完全无监督的筛选过程。在 AIShell-1 上与无数据筛选的基线相比可以有 10.4% 的性…

PYNQ-Z2 开发板

1. 官方手册写的挺全&#xff0c;了解一下PYNQ-Z2 设置指南 https://pynq.readthedocs.io/en/latest/getting_started/pynq_z2_setup.htmlPYNQ-Z2 Reference Manual v1.0 https://www.mouser.com/datasheet/2/744/pynqz2_user_manual_v1_0-1525725.pdfpynq&#xff08;Python O…

单分散PEG之Amino-PEG24-acid;CAS:196936-04-6氨基-二十四聚乙二醇-羧酸

Amino-PEG24-acid氨基-二十四聚乙二醇-羧酸196936-04-6 中文名称&#xff1a;氨基-二十四聚乙二醇-羧酸 英文名称&#xff1a;Amino-PEG24-acid 分子式&#xff1a;C51H103NO26 分子量&#xff1a;1146.35 CAS&#xff1a;196936-04-6 外观&#xff1a;粘稠液体或者固体粉末&a…

数据的存储(3)浮点数的存储

tips 1. 2. 浮点数内存存储方式与整型是截然不同&#xff0c;不可被整型思维带偏了 我用一个例子来理解浮点数在内存当中的表示方法&#xff0c;先上一个十进制浮点数13.5 1. 利用二进制的权重化为二进制浮点数 二进制权重表小数部分如下&#xff1a; 那么13.5&…

Vue好难理解怎么办?

Vue学习笔记分享给你&#xff0c;希望对你有些帮助&#xff0c;另外推荐2个安装 VScode 中的 Vue 插件 Vue 3 Snippets Vue 3 Snippets - Visual Studio Marketplace 这个插件包含了所有的 Vue.js 2 和 Vue.js 3 的 api 对应的代码片段。插件的代码片段如下表格所示&#xff0…

Jetson nano 入手系列之4—外围设备:开机键+PWM风扇

Jetson nano 入手系列之4—外围设备&#xff1a;开机键PWM风扇1.外接按键开机2.PWM可调速风扇2.1 jtop工具2.2 PWM风扇2.3 PWM风扇的控制2.3.1 手动控制2.3.2 自动控制参考文献本系列针对亚博科技jetson nano开发板。 Jetson nano 入手系列&#xff1a; Jetson nano 入手系列之…

《MySQL系列-InnoDB引擎11》InnoDB关键特性-刷新邻接页

InnoDB 关键特性 InnoDB存储引擎的关键特性包括&#xff1a; Insert Buffer (插入缓冲)Double Write (两次写)Adaptive Hash Index (自适应哈希索引)Async IO (异步IO)Flush Neighbor Page (刷新领接页) 这些特性为InnoDB存储引擎带来了更好的性能以及更高的可靠性。 刷新邻接…

极光笔记 | 当前最佳实践:Header Bidding 与瀑布流混合请求技术

通过这篇文章您讲将了解&#xff1a;Header Bidding 的发展史Waterfall、Header Bidding 的逻辑及优劣势为什么说 Header Bidding 与瀑布流混合请求技术是当前最佳实践PART 01、Header Bidding 的起源Header Bidding&#xff08;头部竞价&#xff0c;又称 Pre-Bidding 或 Advan…