草庐IT

智能合约Smart Contract技术详解

jieqiang3 2023-11-26 原文

文章目录

合约编写

建议读者先了解下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,至此,一个智能合约的基本流程就通了。

有关智能合约Smart Contract技术详解的更多相关文章

  1. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  2. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  3. ruby-on-rails - 用于门户的 Ruby 技术 - 2

    我刚刚看到whitehouse.gov正在使用drupal作为CMS和门户技术。drupal的优点之一似乎是很容易添加插件,而且编程最少,即重新发明轮子最少。这实际上正是Ruby-on-Rails的DRY理念。所以:drupal的缺点是什么?Rails或其他基于Ruby的技术有哪些不符合whitehouse.org(或其他CMS门户)门户技术的资格? 最佳答案 Whatarethedrawbacksofdrupal?对于Ruby和Rails,这确实是一个相当主观的问题。Drupal是一个可靠的内容管理选项,非常适合面向社区的站点。它

  4. iNFTnews | 周杰伦18年前未发布的作品Demo,藏在了区块链技术里 - 2

    当音乐碰上区块链技术,会擦出怎样的火花?或许周杰伦已经给了我们答案。8月29日下午,B站独家首发周杰伦限定珍藏Demo独家访谈VCR,周杰伦在VCR里分享了《晴天》《青花瓷》《搁浅》《爱在西元前》四首经典歌曲Demo背后的创作故事,并首次公布18年前未发布的神秘作品《纽约地铁》的Demo。在VCR中,方文山和杰威尔音乐提及到“多亏了区块链技术,现在我们可以将这些Demos,变成独一无二具有收藏价值的艺术品,这些Demos可以在薄盒(国内数藏平台)上听到。”如何将音乐与区块链技术相结合,薄盒方面称:“薄盒作为区块链技术服务方,打破传统对于区块链技术只能作为数字收藏的理解。聚焦于区块链技术赋能,在

  5. 玩以太坊链上项目的必备技能(初识智能合约语言-Solidity之旅一) - 2

    前面一篇关于智能合约翻译文讲到了,是一种计算机程序,既然是程序,那就可以使用程序语言去编写智能合约了。而若想玩区块链上的项目,大部分区块链项目都是开源的,能看得懂智能合约代码,或找出其中的漏洞,那么,学习Solidity这门高级的智能合约语言是有必要的,当然,这都得在公链``````以太坊上,毕竟国内的联盟链有些是不兼容Solidity。Solidity是一种面向对象的高级语言,用于实现智能合约。智能合约是管理以太坊状态下的账户行为的程序。Solidity是运行在以太坊(Ethereum)虚拟机(EVM)上,其语法受到了c++、python、javascript影响。Solidity是静态类型

  6. 物联网MQTT协议详解 - 2

    一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su

  7. Tcl脚本入门笔记详解(一) - 2

    TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是

  8. 智能客服 | 浅谈人工智能聊天机器人ChatGPT - 2

    2022年底,OpenAI的预训练模型ChatGPT给人工智能领域的爱好者和研究人员留下了深刻的印象和启发,他展现的惊人能力将人工智能的研究和应用热度推向高潮,网上也充斥着和ChatGPT的各种聊天,他可以作诗、写小说、写代码、讨论疫情问题等。下面就是一些他的神回复:人命关天的坑: 写歌,留给词作者的机会不多了。。。 回答人类怎么样面对人工智能: 什么是ChatGPT?借用网上的一段介绍,ChatGPT是由人工智能研究实验室OpenAI在2022年11月30日发布的全新聊天机器人模型,一款人工智能技术驱动的自然语言处理工具。它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动

  9. ruby - 使用哪种群发消息技术? - 2

    我感到有点困惑——大约24小时以来,我一直在考虑在我的项目中使用哪种组播技术。基本上,我需要的是:创建组(通过一些后端进程)任意客户端广播消息(1:N,N:N)(可能)直接消息(1:1)(重要)使用我自己的后端(例如,通过某种HTTPAPI)对客户端进行身份验证/授权能够通过后端进程(或服务器插件)踢出特定的客户端这是我要的:Ruby或Haxe中的后端相关流程JS+Haxe(Flash9)中的前端—在浏览器中,因此理想情况下通过80/443进行通信,但不一定。因此,这项技术必须能够在HaxeforFlash中轻松访问,最好是Ruby。我一直在考虑:RabbitMQ(或OpenAMQ)、

  10. 【详解】Docker安装Elasticsearch7.16.1集群 - 2

    开门见山|拉取镜像dockerpullelasticsearch:7.16.1|配置存放的目录#存放配置文件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/config#存放数据的文件夹mkdir-p/opt/docker/elasticsearch/node-1/data#存放运行日志的文件夹mkdir-p/opt/docker/elasticsearch/node-1/log#存放IK分词插件的文件夹mkdir-p/opt/docker/elasticsearch/node-1/plugins若你使用了moba,直接右键新建即可如上图所示依次类推创建

随机推荐