更多有关 Conflux 开发的文档和资料请见 Conflux开发资料包
合约规范
遵循标准
关于在 Conflux 开发遵循 EIP-721 和 EIP-1155 标准合约的规范,请见 Conflux NFT 开发者指南
需要注意的是,EIP-721 和 EIP-1155 标准在 oppenzeppelin 的实现中,对外暴露的读取元数据 metadata 的函数名分别为 tokenURI 和 uri,大多数区块链浏览器和 NFT 交易市场,以及具有 NFT 展示功能的钱包,默认都是读取上述两个函数。
若是有特殊需求,例如将 NFT 存储在多个服务器,可以将主要用于对外展示的 metadata uri暴露在 tokenURI/uri 函数,另写一个或多个函数用于获取存储在其他服务器的 metadata uri。
合约代付
关于内置合约和代付功能的细节,可参考此文: Conflux 内置合约功能介绍
若要使用代付功能,需要引入内置合约 SponsorWhitelistControl.sol
,举一个简单的🌰:
// 引入赞助商白名单合约
import "./SponsorWhitelistControl.sol";
contract example {
// 创建 SponsorWhitelistControl 对象 SPONSOR
SponsorWhitelistControl public constant SPONSOR =
SponsorWhitelistControl(
address(0x0888000000000000000000000000000000000001)
);
constructor(string memory uri_) {
// 设置初始 uri 以获取 metadata
_setURI(uri_);
/**
* 将所有用户注册为受代付机制赞助:
* 调用 addPrivilege 以添加有资格获得代付赞助的用户名单
* address(0) 为全零地址,代表所有用户
*/
address[] memory users = new address[](1);
users[0] = address(0);
SPONSOR.addPrivilege(users);
}
}
去中心化存储
将 NFT 用于展示的部分 - 图片、音频或视频存储在中心化服务器的过程可参考各家云服务商提供的文档,在此不再赘述。
IPFS
以广泛使用的 IPFS 为例(需要科学上网)。
星际文件系统(InterPlanetary File System,缩写为 IPFS)是一个旨在实现文件的分布式存储、共享和持久话的网络传输协议。它是一个内容可寻址的点对点超媒体分发协议。在IPFS网络中的节点构成一个分布式文件系统。
若无复杂需求,则无需对 IPFS 有过多了解,可以直接使用其官方推荐的服务商 pinata ,提供免费的1GB存储空间。
-
进入 pinata 首页注册然后登陆,会跳转到你的个人页面,如下图所示:
-
点击左侧 My Files 下方的 Upload,会出现三个选项: Folder, File, CID。选择 CID,缓存或 Pin 在本地的文件可以通过上传本地生成的 CID 将文件存储在IPFS网络的其他节点,以确保在本地节点离线后依然可以访问。 若选择 File,则会在 pin 成功之后(即已经保存在IPFS网络),生成一个 Content Identifier(CID) ,然后便可通过 Pinata 网关 + CID 来访问,例如 https://gateway.pinata.cloud/ipfs/QmZCDSGV7PRJjRb2PFyopKzsU79LgmPo7AziaB89XFXyP3, 见下图:
-
若选择 Folder 上传文件夹,则会同时给文件夹和其中的每个文件单独生成一个 CID。用户在访问文件时,既可以通过文件自身的 CID 来访问(见上图),也可以通过文件夹的CID + 文件名来访问,例如 https://gateway.pinata.cloud/ipfs/QmNVbNLHNTYLu5bsUSAZ6wKWxn7CQ4UAon9vkkxJfMkoGb/ShuangJing.json, 请见下图:
通过 CID 访问文件夹
通过文件夹 CID + 文件名 访问文件
注意事项: Pinata 有 1GB 的免费额度,超出部分需要付费支持。官方维护了一个固定服务提供商(pinning service providers) 列表。可以考虑使用 NFT.Storage,使用方法和 Pinata 很相似。这是一项免费的去中心化存储服务,由 IPFS 协议的开发团队 Protocol Labs 维护。详见 Introducing NFT.Storage: Free Decentralized Storage for NFTs: https://filecoin.io/blog/posts/introducing-nft.storage-free-decentralized-storage-for-nfts/
部署过程
使用 cfxtruffle 部署合约,更多资料详见
Conflux Truffle 完全使用指南: https://juejin.cn/post/6862239117934067726)
Conflux-truffle: https://github.com/Pana/conflux-101/blob/master/docs/conflux-truffle.md
使用 Cfxtruffle 智能合约开发工作流: https://shimo.im/docs/e1Az4M7EwbCVadqW
以及 Lecture7.1 - Cfxtruffle及标准合约介绍: https://confluxedu.wixsite.com/beidoutianxuan2021/课程录屏
基本步骤:
// 全局安装cfxtruffle
npm install -g conflux-truffle
// 初始化一个项目
cfxtruffle init project-name
// 添加合约
cfxtruffle create contract contract-name
// 安装@openzeppelin/contracts以引入需要的合约
npm install @openzeppelin/contracts
// 编译写好的合约
// 编译完成后会生成一个build文件夹
// 里面有合约相关的json文件
// 想要重新编译所有文件可以使用 cfxtruffle compile --all
cfxtruffle compile
// 配置truffle-config.js文件
// 通过远程节点将合约部署到Conflux主网
module.exports = {
networks: {
Tethys: {
url: "https://main.confluxrpc.com", // Conflux主网rpc
network_id: "1029", // 主网id
/**
* 用于发送交易部署合约的账户私钥
* 需要预先在账户里放一些cfx
* 部署合约的费用通常在11-14cfx
*/
privateKeys: [""],
},
},
// 在此处指定solc编译器版本,即你要部署的合约所使用的版本
compilers: {
solc: {
// Note: 也可以不指定,使用 version: "pragma" 来自动检测编译器版本
version: "^0.8.0", // 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: false,
// runs: 200
// },
// evmVersion: "byzantium"
// }
}
},
}
// 添加合约部署脚本
cfxtruffle create migration contract-name
/**
* Note:
* 创建部署脚本后需要将其重命名,按照所有合约部署的历史顺序
* 如初始化后预设的第一个脚本名称是 1_initial_migration.js
* 那么如果你现在要部署自己的合约
* 则应该创建一个名称为 2_any_maybe_your_new_contract_name.js
* 的部署脚本
*/
// 在部署脚本中加入以下代码
const any = artifacts.require("contract-name");
module.exports = function(_deployer, network) {
// Use deployer to state migration tasks.
_deployer.deploy(any)
/**
* Note:
* _deployer.deploy() 的参数由你的合约构造器 constructor() 的参数决定
* 此处 _deployer.deploy(any) 假设 constructor() 不需要额外参数
* 如果你的合约在部署的时候需要额外传入参数给构造器,比如
* constructor(
string memory name_,
string memory symbol_,
string memory uri_
)
* 那么就需要把参数按顺序传给 deploy 函数,像这样
* _deployer.deploy(any, 'arg1', 'arg2', 'arg3', ......)
* 如果没有在部署的时候正确传入参数,就会产生如下报错
* "Invalid number of parameters for "undefined". Got 0 expected 3!"
*/
};
/**
* 运行部署命令
* --reset用于重新部署所有合约
* --network指定使用的网络,默认使用development网络
*/
cfxtruffle deploy --reset --network Testnet
// deploy命令执行完成之后,会输出部署的结果
// 比如交易hash,合约地址,花费的费用等
// 可以在conflux浏览器 https://confluxscan.net/ 复制合约地址来查看
Tips: 如果你在 VSCode 编辑器上使用 Conflux-Truffle 框架进行开发,可以安装一个 Solidity Contract Flattener 插件,它可以把你的合约联同其所有 import 的依赖项压缩到同一个文件。在某些场景,比如出于开源的目的,你希望将合约公布到区块链浏览器(e.g. ConfluxScan) 进行验证并供别人参阅,同时你的合约拥有复杂的依赖关系时用这个插件会方便很多。
合约交互
通过 Confluxscan 与合约交互
将合约部署到 Conflux 主网后,可以在 Confluxscan 搜索合约地址,并通过验证合约来与合约交互。如下图所示:
-
此时合约尚未验证
-
在验证页面提交合约代码,需要注意的是提交的代码必须包括所有引入的合约(以及引入合约所引入的合约)
合约验证成功后,就可以返回合约主页查看合约代码以及与合约交互了。
-
选择 读取合约 读取合约信息。
通过输入nft的id,即可获取存储在IPFS上的metadata uri
-
选择 写入合约 改变合约状态
输入一个地址,将此地址设置为有权限铸造 nft 的 minter。此操作需要通过 Fluent 钱包连接 ConfluxScan,连接的账户必须是合约的管理者(admin, i.e. 合约的创建者),点击写入后将会调用 Fluent 钱包发送交易。
通过 js-conflux-sdk 交互
通过运行node.js脚本的方式,使用 js-conflux-sdk 与合约交互,查看完整官方 SDK 文档 Conflux-SDKs。
// 首先引入合约的abi文件
const abi = require('./contract-abi')
// 从js-conflux-sdk引入Conflux
const { Conflux } = require('js-conflux-sdk')
// 将私钥保存在.env文件,通过dotenv来引入,避免直接暴露在脚本代码中
const dotenv = require('dotenv')
dotenv.config()
// 创建Conflux对象,根据交互的网络输入相应参数
const conflux = new Conflux({
url: 'https://main.confluxrpc.com',
networkId: 1029,
})
// 获取保存在.env文件的私钥,调用addPrivateKey方法创建账户
const pk = process.env.PRIVATE_KEY
const account = conflux.wallet.addPrivateKey(pk)
// 以batchAddItemByAddress为例,批量铸造nft发送给多个地址
// 参数为接收nft的address数组和该nft的metadata uri数组
let addressArr = ["cfx:aas6ettbcgrar1c7fvx745p2m7phz7c18edvfxepfn",]
let uriArr = ["metadata-uri",]
async function interact() {
// 创建要交互的合约对象,参数为合约的abi和地址
const contract = conflux.Contract({
abi,
address: 'cfx:ace7jmw43nfd0agmjnhzhvhg23m0wcax0y7su6hh5g'
})
/**
* 通过合约对象调用batchAddItemByAddress方法
* 并通过sendTransaction将交易发送到链上
* from参数指定发送此交易并支付gas和抵押存储费用的账户
* 还可以指定nonce, gasPrice等参数,详见官方文档
*/
let receipt = await contract.batchAddItemByAddress(addressArr, uriArr).sendTransaction({
from: account,
})
console.log(receipt)
}
interact()