本記事は下記の翻訳となります。
『Fair Random NFT Mints with Pyth Entropy on Berachain』
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
Installing Foundry To Your Computer
インストールの詳細については、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
依存関係のインストール
いくつかの依存関係を利用するので、以下にインストールします:
# 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
Install Contract Dependencies & 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 File For Remapping
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 と定義し、availableTokenIds
配列を[0…499]で初期化します。
requestMint関数は、ユーザーがユーザーコミットメント(詳細は後述)を提供し、必要な手数料を支払うことで、Entropy
からランダム数をリクエストすることを可能にします。返された sequenceNumber
は、そのシーケンス番号に対応するランダム数で NFT をミントする権利を呼び出し元にインデックス付けします。
entropyCallback関数は、分散型キーパーボットがランダム数のリクエストを満たした時に呼び出され、最終的に固有のトークン ID で NFT をミントします。これは、ランダム数をミントされるべき残りの NFT 数で剰余を取ることで達成され、ランダムなインデックスが availableTokenIds
配列の範囲内にあることを保証します。使用済みの tokenIds
は配列の最後の要素と交換され、利用可能な tokenIds
のリストを維持します。
プロジェクトと共に生成された 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 Testnet へのデプロイ
まず、スマート・コントラクトをコンパイルします:
# FROM: ./pyth-entropy
forge build;
./out
ディレクトリに多くのビルド出力があることに気づくでしょう。
Berachain Testnet に新しいコントラクトをデプロイするために、forge create
コマンドを活用する予定です(コマンドについての詳細はこちら):
# 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>
.env
ファイルのENTROPY_NFT_ADDRESS
に、デプロイしたコントラクトのアドレスを入力してください。
デプロイメント手数料を支払うには$BERA が必要です。フォーセット(無料配布)の資金はhttps://artio.faucet.berachain.com/で入手できます。
ランダム化された NFT のミント
さて、JavaScript を使用して新しい NFT コントラクトと対話しましょう。スクリプトを格納する新しいフォルダを作成しましょう(2 つのスクリプトを作成します):
# FROM ./pyth-entropy
mkdir app;
cd app;
touch requestMint.js;
./app/requestMint.js`に以下のコードを貼り付けます:
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
スクリプトは主に 2 つの手順を実行します:
ステップ 1: まず、ユーザーウォレットと NFT および Entropy コントラクトと対話するための web3 コントラクトを初期化します。
ステップ 2: ランダムな数字が生成され、必要な手数料と共にEntropyNFT
のrequestMint
関数に渡されます。その見返りとして、Entropy コントラクトからsequenceNumber
を受け取ります。
ステップ 3: リクエストを送信した後、自動化された Pyth サービスがランダムな数字を生成し、NFT コントラクトのentropyCallback
関数を実行します。このスクリプトの部分は、コントラクトのMinted
イベントを監視します。このイベントは、Entropy リクエストが満たされ、そのランダムな数字を使用して検証可能なランダム NFT があなたのウォレットにミントされたことを示します。
おめでとうございます!Pyth の Entropy サービスを活用して、証明可能な公平な NFT ミントプロセスを提供することに成功しました 🎉
Entropy の概要 🔮
Pyth Entropy は、ランダムな数字を生成するための古典的なコミット/リビールスキームを拡張しています。まず、2 つの当事者が個別に秘密の値(生成されたランダムな数字)にコミットします。リビールフェーズでは、2 つのランダムな数字のハッシュに基づいて最終的なランダムな数字が生成されます。
NFT コントラクトの文脈では、ランダムな数字の生成は以下のように機能し、関与する異なる当事者に重点を置いています:
- ユーザーが NFT コントラクトの
requestMint
呼び出しでランダムな数字にコミットします - オフチェーンサービス(ボット)が 2 番目のランダムな数字を生成します
- ボットは 2 つのランダムな数字を Pyth の
Entropy
コントラクトにrevealWithCallBack
呼び出しを通じて送信します。これにより最終的に、NFT コントラクトのentropyCallback
が呼び出され、ランダム化された NFT がユーザーにミントされます
注意: ユーザーの視点からは、ランダムな数字をリクエストする最初のトランザクション(requestMint
)は NFT をミントしません。むしろ、ユーザーはボットによって開始される後続の呼び出しで NFT を受け取ります。
多くの動く部分があるので、ここに相互作用を分解するのに役立つフロー図があります:
Entropy の設計の詳細は、Pyth's docsを参照してください。
🐻 Full Code Repository
最終的なコードや他のガイドを見たい場合は、Berachain Pyth Entropy Guide Code をチェックしてください。
🛠️ さらに開発を進めたいですか?
Berachain でさらに開発を進めたい、より多くの実装例を見たいとお考えですか?私たちBerachain GitHub Guides Repo をぜひご覧ください。このリポジトリには、NextJS、Hardhat、Viem、Foundry など、様々な技術を使用した幅広い実装例が用意されています。
より詳細な情報を探求したい場合は、Berachain ドキュメントをご覧ください。
開発者サポートをお探しですか?
質問をするために、Berachain Discordサーバーに参加し、開発者チャンネルをチェックしてください。
❤️ この記事に対して愛を示すのを忘れないでください 👏🏼
【Sunrise とは】
Sunrise は Proof of Liquidity(PoL)と Fee Abstraction(手数料抽象化)を備えたデータ可用性レイヤーです。 私たちは DA の体験を再構築し、多様なエコシステムからのモジュラー型流動性を活用してロールアップを立ち上げています。
【Social Links】
【お問合せ】
Sunrise へのお問い合わせはこちらから 👉 Google Form