本文介绍了如何在 Berachain 上使用 Pyth Entropy 生成可验证的随机数,确保 NFT 铸造的公平性和透明度。通过详细的步骤指导,开发者能够在区块链上创建完全随机且可证明公平的 NFT 铸造流程,提升用户信任和项目的可信度。
导言
在区块链生态系统中,生成可验证的随机性是许多应用(例如 NFT 铸造、彩票和去中心化游戏)公平性和透明度的核心。然而,由于区块链协议的确定性,生成真正的随机性是一项挑战。为解决这个问题,Pyth Entropy 提供了一种安全的、可验证的随机数生成机制,适用于智能合约。
本文由,介绍了如何利用 Pyth Entropy 在 Berachain 上构建公平铸造的 NFT 系列,确保每个 NFT 的分配是完全随机且可验证的,从而消除铸造过程中的任何偏见或操控风险。通过本指南,开发者可以轻松实现基于区块链的公平铸造流程,增强用户信任并提升项目的透明度。
Erica
使用 Pyth 构建公平铸造的 NFT 系列
Pyth Entropy 允许开发人员轻松生成区块链上的安全随机数。
可验证的随机性在 NFT 中有很有用的应用。如果通过内部消息或侦探手段提前知道稀有物品的位置,可能会影响铸造的公平性。解决这个问题的有效方法是完全随机地分配铸造位置。由于大多数区块链协议的确定性,本地获得真正的随机性很困难。
在本文中,我们将向您展示如何利用 Pyth Entropy 来创建对用户而言可证明公平的 NFT 铸造。
📋 要求
在继续之前,请确保您的计算机已安装并设置以下内容:
- Node v20.11.0 或更高版本
- pnpm
- 配置了 Berachain Artio 网络的钱包
- 钱包中有 $BERA 或 Berachain 测试网代币 — 请参阅 Berachain Faucet
Foundry
本文需要安装 Foundry。在终端窗口中运行:
curl -L https://foundry.paradigm.xyz | bash;
foundryup;
# foundryup installs the 'forge' and 'cast' binaries, used later
在你的电脑安装 Foundry
有关更多安装说明,请参阅 Foundry 的安装指南。有关在 Berachain 上使用 Foundry 的详细信息,请参阅本指南。
创建我们的 Pyth Entropy 项目
首先,我们将使用 Foundry 设置您的开发环境。
首先创建一个新的项目文件夹并初始化 Foundry:
forge init pyth-entropy --no-git --no-commit;
cd pyth-entropy;
# We observe the following basic layout
# .
# ├── foundry.toml
# ├── script
# │ └── Counter.s.sol
# ├── src
# │ └── Counter.sol
# └── test
# └── Counter.t.sol
Initial Forge Project Template
初始 Forge 项目模板
安装依赖项
我们将使用多个依赖项,因此在下方安装它们:
# FROM: ./pyth-entropy
pnpm init;
pnpm add web3 dotenv @pythnetwork/entropy-sdk-solidity @openzeppelin/contracts --ignore-workspace;
# web3 - interact with EVM blockchains with JavaScript
# dotenv - manage environment variables
# @pythnetwork/entropy-sdk-solidity - Pyth Entropy interfaces
# @openzeppelin/contracts - audited smart contract libraries
安装合约依赖项和 SDKs
Forge 可以重新映射依赖项,使导入更加易读。因此,让我们重新映射我们的 Solidity 导入:
# FROM: ./pyth-entropy
echo "remappings = ['@pythnetwork/entropy-sdk-solidity/=node_modules/@pythnetwork/entropy-sdk-solidity', '@openzeppelin/contracts/=node_modules/@openzeppelin/contracts']" >> ./foundry.toml
Foundry Toml 文件用于重映射
编写 NFT 铸造合约
现在我们准备进入令人兴奋的部分:使用 Pyth 在 Solidity 智能合约中生成可验证的随机数。
创建一个新文件 ./src/EntropyNFT.sol
并粘贴以下代码:
pragma solidity ^0.8.13;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
contract EntropyNFT is ERC721Enumerable, IEntropyConsumer {
event NumberRequested(uint64 sequenceNumber, address minter);
event Minted(uint64 sequenceNumber, address minter, uint256 tokenId);
IEntropy entropy;
address provider;
uint256 public constant MAX_SUPPLY = 500;
uint256 public nextIndex;
uint256[] private availableTokenIds;
// Mapping of sequence numbers to minter addresses
mapping(uint64 => address) public sequenceNumberToMinter;
constructor(
address _entropy,
address _provider
) ERC721("EntropyNFT", "eNFT") {
entropy = IEntropy(_entropy);
provider = _provider;
initializeAvailableTokenIds();
}
// Step 1 of 2: Request a new random number for minting
// Returns sequence number used to obtain random number from Pyth
function requestMint(bytes32 userRandomNumber) external payable {
require(nextIndex < MAX_SUPPLY, "Reached max supply");
uint128 requestFee = entropy.getFee(provider);
require(msg.value >= requestFee, "not enough fees");
uint64 sequenceNumber = entropy.requestWithCallback{value: requestFee}(
provider,
userRandomNumber
);
sequenceNumberToMinter[sequenceNumber] = msg.sender;
emit NumberRequested(sequenceNumber, msg.sender);
}
// Step 2 of 2: Fulfill mint request on Pyth callback
function entropyCallback(
uint64 sequenceNumber,
address,
bytes32 randomNumber
) internal override {
address minter = sequenceNumberToMinter[sequenceNumber];
uint256 randomIndex = uint256(randomNumber) % availableTokenIds.length;
uint256 tokenId = availableTokenIds[randomIndex];
// Swap-and-pop to replace minted tokenId
availableTokenIds[randomIndex] = availableTokenIds[
availableTokenIds.length - 1
];
availableTokenIds.pop();
nextIndex++;
_safeMint(minter, tokenId);
emit Minted(sequenceNumber, minter, tokenId);
}
// Initialize array of available token IDs
function initializeAvailableTokenIds() private {
for (uint256 i = 0; i < MAX_SUPPLY; i++) {
availableTokenIds.push(i);
}
}
// This method is required by the IEntropyConsumer interface
function getEntropy() internal view override returns (address) {
return address(entropy);
}
}
现在让我们分解这个合约:
- EntropyNFT 合约继承自
ERC721Enumerable
,提供了标准的 NFT 功能。我们的合约还继承了IEntropyConsumer
,当随机数请求完成后实现回调。 - IEntropy.sol 提供了与 Pyth Entropy 合约交互的接口。构造函数参数
_entropy
将已部署的 Entropy 合约连接到该接口。 - 合约定义了 500 个 NFT 的最大供应量,并从
[0…499]
初始化了一个数组availableTokenIds
。 - requestMint 函数允许用户通过提供用户承诺(见下文)并支付所需费用从 Entropy 请求随机数。返回的
sequenceNumber
索引调用者有权铸造与该sequenceNumber
对应的随机数生成的 NFT。 - entropyCallback 函数在去中心化的 keeper 机器人完成随机数请求时调用,最终铸造出具有唯一 tokenId 的 NFT。通过将随机数对剩余 NFT 数量取模,确保随机索引在
availableTokenIds
数组范围内。使用过的tokenId
将被替换为数组中的最后一个元素,以保持可用tokenId
的列表。
您可以删除项目生成的
src/Counter.sol
、test/Counter.t.sol
和script/Counter.s.sol
。
部署准备
在项目根目录下创建一个 .env
文件,并填入以下内容:
RPC_URL=https://artio.rpc.berachain.com/
ENTROPY_NFT_ADDRESS=YOUR_ENTROPY_NFT_ADDRESS
PRIVATE_KEY=YOUR_PRIVATE_KEY
ENTROPY_ADDRESS=0x26DD80569a8B23768A1d80869Ed7339e07595E85
PROVIDER_ADDRESS=0x6CC14824Ea2918f5De5C2f75A9Da968ad4BD6344
填入您的部署者钱包 PRIVATE_KEY
,然后将这些环境变量加载到终端会话中:
# FROM: ./pyth-entropy
source .env;
部署到 Berachain 测试网
首先,编译智能合约:
# FROM: ./pyth-entropy
forge build;
您会注意到多个构建输出出现在 ./out
目录中。我们将使用 forge create
命令将我们的新合约部署到 Berachain 测试网(在此处阅读更多有关该命令的信息):
# FROM: ./pyth-entropy
forge create src/EntropyNFT.sol:EntropyNFT \
--private-key $PRIVATE_KEY \
--rpc-url $RPC_URL \
--constructor-args $ENTROPY_ADDRESS $PROVIDER_ADDRESS;
# [Example output]
# Deployer: <YOUR_WALLET_ADDRESS>
# Deployed to: <YOUR_DEPLOYED_CONTRACT>
# Transaction hash: <CONTRACT_DEPLOYMENT_TX_HASH>
您需要拥有 $BERA 以支付部署费用。可以通过以下网址获取水龙头资金:https://artio.faucet.berachain.com/。
铸造您的随机 NFT
现在让我们使用 JavaScript 与我们的新 NFT 合约进行交互。创建一个新文件夹来存放我们的脚本(我们将有两个脚本):
# FROM ./pyth-entropy
mkdir app;
cd app;
touch requestMint.js;
In ./app/requestMint.js
paste in the following code:
const { Web3 } = require("web3");
const EntropyNFTAbi = require("../out/EntropyNFT.sol/EntropyNFT.json");
const EntropyAbi = require("@pythnetwork/entropy-sdk-solidity/abis/IEntropy.json");
require("dotenv").config({ path: "../.env" });
async function requestMint() {
// Step 1: initialize wallet & web3 contracts
const web3 = new Web3(process.env["RPC_URL"]);
const { address } = web3.eth.accounts.wallet.add(
process.env["PRIVATE_KEY"]
)[0];
const entropyNFTContract = new web3.eth.Contract(
EntropyNFTAbi.abi,
process.env["ENTROPY_NFT_ADDRESS"]
);
const entropyContract = new web3.eth.Contract(
EntropyAbi,
process.env["ENTROPY_ADDRESS"]
);
// Step 2: generate user random number, request mint
console.log("Generating user random number and commitment...");
const userRandomNumber = web3.utils.randomHex(32);
console.log(`User Random Number: ${userRandomNumber}`);
console.log("Fetching request fee...");
const fee = await entropyContract.methods
.getFee(process.env["PROVIDER_ADDRESS"])
.call();
console.log(`Request Fee: ${fee}`);
console.log("Requesting NFT mint...");
const requestReceipt = await entropyNFTContract.methods
.requestMint(userRandomNumber)
.send({ value: fee, from: address });
console.log(`Request Transaction Hash: ${requestReceipt.transactionHash}`);
const sequenceNumber =
requestReceipt.events.NumberRequested.returnValues.sequenceNumber;
console.log(`Sequence Number: ${sequenceNumber}`);
// Step 3: Poll for new Minted events emitted by EntropyNFT
// Stops polling when same sequenceNumber is fulfilled
const intervalId = setInterval(async () => {
currentBlock = await web3.eth.getBlockNumber();
const events = await entropyNFTContract.getPastEvents("Minted", {
fromBlock: currentBlock - 5n,
toBlock: currentBlock,
});
// Find the event with the same sequence number as the request.
const event = events.find(
(event) => event.returnValues.sequenceNumber === sequenceNumber
);
// If the event is found, log the result and stop polling.
if (event !== undefined) {
const values = events[0].returnValues
console.log(`✅ NFT ID ${values.tokenId} minted to ${values.minter}, based on sequenceNumber ${values.sequenceNumber}`)
clearInterval(intervalId);
}
}, 2000);
}
requestMint();
现在运行:
# FROM: ./pyth-entropy/app
node requestMint.js;
# [Expected Output]:
# Generating user random number and commitment...
# User Random Number: <0xUSER_RANDOM_NUMBER>
# Fetching request fee...
# Request Fee: 101
# Requesting NFT mint...
# Request Transaction Hash: <0xTX_HASH>
# Sequence Number: 116
# ✅ NFT ID 168 minted to <YOUR_WALLET_ADDRESS>, based on sequenceNumber 116
requestMint
脚本执行两个主要步骤:
步骤 1:我们首先初始化用户钱包和用于与 NFT 及 Entropy 合约交互的 web3 合约。
步骤 2:生成一个随机数并将其与所需费用一起传递给 EntropyNFT 的 requestMint
函数。我们从 Entropy 合约中收到一个 sequenceNumber
作为返回值。
步骤 3:在提交请求后,自动化的 Pyth 服务将生成一个随机数,并执行我们 NFT 合约中的 entropyCallback
函数。脚本的这一部分监控合约中的 Minted
事件,表明 Entropy 请求已完成,且基于随机数为您的钱包铸造了一个可验证的随机 NFT。
恭喜! 您已成功利用 Pyth 的 Entropy 服务提供了一个可证明公平的 NFT 铸造流程 🎉
Entropy 回顾 🔮
Pyth Entropy 扩展了经典的提交/揭示机制以生成随机数。首先,两个参与方分别提交一个秘密值(生成的随机数)。在揭示阶段,基于两个随机数的哈希生成最终的随机数。
在我们的 NFT 合约中,随机数生成流程如下,重点放在不同的参与方上:
- 用户在 NFT 合约的
requestMint
调用中提交一个随机数。 - 一个链下服务(机器人)生成第二个随机数。
- 机器人通过
revealWithCallBack
调用将这两个随机数发送到 Pyth 的 Entropy 合约。这最终调用我们的 NFT 合约中的entropyCallback
函数,将随机化的 NFT 铸造给用户。
注意:从用户的角度来看,他们最初的请求随机数交易(requestMint
)不会直接铸造 NFT。相反,用户将在由机器人发起的后续调用中收到 NFT。
此过程中有很多步骤,以下是一个流程图帮助解释这些交互:
Pyth Entropy Sequence Diagram
在 Pyth 的文档中了解 Entropy 设计的详细信息。
🐻 完整代码库
如果您想查看最终代码并查看其他指南,请查阅 Berachain Pyth Entropy 指南代码。
https://github.com/berachain/guides/tree/main/apps/pyth-entropy
🛠️ 想构建更多项目吗?
想在 Berachain 上构建更多项目并查看更多示例吗?请查看我们的 Berachain GitHub 指南库,其中包含各种实现,包括 NextJS、Hardhat、Viem、Foundry 等。
如果您想深入了解更多细节,请查看我们的 Berachain 文档。
寻找开发支持?
请务必加入我们的 Berachain Discord 服务器,并查看我们的开发者频道以提问。
❤️ 别忘了通过分享和点赞这篇文章来表达支持。
原创文章,作者:Erica,如若转载,请注明出处:https://www.dappchaser.com/fair-random-nft-mints-with-pyth-entropy-on-berachain/