前言
最近随着明星投资NFT项目大火,一堆"元宇宙"概念的营销号开始作妖,身边也有朋友跃跃欲试中。劝其实是劝不住的,我只想通过一个实际项目的极简版本来说明一下,这类基于Web3
的NFT
项目,其内在本质是什么,以及区块链应用开发的一组技术栈。
整个应用大致会采用Solidity
写智能合约,Python
来写AI
部分,React
来做前端界面,过程中涉及的内容比较杂,所以分几篇来介绍。希望我能理清思路,深入浅出的尽快能连载完。
第一篇就轻松愉快些,简单聊聊个人对区块链的看法,并顺手发一个币体验一下流程。
区块链的本质
区块链的本质是一堆采用非对称加密串起来的账本。是的,你的直觉没错,其本身就像一个包含状态的快照数据库,无需云服务器作为后端,有点Serverless
的味道。区别只在于,它是去中心化的,由多个节点来确认区块(上链写入),所以读操作很快,写操作很慢。
虽然省去了租服务器的费用,但每次在链上写入操作都要花费gas
,还要忍受漫长的区块确认过程,这天下没有免费的午餐。效率,安全和隐私是一组矛盾,现实中的绝大多数场景在中心化程序里都有近乎完美的解决方案,其实真正需要区块链的场景并不多。
只有在面对公众信任或是跨域合作的时候,才是其用武之地。
解决信任问题
智能合约事先约定了一组规则和触发条件,一旦生效,调用执行即可。由于一开始就被“固化”在链上,就不需要第三方公证来做增信了,极大的消解了现实中各种猜疑链的源头。
“不信任”的成本非常的高,因为做任何事情前都需要再三确认(码农们可以回想一下你程序里真正的工作代码和排错检查代码的占比)。而即使确认了很多遍,人总是最大的不稳定因素。(即使上了区块链的供应链系统,人为输入阶段“掺假”,那再可靠的智能合约不过是歪曲历史的帮凶罢了)
将程序和数据“固化”在链上,其实就是将对人性的考验,转化为对非对称加密(数学)的考验,以及对攻击整个生态链的“经济学”的考验,显然后两者更可控和可量化些。
天然的历史记录回溯
任何操作都一旦被记入在链上,就没法修改(只要控制好上链时的“脏数据问题”),后续的数据均真实有效,回溯整个链条可以还原出事实的全部真相,自带时间戳属性(虽然这区块挖掘时间不太准,但高频应用一般不适合区块链,再不济可以把精准时间打包成数据也存上链)。
公益类的应用就特别适合区块链,比如公益捐赠,公益社会服务,公益养老服务等等,天然“公示”的账目和流水可以给公益项目赋予最大的信任感。
社区治理换来的“永续”
"永续"太重要了,我们不能把程序寄希望于一个人,一家企业甚至是一个国家,参考一下最近的Faker.js事件或是暴雪陨落。DAO
的大热恰恰反映了这是区块链的典型应用场景,是个相互成就的过程。
切片的元宇宙
分链已经成为下一个共识基础,越多的侧链意味着更低的交易速度和更低的交易成本。这里不想谈币价,毕竟铸币权这种东西太敏感,我也不懂经济。但可以把代币想象为一种权利证明,信用证明,游戏点券等等,类似Q币,蚂蚁积分。完全与现实无关的话,也就不是元宇宙了,那是封闭的VR空间,区块链的价值之一就是让现实更多元化,在众多分裂的平行宇宙中建立沟通的桥梁,助力服务于现实。
匿名的信用体系
其实每个人都应该拥有自己的代币,比如信用分。想象一下,你去银行贷款说,我用信用担保,不提供消费流水和房产证明,就像没带钱去饭店结账说刷脸支付一样可笑。如果能有区块链上的可信数据作为增信,那既能不出卖隐私给银行,又能给足够可信的资产证明。
就像开源世界里,我们认可的是那个Id背后的技术,而不是哪个名人或是权威。在人均千万的知乎和人均上亿的小红书上,展示一下钱包地址再发帖会更有可信度,关键这同样不侵犯隐私。
网红KOL和社群组织其实也适合用代币来回馈粉丝,标记工作量,让私域流量有了更好的量化依据。元宇宙时代,既让消费端获得虚拟世界无限满足,又让生产端在现实世界赚的盆满钵满,双赢!
同理私钥是区块链的唯一凭证,丢了就会“真正的死亡”,没有用真实身份找回账号一说,这是匿名的代价。
普通开发者搭建基础主链的难度太大了,私有链也无法满足去中心化,更没有公信力,相对成本也高。长远来看,国家牵头搭建POS的公链对区块链生态来说还是很有必要的。代币不直连主币市场,不设中心化交易所就能避免很多金融风险。
所谓,一代韭菜信仰ICO
的空气币价值;二代韭菜在中心化交易所买卖合约;三代韭菜通过DeFi
质押稳定币来获取代币;四代韭菜奔赴NFT
元宇宙....
好了,理解一下,以上只是作为一个苦逼AI开发者,在那些年的币圈乱象下,无显卡无算力可用时的酸楚。吐槽结束,回到技术实现,我们来聊点干货吧。
区块链应用
除去基础的公链选择(或是私链搭建),一个完整的区块链应用一般分为以下几步:
区别于网上一堆3分钟获取代币的方法,作为码农我们要选择一种更安全可靠的方案。因为就区块链来说,一旦部署就没法修改,意味着所有的漏洞也很难被修补(仅通过整体迁移到新合约来升级版本)。
这有点像"嵌入式开发"里的固件烧录,若完全不考虑代币安全问题,最终在其上部署的应用未来会面临很大的金融风险。这些年被黑客盗取代币的项目,不在少数。
在开始写智能合约之前,一般会先给自己套两个“紧箍咒”:
基于以上思路,我们先来打造一款属于自己的ERC20
代币,固定发行总量为2100万枚,预留一个合约接口,可以等待被其他智能合约触发。
为什么基于ERC20
ERC20是一个以太链上被广泛使用的代币合约,提供了标准化的代币铸造,转账,批准操作。作为应用最广泛的合约,非常适合用来做基本代币。
为什么固定总量是2100万
其实固定总供应量的数值无所谓,它只标记了一个“信用总额”。未来要有参考价值,那就和同样总发行量的比特币对标。其实发行的越多,单位价值越小,是不是很反直觉,这与货币的通胀通缩是一个概念。
为什么预留一个合约接口
作为主合约,我们不想引入太多功能,具体业务逻辑我们可以通过后续发行的N个子合约来实现。
主合约
简单合约直接使用Remix
是最佳选择,打开官网
https://remix.ethereum.org/
在contracts
目录下,新建一个名为MainToken.sol
的文件
继承合约
这里基于openzeppelin
开源库,在社区的千锤百炼下,可以最大限度的减少安全隐患。
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
继承ERC20
基本合约,Ownable
检查合约拥有人,使用SafeERC20
来增强使用合约传输代币的安全性。
contract MainToken is ERC20, Ownable {
using SafeERC20 for IERC20;
...
}
铸造代币
构造函数里,声明代币的名称,缩写和总供应量,精度默认为18位。这里预留变量,可以便于后续在部署的时候指定具体名称。构造函数仅在创建时调用一次,意味着代币一次性挖掘完毕,是一个固定总量代币。
constructor(
string memory _name,
string memory _symbol,
uint256 totalSupply
) ERC20(_name, _symbol) {
_mint(msg.sender, totalSupply);
}
合约转账接口
采用 SafeERC20
包装后,可以保证仅在合约地址下,接受发送者的代币。这个发送者可以是个人钱包,也可以是其他合约。我们这里使用这个接口,来“授权”各个子合约在具体业务里,使用主合约的代币计量。
function safeInteractWithToken(uint256 sendAmount) external {
IERC20 token = IERC20(address(this));
token.safeTransferFrom(msg.sender, address(this), sendAmount);
}
提现接口
将主合约上收到的代币,提现到合约拥有人的钱包上,完成整个闭环。注意这里提取的是代币,而不是主币。
function withdraw() external onlyOwner {
_transfer(address(this), msg.sender, balanceOf(address(this)));
}
子合约
配置接口
配置了主合约上的两个函数作为接口,
interface MainInterface {
function safeInteractWithToken(uint256 sendAmount) external;
function approve(address spender, uint256 amount) external returns (bool);
}
绑定主合约
构造函数中接受一个合约地址,完成绑定操作。
contract SubToken{
address private _mainAddress;
MainInterface private _mainContract;
constructor(address _slotAddress) {
_mainAddress = _slotAddress;
_mainContract = MainInterface(_slotAddress);
}
...
}
提交代币
approve
来先将子合约上的代币批准到主合约,safeInteractWithToken
再调用主合约领取代币,完成真正代币转移工作。这里组合到一起简单示例一下,功能近似于transfer
方法,实际业务中,一般会分开调用。
function transferWithToken(uint256 sendAmount) external {
_mainContract.approve(_mainAddress, sendAmount);
_mainContract.safeInteractWithToken(sendAmount);
}
我们可以通过一个新的子合约调用任意已部署的合约,并不一定是自己编写的,是不是想到了 Serverless
中的云函数Serverless Cloud Function,SCF
,这有着异曲同工之妙。
这类子合约可以有很多,后续我们要编写的NFT合约NftToken.sol
就会是其中之一。
主合约部署后,会对合约拥有者预挖固定总额代币;然后合约拥有者向子合约转账一定量的初始代币作为项目资金;子合约开展具体业务统一使用主合约的代币作为结算工具;项目结束或是暂停时可将剩余代币归还主合约;最终由主合约提现到合约拥有者的钱包内,完成整个闭环。
基本架构图如下所示:
测试合约
部署主合约
确认编译器版本,选择自动编译,启用优化以后,每次代码保存时就会进行合约编译了。
我们测试先在虚拟机内部署,选择MainToken
,填上代币名称bluishfish Coin
,缩写BLF
和总供应量21000000
(注意这里忽略了18位精度值,缩小了发行量)。其中主钱包地址ACCOUNT
可以先复制出来,后续转账会使用到(合约拥有者钱包)。
点击transact
进行部署,主合约部署完成后,我们可以通过合约下方的命令按钮来测试功能。黄色或红色都是需要花费gas
的,蓝色则是只读免费的。
这是因为,无论是写入还是更改一段数据, 这都将永久性地写入区块链。”永久性“啊!需要在全球数千个节点的硬盘上存入这些数据,这是需要成本的!每次调用更改都要花钱,所以为了降低用户使用成本,不到万不得已,应尽量避免将大数据写入链上。
调用balanceOf
函数填入之前复制的钱包地址,能查询到该钱包上的代币数量,说明主合约已经部署成功。
部署子合约
子合约的编译和之前的雷同,值得注意的是,我们需要先采集到主合约的合约地址,作为部署参数完成绑定。
选择SubToken
后,复制下方的主合约地址,然后填入_SLOTADDRESS
,点击transact
部署。
转账给子合约
现在虚拟机内我们有两个已部署的合约了,先要理清一个概念,合约有自己的地址,部署者作为合约拥有者也有一个主钱包地址,两者相互独立。
主钱包地址:0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
主合约地址:0xd9145CCE52D386f254917e481eB44e9943F39138
子合约地址:0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8
你的可能会有所不同,这里取决于虚拟机的分配,生产环境下取决于实际链上分配的地址。理清关系后与后续截图对应来看即可。
我们部署主合约的时候,将代币已经存入主钱包。所以这里先将一万枚代币作为项目初始资金,由主钱包
转账给子合约
。
复制子合约的地址,展开主合约,调用transfer
方法,填入子合约地址,数量10000
,点击transact
进行转账。
调用balanceOf
函数,填入子合约地址,查询代币是否到账。
转账给主合约
在完成子合约的所有业务后,我们可以将其剩余的代币归还给主合约。
比如调用transferWithToken
将5000代币归还主合约,可以多调用几次,前2次成功后,第3次就好显示余额不足,转账失败了。
提现
回到主合约,我们先查一下主钱包的余额,2099万。
查一下主合约的余额,1万。很好,说明之前的子合约已完全将代币归还。
最后将主合约上的代币,提现到主钱包上。
我们执行withdraw
后,再查下主钱包余额,就会发现又变为2100万了。
链上部署
安装钱包插件
MetaMask
插件,推荐配合chrome
浏览器使用,墙外不方便下载的,也可以在文末的资源里获取到离线安装包。
https://metamask.io/download.html
安装好钱包后,切换到Remix
,选择Injected Web3
来连接钱包。
选择哪个网络,就需要先持有这个网络的主币。比如以太链就用ETH
,Polygon
就用MATIC
。在链上一切涉及到“写入”操作的都需要花费主币,一般由存储费用和执行费用(gas)两部分组成。
正式部署时,记得填入TOTALSUPPLY
的值后面再增加18个0,也可以修改主合约把精度加上。
_mint(msg.sender, totalSupply*10**decimals());
Ropsten 测试网
测试网环境几乎与主网相同,而且无成本,所以在产品上线之前,推荐在这里做最后一版本的测试。
这里有几个水管来获取测试网ETH
,进入链上世界后,没有主币你将寸步难行...
https://faucet.dimensions.network/
https://faucet.egorfine.com/
https://faucet.ropsten.be/
部署流程和前文雷同,这里不赘述了,最终点击部署按钮后,需要在弹出的钱包里确认手续费。这里要预计花费0.03个ETH
,以太网现价大概2万左右,不便宜。测试网不心疼,我们点击确认继续。
耐心等待区块确认,一切正常的话,一分钟左右就能完成上链操作了。在区块链浏览器上可以查询到你新部署的合约。
https://ropsten.etherscan.io/token/0x2ed5895A94720Ae3C22B7Bb30771641C9cDC976a
主合约地址:0x2ed5895A94720Ae3C22B7Bb30771641C9cDC976a
子合约地址:0xfC5790771590D48D6389f65809CBDd0fC7c8065E
Polygon:
真实落地的产品还是要在主网上部署的,其中在Polygon
上部署的时候,当前线上版本的Metamask 10.8.1
钱包会遇到个bug,
Invalid transaction params: params specify an EIP-1559 transaction but the current network does not support EIP-1559 Object
其实Polygon
是支持EIP-1559
的,下载最新的10.8.2
版本修复一下,就可以正常部署了。
https://github.com/MetaMask/metamask-extension/releases/download/v10.8.2/metamask-chrome-10.8.2.zip
Polygon
是一条以太坊的侧链,完全兼容以太链上的功能,手续费会便宜很多。当然还有币安链,波场链等等,不同的链其实是不同的生态圈,就像币安链上多游戏类应用,波场链上多赌场类应用,以太链上则多金融类应用,手续费某种程度上来说,也是一种门槛。对开发者来说,本地私有链是效率最高的,后续我们会细聊这部分实现。
Polygon
会比测试网更慢,慢很多,耐心+1,足够你泡杯咖啡,运动一下的。等待部署完成后,钱包会弹出通知,点击状态可以直接打开polygonscan
查到我们的代币。
https://polygonscan.com/token/0x1ab5165b955ebc223b734de1b778a6d57f90c9ac
钱包导入代币
在'Remix'中复制合约地址,然后回到钱包内,点击Import tokens
就能将你发行的代币导入钱包。2100万到账,请查收!
之后无论是发送还是兑换,都很方便。想象一下,找一些资金在那些去中心化交易所添加一下交易对,提供流动性,再说几个“好故事”,那些资金盘就正式开工了... 细思极恐!现在你确定还要用真金白银去买那些山寨币么。
如果你跟着上面的流程走到这里,那恭喜你,应该已经拥有了第一个完全属于自己的智能合约和代币了。
是的,它什么也不是,它也可以是一切,欢迎来到我的元宇宙
,enjoy your dream!
源码下载
本期相关文件资料,可在公众号“深度觉醒”,后台回复:“chain01”,获取下载链接。
下一篇预告
在线Remix
虽然很好用,但是不方便调试,那繁琐的测试流程,肯定不想再来一遍了吧。下一篇我们做一下基于truffle
和ganache
的本地化部署,方便后续的开发和调试。