diff --git a/apps/developer-hub/content/docs/entropy/best-practices.mdx b/apps/developer-hub/content/docs/entropy/best-practices.mdx deleted file mode 100644 index 1daf41e6c4..0000000000 --- a/apps/developer-hub/content/docs/entropy/best-practices.mdx +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Best Practices -description: Best practices for using Pyth Entropy in your applications ---- - -# Best Practices - -## Limit gas usage on the callback - -Keeping the callback function simple is crucial because the entropy providers limit gas usage. -This ensures gas usage is predictable and consistent, avoiding potential issues with the callback. - -For example, if you want to use entropy to generate a random number for each player in a round of game, -you need to make sure that the callback function works for the maximum number of players that can be in each round. -Otherwise, the callbacks will work for some rounds with fewer players, but will fail for rounds with more players. - -Multiple solutions are possible to address this problem. You can store the random number received from the callback and -either ask users to submit more transactions after the callback to continue the flow or run a background crank service -to submit the necessary transactions. - -The gas limit for each chain is listed on the [contract addresses](contract-addresses) page. - -## Handling callback failures - -While the default entropy provider is highly reliable, in rare cases a callback might not be received. This typically happens when there's an issue with your contract's callback implementation rather than with the provider itself. The most common causes are: - -1. The callback function is using more gas than the allowed limit -2. The callback function contains logic that throws an error - -If you're not receiving a callback, you can manually invoke it to identify the specific issue. This allows you to: - -- See if the transaction fails and why -- Check the gas usage against the chain's callback gas limit -- Debug your callback implementation - -For detailed instructions on how to manually invoke and debug callbacks, refer to the [Debug Callback Failures](debug-callback-failures) guide. - -## Generating random values within a specific range - -You can map the random number provided by Entropy into a smaller range using the solidity [modulo operator](https://docs.soliditylang.org/en/latest/types.html#modulo). -Here is a simple example of how to map a random number provided by Entropy into a range between `minRange` and `maxRange` (inclusive). - -```solidity -// Maps a random number into a range between minRange and maxRange (inclusive) -function mapRandomNumber( - bytes32 randomNumber, - int256 minRange, - int256 maxRange -) internal pure returns (int256) { - require(minRange <= maxRange, "Invalid range"); - - uint256 range = uint256(maxRange - minRange + 1); - uint256 randomUint = uint256(randomNumber); - - return minRange + int256(randomUint % range); -} -``` diff --git a/apps/developer-hub/content/docs/entropy/contract-addresses.mdx b/apps/developer-hub/content/docs/entropy/chainlist.mdx similarity index 61% rename from apps/developer-hub/content/docs/entropy/contract-addresses.mdx rename to apps/developer-hub/content/docs/entropy/chainlist.mdx index d745ef6f8a..3554fccc52 100644 --- a/apps/developer-hub/content/docs/entropy/contract-addresses.mdx +++ b/apps/developer-hub/content/docs/entropy/chainlist.mdx @@ -1,12 +1,21 @@ --- -title: Contract Addresses +title: Chainlist and Fee Details description: Pyth Entropy contract addresses on EVM networks +icon: FileText --- import { EntropyTable } from "../../../src/components/EntropyTable"; ## Mainnets + + The fees for mainnet are dynamically set. Always use the on-chain method + `entropy.getFeeV2()` to get the current fee. + + +The following tables shows the total fees payable when using the **default provider**. +Note that the fees shown below will vary over time with prevailing gas prices on each chain. + The Entropy contract is deployed on the following mainnet chains: @@ -16,10 +25,16 @@ The Entropy contract is deployed on the following mainnet chains: The default provider on mainnet has a reveal delay to avoid changes on the outcome of the Entropy request because of block reorgs. The reveal delay shows how many blocks should be produced after the block including the request transaction in order to reveal and submit a callback transaction. -The default provider fulfills the request by sending a transaction with a gas limit as mentioned in above table. Entropy callbacks the consumer as part of this transaction. +The default provider fulfills the request by sending a transaction with a gas limit as mentioned in above table or as set by the user in the request. +Entropy callbacks the consumer as part of this transaction. ## Testnets + + The fees for testnets kept deliberately low and different from the mainnet + fees. + + The Entropy contract is deployed on the following testnet chains: diff --git a/apps/developer-hub/content/docs/entropy/create-your-first-entropy-app.mdx b/apps/developer-hub/content/docs/entropy/create-your-first-entropy-app.mdx index c69e437bc0..9a74eb1358 100644 --- a/apps/developer-hub/content/docs/entropy/create-your-first-entropy-app.mdx +++ b/apps/developer-hub/content/docs/entropy/create-your-first-entropy-app.mdx @@ -1,108 +1,328 @@ --- title: Create your first Entropy app on EVM -description: Build a coin flip application using Pyth Entropy +description: Build a coin flip example using Pyth Entropy +icon: RocketLaunch --- -# Create your first Entropy app on EVM - In this tutorial we will implement and deploy a coin flip contract which will use entropy to generate a random output. ## Preliminaries Before we start, please make sure you have the following tools installed: -- [Foundry](https://book.getfoundry.sh/getting-started/installation) -- [Node.js](https://nodejs.org/en/download) (version >= v18.0.0) +- [Foundry](https://book.getfoundry.sh/getting-started/installation). +- [Node](https://nodejs.org/en/download). Run `node -v{:js}` to confirm. You should get an output with version >= `v18.0.0`. ## Getting Started -Create a directory named `coin-flip` and initialize a new Foundry project: +Create a directory named `coin-flip{:bash}` in your filesystem: -```bash +```bash copy mkdir coin-flip cd coin-flip +``` + +Let's initialize a new project in `coin-flip{:bash}` by running `forge init contracts{:bash}`: + +```bash copy forge init contracts ``` -Install the Pyth Entropy SDK: +This will create a new directory in `coin-flip{:bash}` named `contracts/src`, which will contain the smart contract code. +We will use this directory as the working directory for the rest of the tutorial. + +Now we will install the Pyth Entropy SDK in the `contracts` directory: -```bash +```bash copy cd contracts npm init -y npm install @pythnetwork/entropy-sdk-solidity ``` -Add a `remappings.txt` file to the `contracts` directory: +Add a `remappings.txt` file to `contracts` directory with the following content to tell Foundry where to find the Pyth Entropy SDK: -```text +```text copy filename="remappings.txt" @pythnetwork/entropy-sdk-solidity/=node_modules/@pythnetwork/entropy-sdk-solidity ``` -## Smart Contract Implementation +## Implementation -Replace the content of `contracts/src/Counter.sol` with the following coin flip contract: +Create a new file `CoinFlip.sol{:solidity}` in `contracts/src` directory and add the following code into it to start: -```solidity -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; +```solidity copy filename="CoinFlip.sol" +// contracts/src/CoinFlip.sol +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; -import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol"; +import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol"; import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol"; contract CoinFlip is IEntropyConsumer { - IEntropy entropy; + event FlipRequested(uint64 sequenceNumber); + event FlipResult(uint64 sequenceNumber, bool isHeads); - struct FlipRequest { - address player; - bool isHeads; - } + IEntropyV2 entropy; + + constructor(address \_entropy) { + entropy = IEntropyV2(\_entropy); + } + + // This method is required by the IEntropyConsumer interface + function getEntropy() internal view override returns (address) { + return address(entropy); + } +} + +``` - mapping(uint64 => FlipRequest) public requests; - mapping(address => uint256) public results; // 0 = not played, 1 = won, 2 = lost +The code implements a`CoinFlip` contract which inherits the `IEntropyConsumer` interface. +We have also defined some events, properties and a constructor to instantiate the contract. +One of the properties is of type `IEntropyV2` which is an interface imported from the Entropy SDK. - constructor(address entropyAddress) { - entropy = IEntropy(entropyAddress); +### Request a coin flip + +Copy the following code into `CoinFlip.sol{:solidity}`: + +```solidity copy filename="CoinFlip.sol" +contract CoinFlip { + // ... prior code omitted + +function request() external payable { +// get the required fee +uint128 requestFee = entropy.getFeeV2(); +// check if the user has sent enough fees +if (msg.value < requestFee) revert("not enough fees"); + + // pay the fees and request a random number from entropy + uint64 sequenceNumber = entropy.requestV2{ value: requestFee }(); + + // emit event + emit FlipRequested(sequenceNumber); } +} +``` + +Users will invoke the `request` method to initiate a coin flip, paying a fee in the process. +The method first retrieves the fee required to request a random number from Entropy. +It then includes the fee in the `requestV2{:bash}` method call to Entropy. +Finally, the method emits a `FlipRequested{:bash}` event with a `sequenceNumber`. This event is also defined in the code snippet above. + +### Handle the callback + +Copy the following code into `CoinFlip.sol{:solidity}`: + +```solidity copy filename="CoinFlip.sol" +contract CoinFlip { + // ... prior code omitted - function flipCoin(bool isHeads) external payable { - uint256 fee = entropy.getFee(entropy.getDefaultProvider()); - require(msg.value >= fee, "Insufficient fee"); +function entropyCallback( + uint64 sequenceNumber, + // If your app uses multiple providers, you can use this argument + // to distinguish which one is calling the app back. This app only + // uses one provider so this argument is not used. + address \_providerAddress, + bytes32 randomNumber + ) internal override { + bool isHeads = uint256(randomNumber) % 2 == 0; + emit FlipResult(sequenceNumber, isHeads); + } +} + +``` + +Implement `entropyCallback` method which is required by the `IEntropyConsumer` Interface. Entropy calls back this method to fulfill a request. Entropy will call back this +method with the `sequenceNumber` of the request, the `_providerAddress` from which the random number was requested and the generated `randomNumber`. +Finally, the method emits a `FlipResult` event with the result of the flip. + +Yay! you have successfully implemented a coin flip contract. + +## Deploy + +First, create a new wallet: + +```bash copy +cast wallet new +``` + +This command will generate a new Ethereum keypair, producing output similar to the following. Note that the address and private key will be different hexadecimal values: + +```bash copy +Successfully created new keypair. +Address: 0xB806824fdA4b2b6631e9B87a86d42C9dfd04D129 +Private key: 0x0d510c72fd2279155c717eb433ae598a83cfb34b09c2ada86bc424b481082023 +``` + +We will export the values from the command above as environment variables to simplify the commands below. We will also export the RPC URL of the network. Run the following commands in your shell substituting the address and private key in the indicated places: + +```bash copy +export ADDRESS=
+export PRIVATE_KEY= +export RPC_URL="https://sepolia.optimism.io" +``` + +Next, use the [Superchain Faucet](https://app.optimism.io/faucet?utm_source=docs) to claim some test Sepolia ETH. Paste the address from the command above into the faucet to get your ETH. You can verify that the ETH has arrived in your wallet by running the command - bytes32 userRandomNumber = keccak256(abi.encode(block.timestamp, msg.sender)); - uint64 sequenceNumber = entropy.requestWithCallback{value: fee}( - entropy.getDefaultProvider(), - userRandomNumber - ); +```bash copy +cast balance $ADDRESS -r $RPC_URL -e +``` - requests[sequenceNumber] = FlipRequest(msg.sender, isHeads); +The final step before deploying is to get the arguments for the contract's constructor: the [Entropy contract address](./entropy/chainlist) for Optimism Sepolia and [the Provider address](./entropy/chainlist). We will also export these values as environment variables for convenience: + +```bash copy +export ENTROPY_ADDRESS=0x4821932D0CDd71225A6d914706A621e0389D7061 +``` + +Finally, let's deploy the contracts. Run the following command: + +```bash copy +forge create src/CoinFlip.sol:CoinFlip \ +--private-key $PRIVATE_KEY \ +--rpc-url $RPC_URL \ +--constructor-args $ENTROPY_ADDRESS +``` + +You should see an output similar to: + +```bash copy +[⠢] Compiling... +[⠔] Compiling 28 files with 0.8.23 +[⠑] Solc 0.8.23 finished in 3.40s +Compiler run successful! +Deployer: 0xfa57d0f2CBDA2729273F2a431E4FeDAc656d0402 +Deployed to: 0x8676ba0Dd492AB9813BC21D5Dce318427d1d73ae +Transaction hash: 0x2178aa6d402c94166a93e81822248d00dd003827675ebd49b3c542970f5a0189 +``` + +Let's export the coin flip contract address as environment variable for later use: + +```bash copy +export COINFLIP_ADDRESS= +``` + +Congratulations you have successfully implemented and deployed a CoinFlip contract. + +## Interact from Javascript + +Next, let's interact with the CoinFlip contract from Javascript. Create a new directory inside `coin-flip` named `app`. Run `cd app` to make it your terminal’s working directory — the following commands will need to be run from here. + +Run the following to initialise a new project and install required libraries: + +```bash copy +npm init -y +npm install web3 @pythnetwork/entropy-sdk-solidity +``` + +Create a `script.js` file in `app` and add the following code to the script: + +```javascript copy filename="script.js" +const { Web3 } = require("web3"); +const CoinFlipAbi = require("../contracts/out/CoinFlip.sol/CoinFlip.json"); +const EntropyAbi = require("@pythnetwork/entropy-sdk-solidity/abis/IEntropyV2.json"); + +async function main() { + const web3 = new Web3(process.env["RPC_URL"]); + const { address } = web3.eth.accounts.wallet.add( + process.env["PRIVATE_KEY"], + )[0]; + + web3.eth.defaultBlock = "finalized"; + + const coinFlipContract = new web3.eth.Contract( + CoinFlipAbi.abi, + process.env["COINFLIP_ADDRESS"], + ); + + const entropyContract = new web3.eth.Contract( + EntropyAbi, + process.env["ENTROPY_ADDRESS"], + ); +} + +main(); +``` + +The code above imports the required libraries and defines a `main` method. In `main` we initialize web3 contracts that help us interact with the coin flip and entropy contracts. At the end, the script calls the main method. + +Next, add the following code to the main method to request a flip from the CoinFlip contract. + +```javascript copy filename="script.js" +async main() { + // ... prior code omitted + +// Request a random number + + const fee = await entropyContract.methods.getFeeV2().call() + console.log("fee: ${fee}"); + + const requestReceipt = await coinFlipContract.methods + .request() + .send({ + value: fee, + from: address, + }); + + console.log("request tx: ${requestReceipt.transactionHash}"); + // Read the sequence number for the request from the transaction events. + const sequenceNumber = + requestReceipt.events.FlipRequested.returnValues.sequenceNumber; + console.log("sequence: ${sequenceNumber}"); +} +``` + +The code snippet above generates a random number. The code calls the Entropy contract to get the fee required for requesting a random number. Then it calls the request method of the CoinFlip contract with the `userRandomNumber` as an argument and the required fee. Finally, the code reads the sequenceNumber from the `FlipRequested` event emitted by the CoinFlip contract. + +Finally, add the following code snippet to get the flip result: + +```javascript copy filename="script.js" +async main() { + // ... prior code omitted + +let fromBlock = requestReceipt.blockNumber; +const intervalId = setInterval(async () => { +const currentBlock = await web3.eth.getBlockNumber(); + + if(fromBlock > currentBlock) { + return; } - function entropyCallback( - uint64 sequenceNumber, - address, - bytes32 randomNumber - ) internal override { - FlipRequest memory request = requests[sequenceNumber]; - bool coinIsHeads = uint256(randomNumber) % 2 == 0; - - if (coinIsHeads == request.isHeads) { - results[request.player] = 1; // won - } else { - results[request.player] = 2; // lost - } + // Get 'FlipResult' events emitted by the CoinFlip contract for given block range. + const events = await coinFlipContract.getPastEvents("FlipResult", { + fromBlock: fromBlock, + toBlock: currentBlock, + }); + fromBlock = currentBlock + 1n; + + // 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) { + console.log("result: ${event.returnValues.isHeads ? 'Heads' : 'Tails'}"); + clearInterval(intervalId); } + +}, 1000); } ``` -## Deployment +The code above polls for new `FlipResult` events emitted by the CoinFlip contract. It checks if the event has the same `sequenceNumber` as the request. If it does, it logs the result and stops polling. + +That's it, Let's run the script with the command `node script.js` . You should get an output similar to: -Create a deployment script and deploy to Optimism Sepolia testnet. Check the [contract addresses](contract-addresses) page for the Entropy contract address on your chosen network. +```bash copy +fee : 101 +request tx : 0xde0dce36a3c149b189aba8b29cad98375a62a811e65efdae28b28524da59cfb6 +sequence : 42 +result : Tails +``` + +Note that: the script can fail due to transient RPC issues. You can run the script again to get the expected result. ## Next Steps -- Add more complex game logic -- Implement proper error handling -- Add events for better tracking -- Consider gas optimization techniques +Congratulations! You've built your first app using Entropy. In this tutorial, we created a Solidity contract that generates a random flip using Entropy. We deployed the contract and interacted with it from Javascript. + +You can learn more about Entropy from the following links: -For a complete example with deployment scripts and frontend integration, see the [Coin Flip example](https://github.com/pyth-network/pyth-examples/tree/main/entropy/coin_flip) in the Pyth examples repository. +- [Protocol Design](./entropy/protocol-design) +- [How to Transform Entropy Results](./entropy/transform-entropy-results) diff --git a/apps/developer-hub/content/docs/entropy/current-fees.mdx b/apps/developer-hub/content/docs/entropy/current-fees.mdx deleted file mode 100644 index 57688fbd50..0000000000 --- a/apps/developer-hub/content/docs/entropy/current-fees.mdx +++ /dev/null @@ -1,39 +0,0 @@ ---- -title: Current Fees -description: Current fees for using Pyth Entropy ---- - -# Current Fees - -The following tables shows the total fees payable when using the **default provider**. -Note that the fees shown below will vary over time with prevailing gas prices on each chain. - -## Mainnet - -> ⚠️ **Warning** -> The fees for mainnet are dynamically set. Always use the on-chain method `entropy.getFeeV2()` to get the current fee. - -| Network | Fee (Native Token) | -| --------- | ------------------ | -| Ethereum | Dynamic | -| Arbitrum | Dynamic | -| Avalanche | Dynamic | -| Base | Dynamic | -| BNB Chain | Dynamic | -| Optimism | Dynamic | -| Polygon | Dynamic | - -## Testnet - -> ℹ️ **Note** -> The fees for testnets kept deliberately low and different from the mainnet fees. - -| Network | Fee (Native Token) | -| ---------------- | ------------------ | -| Ethereum Sepolia | 0.0001 ETH | -| Arbitrum Sepolia | 0.0001 ETH | -| Avalanche Fuji | 0.01 AVAX | -| Base Sepolia | 0.0001 ETH | -| BNB Testnet | 0.001 BNB | -| Optimism Sepolia | 0.0001 ETH | -| Polygon Amoy | 0.01 MATIC | diff --git a/apps/developer-hub/content/docs/entropy/debug-callback-failures.mdx b/apps/developer-hub/content/docs/entropy/debug-callback-failures.mdx index 97b0a8f2f6..794d0fc758 100644 --- a/apps/developer-hub/content/docs/entropy/debug-callback-failures.mdx +++ b/apps/developer-hub/content/docs/entropy/debug-callback-failures.mdx @@ -1,16 +1,15 @@ --- title: Debug Callback Failures description: How to identify and resolve issues with Entropy callbacks +icon: Bug --- -import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; - This guide explains how to identify and resolve issues with the Entropy callback. The intended audience for this guide is developers who have made an Entropy random number request, but their application hasn't received a callback. > 🔍 **Quick Debug Tool** > -> Use the [Entropy Explorer](https://entropy-debugger.pyth.network/) to quickly diagnose and resolve callback issues. +> Use the [Entropy Explorer](https://entropy-explorer.pyth.network/) to quickly diagnose and resolve callback issues. ## Dependencies @@ -23,27 +22,24 @@ Developers can run the Entropy callback themselves to see the reason for the fai To run the callback, invoke the `revealWithCallback` function on the Entropy contract on your blockchain. The function has the following signature: - +``` This call requires the chain ID, contract address, and four other arguments. -The chain ID and contract address can be retrieved from [Contract Addresses](contract-addresses). +The chain ID and contract address can be retrieved from [Chainlist and Fee Details](./entropy/chainlist) page. Export these values as environment variables for later use: - + +``` Three of the other arguments can be retrieved from the request transaction's event logs. Look at the event logs of the request transaction in a block explorer. @@ -51,13 +47,11 @@ You should see a `RequestedWithCallback` event emitted from the Entropy contract Copy the following values from the event into environment variables: - +``` The fourth argument (provider contribution) must be retrieved from the provider's API. This value becomes available after the reveal delay has passed. @@ -68,10 +62,10 @@ There are a few common issues that can cause the callback to fail. ### Gas Limit Exceeded -If your callback function uses too much gas, the transaction will fail. Check the gas limit for your chain on the [contract addresses](contract-addresses) page and ensure your callback function uses less gas. +If your callback function uses too much gas, the transaction will fail. Check the gas limit for your chain on the [chainlist](./entropy/chainlist) page and ensure your callback function uses less gas. > 💡 **Tip** -> Refer to the [Set Custom Gas Limits](set-custom-gas-limits) guide to set a custom gas limit for your callback function. +> Refer to the [Set Custom Gas Limits](./entropy/set-custom-gas-limits) guide to set a custom gas limit for your callback function. ### Callback Function Errors @@ -85,4 +79,4 @@ Your callback function might contain logic that throws an error. Review your cal ### Transaction Timing Make sure you're attempting the callback after the reveal delay has passed. The reveal delay varies by network and helps prevent MEV attacks. -Refer to the [Contract Addresses](contract-addresses) page for the reveal delay for each network. +Refer to the [chainlist](./entropy/chainlist) page for the reveal delay for each network. diff --git a/apps/developer-hub/content/docs/entropy/error-codes.mdx b/apps/developer-hub/content/docs/entropy/error-codes.mdx index 932d583e7e..2802545b22 100644 --- a/apps/developer-hub/content/docs/entropy/error-codes.mdx +++ b/apps/developer-hub/content/docs/entropy/error-codes.mdx @@ -1,10 +1,9 @@ --- title: Error Codes -description: Error codes and their meanings for Pyth Entropy +description: On chain error codes from the Pyth Entropy EVM contracts +icon: Bug --- -# Error Codes - The following table contains the errors used in the Pyth Network's Entropy [EVM contracts](https://github.com/pyth-network/pyth-crosschain/blob/d290f4ec47a73636cf77711f5f68c3455bb8a8ca/target_chains/ethereum/contracts/contracts/entropy/Entropy.sol). This information is derived from [EntropyErrors.sol](https://github.com/pyth-network/pyth-crosschain/blob/d290f4ec47a73636cf77711f5f68c3455bb8a8ca/target_chains/ethereum/entropy_sdk/solidity/EntropyErrors.sol) in the Pyth EntropySDK and can be used to decode error codes programmatically. diff --git a/apps/developer-hub/content/docs/entropy/fees.mdx b/apps/developer-hub/content/docs/entropy/fees.mdx index a88aecc7b6..347ef78fd6 100644 --- a/apps/developer-hub/content/docs/entropy/fees.mdx +++ b/apps/developer-hub/content/docs/entropy/fees.mdx @@ -1,10 +1,9 @@ --- title: Fees description: Understanding Pyth Entropy fee structure +icon: CurrencyDollar --- -# Fees - The Entropy protocol has been designed to charge fees on a per-request basis. The total fee is the sum of the provider fee, which is determined by the individual provider on a per-blockchain basis, and the protocol fee, which is subject to Pyth governance decisions also on a per-chain @@ -18,4 +17,4 @@ Note that protocols integrating with Entropy can pass these fees along to their submits a transaction that requests a random number, the user can directly pay the entropy fees required with the native blockchain token. There are no fees for revealing the random numbers. -You can check the [current fees](current-fees) page to see the latest fees for each blockchain. +You can check the current fees in the [chainlist and fee details](./entropy/chainlist) page for each blockchain. diff --git a/apps/developer-hub/content/docs/entropy/generate-random-numbers-evm.mdx b/apps/developer-hub/content/docs/entropy/generate-random-numbers-evm.mdx index a01e2fa0a6..1db75566d4 100644 --- a/apps/developer-hub/content/docs/entropy/generate-random-numbers-evm.mdx +++ b/apps/developer-hub/content/docs/entropy/generate-random-numbers-evm.mdx @@ -1,10 +1,10 @@ --- -title: Generate Random Numbers onchain +title: Generate Random Numbers On-chain description: Learn how to integrate Pyth Entropy to generate random numbers in your dapp +icon: Code --- import { Step, Steps } from "fumadocs-ui/components/steps"; -import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; This guide explains how to integrate Pyth Entropy into EVM Contracts to generate on-chain random numbers. The intended audience for this guide is developers of any application that needs on-chain randomness, such as NFT mints or games. @@ -16,21 +16,21 @@ Install the SDK using your package manager: - +```shell copy +npm install @pythnetwork/entropy-sdk-solidity +``` - +``` Then add the following line to your `remappings.txt` file: - +```text copy +@pythnetwork/entropy-sdk-solidity/=node_modules/@pythnetwork/entropy-sdk-solidity +``` @@ -42,22 +42,21 @@ The Solidity SDK exports two interfaces: - [`IEntropyConsumer`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropyConsumer.sol) - The interface that your contract should implement. It makes sure that your contract is compliant with the Entropy contract. - [`IEntropyV2`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropyV2.sol) - The interface to interact with the Entropy contract. You will need the address of an Entropy contract on your blockchain. - Consult the current [Entropy contract addresses](../contract-addresses) to find the address on your chain. + Consult the current [Entropy contract addresses](./entropy/chainlist) to find the address on your chain. Once you have a contract address, instantiate an `console.log("IEntropyV2"){:bash}` contract in your solidity contract: - +``` ## Usage @@ -71,22 +70,21 @@ Invoke the [`requestV2`](https://github.com/pyth-network/pyth-crosschain/blob/ma The `console.log("requestV2"){:bash}` method requires paying a fee in native gas tokens which is configured per-provider. The fee differs for every chain and also varies over time depending on the chain's current gas price. -The current value for each chain can be found on the [Current Fees](../current-fees) page. +The current value for each chain can be found on the [chainlist and fee details](./entropy/chainlist) page. However, you should use the on-chain method [`getFeeV2`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol#L101) to compute the required fee and send it as the value of the `requestV2{:bash}` call. -These methods use the default randomness provider ([see here](#randomness-providers) for more info on providers). +These methods use the default randomness provider. - +``` This method returns a sequence number and emits a [`Requested`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/EntropyEventsV2.sol#L30) event. You can store this sequence number to identify the request in next step. -Note that there are several variants of `requestV2` that allow the caller to configure the provider fulfilling the request and the gas limit for the callback. Refer [request callback variants](../request-callback-variants.mdx) for more details. +Note that there are several variants of `requestV2` that allow the caller to configure the provider fulfilling the request and the gas limit for the callback. Refer [request callback variants](./entropy/request-callback-variants.mdx) for more details. Please see the method documentation in the [IEntropyV2 interface](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropyV2.sol). @@ -95,8 +93,8 @@ Please see the method documentation in the [IEntropyV2 interface](https://github ### Implement the Entropy callback - +``` @@ -152,7 +151,7 @@ When the final random number is ready to use, the entropyCallback function will The `entropyCallback` function on your contract should **never** return an error. If it returns an error, the keeper will not be able to invoke the callback. If you are having problems receiving the callback, please see - [Debugging Callback Failures](/entropy/debug-callback-failures). + [Debugging Callback Failures](./entropy/debug-callback-failures). ## Additional Resources @@ -161,19 +160,19 @@ You may find these additional resources helpful while integrating Pyth Entropy i ### Debug Callback Failures -Check how to [Debug Callback Failures](../debug-callback-failures) if you are having trouble getting the callback to run. +Check how to [Debug Callback Failures](./entropy/debug-callback-failures) if you are having trouble getting the callback to run. ### Pyth Entropy Contract Addresses -Consult the [Entropy contract addresses](../contract-addresses) to find the Entropy contract address on your chain. +Consult the [Entropy contract addresses](./entropy/chainlist) to find the Entropy contract address on your chain. ### Current Fees -Check the [Current Fees](../current-fees) to find the current fee for each provider on your chain. +Check the [Current Fees](./entropy/current-fees) to find the current fee for each provider on your chain. -### Best Practices +### Transform Entropy Results -Check out the [Best Practices](../best-practices) guide for tips to limit gas usage, or generate multiple random numbers in a single transaction. +Check out the [Transform Entropy Results](./entropy/transform-entropy-results) guide for tips to limit gas usage, or generate multiple random numbers in a single transaction. ### Randomness providers @@ -183,8 +182,6 @@ a keeper service for fullfilling requests. You can get the default provider's address by calling the [`getDefaultProvider`](https://github.com/pyth-network/pyth-crosschain/blob/f8ebeb6af31d98f94ce73edade6da2ebab7b2456/target_chains/ethereum/entropy_sdk/solidity/IEntropy.sol#L94) method: - +```solidity copy +address provider = entropy.getDefaultProvider(); +``` diff --git a/apps/developer-hub/content/docs/entropy/index.mdx b/apps/developer-hub/content/docs/entropy/index.mdx index d223592454..af289128e0 100644 --- a/apps/developer-hub/content/docs/entropy/index.mdx +++ b/apps/developer-hub/content/docs/entropy/index.mdx @@ -5,29 +5,57 @@ icon: DiceSix full: true --- +import { + RocketLaunch, + FileText, + DiceSix, + MagnifyingGlass, +} from "@phosphor-icons/react/dist/ssr"; + **Pyth Entropy** is an on-chain random number generator (RNG) designed for developers who need fair, unbiased, and cryptographically secure randomness. Whether you're building a blockchain game, NFT mint, lottery, or simulation, Entropy delivers randomness that is: -- **Trustless & verifiable** - built on commit-reveal(TODO: link to commit-reveal). -- **Low-latency** - randomness available within a few blocks(TODO: link to latency). -- **Easy to integrate** - Permissionless Integration, Visual Tx Explorer(TODO: link to explorer). -- **Cost-efficient** - designed for scalable production use(TODO: link to fees). +- **Trustless & verifiable** - built on [commit-reveal protocol](./entropy/protocol-design). +- **Low-latency** - randomness available within a [few blocks](./entropy/chainlist). +- **Easy to use** - get started in < 5 minutes, no registration required. +- **Cost-efficient** - designed for scalable production use [fees](./entropy/chainlist). - **Native gas fees** - pay with chain native token. ## What's New in Entropy v2 Entropy v2 introduces several improvements and new features to make random number generation more flexible and efficient. -See [What's New in Entropy v2](whats-new-entropyv2) for more details. -(TODO: This can be displayed in a banner above) (TODO: Add aan infographic here) - -## Getting Started - -Using Pyth Entropy is permissionless and developers can integrate in a few minutes. -Please see [How to Generate Random Numbers Using Pyth Entropy](generate-random-numbers) to start integrating Pyth Entropy into your application. - -## Reference Material - -- [Protocol design](protocol-design) -- [Contract Addresses/Supported Networks](contract-addresses) -- [Error Codes](error-codes) -- [Entropy Debugger](https://entropy-debugger.pyth.network/) - Interactive tool for diagnosing callback issues +See [What's New in Entropy v2](./entropy/whats-new-entropyv2) for more details. + +## Start Building + +
+ } + colorScheme="green" + /> + } + colorScheme="purple" + /> + } + colorScheme="blue" + /> + } + colorScheme="yellow" + external={true} + /> +
diff --git a/apps/developer-hub/content/docs/entropy/meta.json b/apps/developer-hub/content/docs/entropy/meta.json index 49040f31ab..c08cfda9f9 100644 --- a/apps/developer-hub/content/docs/entropy/meta.json +++ b/apps/developer-hub/content/docs/entropy/meta.json @@ -7,19 +7,22 @@ "---Introduction---", "index", "whats-new-entropyv2", + "---Tutorials---", + "create-your-first-entropy-app", "---How-To Guides---", "generate-random-numbers-evm", "set-custom-gas-limits", "debug-callback-failures", + "transform-entropy-results", "---Reference Material---", - "contract-addresses", - "best-practices", - "fees", - "protocol-design", - "current-fees", - "error-codes", - "examples", + "chainlist", "request-callback-variants", - "create-your-first-entropy-app" + "error-codes", + "[Example Applications](https://github.com/pyth-network/pyth-examples/tree/main/entropy)", + "[Entropy Explorer](https://entropy-explorer.pyth.network/)", + "[Fortuna API Reference](https://fortuna.dourolabs.app/docs/)", + "---Understanding Entropy---", + "protocol-design", + "fees" ] } diff --git a/apps/developer-hub/content/docs/entropy/protocol-design.mdx b/apps/developer-hub/content/docs/entropy/protocol-design.mdx index 7d01f55387..9b5eff697a 100644 --- a/apps/developer-hub/content/docs/entropy/protocol-design.mdx +++ b/apps/developer-hub/content/docs/entropy/protocol-design.mdx @@ -1,42 +1,53 @@ --- title: Protocol Design description: Understanding how Pyth Entropy generates secure random numbers +icon: Book --- -# Protocol Design - The Entropy protocol implements a secure 2-party random number generation procedure. The protocol is an extension of a simple commit/reveal protocol. The original version has the following steps: -1. Two parties A and B each randomly sample secret contributions to the random number, $x_A$ and $x_B$. -2. A commits to their number by sharing $h_A = \mathrm{hash}(x_A)$ +1. Two parties A and B each randomly sample secret contributions to the random number, $$(x_A)$$ and $$(x_B)$$. +2. A commits to their number by sharing $$(h_A) = hash(x_A).$$ 3. B reveals $x_B$ 4. A reveals $x_A$ -5. B verifies that $\mathrm{hash}(x_{A}) == h_A$ -6. The random number $r = \mathrm{hash}(x_A, x_B)$ +5. B verifies that $$hash(x_{A}) == h_A$$ +6. The random number $$r = hash(x_A, x_B)$$ This protocol has the property that the result is random as long as either A or B are honest. -Honesty means that (1) they draw their value at random, and (2) for A, they keep $x_A$ a secret until +Honesty means that (1) they draw their value at random, and (2) for A, they keep $$(x_A)$$ a secret until step 4. Thus, neither party needs to trust the other -- as long as they are themselves honest, they can -ensure that the result $r$ is random. +ensure that the result $$(r)$$ is random. Entropy implements a version of this protocol that is optimized for on-chain usage. The key difference is that one of the participants (the provider) commits to a sequence of random numbers up-front using a hash chain. Users of the protocol then simply grab the next random number in the sequence. -**Setup**: The provider P computes a sequence of $N$ random numbers, $x_i$ for $0 \leq i \leq N-1$: +**Setup**: The provider $$P$$ computes a sequence of $$N$$ random numbers, $$x_i$$ for $$0 \leq i \leq N-1$$: -- $x_{N-1} = \mathrm{random}()$ -- $x_i = \mathrm{hash}(x_{i + 1})$ +- $$x_{N-1} = random()$$ +- $$x_i = hash(x_{i + 1})$$ -The provider commits to $x_0$ by posting it to the Entropy contract. -Each random number in the sequence can then be verified against the previous one in the sequence by hashing it, i.e., $\mathrm{hash}(x_i) = x_{i - 1}$ +The provider commits to $$x_0$$ by posting it to the Entropy contract. +Each random number in the sequence can then be verified against the previous one in the sequence by hashing it, i.e., $$hash(x_i) = x_{i - 1}$$ **Request**: To produce a random number, the following steps occur. -1. The user randomly samples a secret value $x_u$ and sends a request to the Entropy contract containing $\mathrm{hash}(x_u)$. -2. The Entropy contract stores the request and assigns it a sequence number corresponding to the provider's next random number $x_i$. -3. The provider fulfills the request by revealing $x_i$ to the Entropy contract. -4. The Entropy contract verifies that $\mathrm{hash}(x_i) = x_{i-1}$ (the previous commitment), then computes the random number $r = \mathrm{hash}(x_u, x_i)$ and delivers it to the user's callback function. +1. The user randomly samples their contribution $$(x_U)$$ and submits it to the contract. + (Users may also run this step using an on-chain PRNG if they trust the validator to not collude with the provider.) +2. The contract remembers $$(x_U)$$ and assigns it an incrementing sequence number $$(i)$$, representing which + of the provider's random numbers the user will receive. +3. After sufficient block confirmations, the provider submits a transaction to the contract revealing their contribution $$(x_i)$$ to the contract. +4. The contract verifies $$hash(x_i) == x_{i-1}$$ to prove that $$(x_i)$$ is the $$(i)$$'th random number. + The contract stores $$(x_i)$$ as the $$(i)$$'th random number to reuse for future verifications. +5. If the condition above is satisfied, the random number $$(r) = hash(x_i, x_U)$$. +6. The contract submits a callback to the calling contract with the random number $$(r)$$. + +This flow is secure as long as several trust assumptions hold: + +- Providers are trusted to reveal their random number $$(x_i)$$ regardless of what the final result $$(r)$$ is. Providers can compute $$(r)$$ off-chain before they reveal $$(x_i)$$, which permits a censorship attack. +- Providers are trusted not to front-run user transactions (via the mempool or colluding with the validator). Providers who observe user transactions can manipulate the result by inserting additional reuests or rotating their commitment. +- Providers are trusted not to keep their hash chain a secret. Anyone with the hash chain can predict the result of a randomness request before it is requested, + and therefore manipulate the result. This applies both to users of the protocol as well as blockchain validators who can use this information to manipulate the on-chain PRNG or reorder user transactions. -This approach ensures that the resulting random number is unpredictable as long as either the user or the provider is honest, while being efficiently verifiable on-chain. +The code of default deployed provider can be found [here](https://github.com/pyth-network/pyth-crosschain/tree/7bccde484f01c19844b7105d63df207a24018957/apps/fortuna). diff --git a/apps/developer-hub/content/docs/entropy/request-callback-variants.mdx b/apps/developer-hub/content/docs/entropy/request-callback-variants.mdx index 0fc26f4308..a27f79ba49 100644 --- a/apps/developer-hub/content/docs/entropy/request-callback-variants.mdx +++ b/apps/developer-hub/content/docs/entropy/request-callback-variants.mdx @@ -1,13 +1,12 @@ --- title: Request Callback Variants description: Different ways to request random numbers with Pyth Entropy +icon: Code --- -# Request Callback Variants - The [`IEntropyV2`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropyV2.sol) interface provides multiple variants of the `requestV2` function: -## 1. Basic Request +## 1. Basic request ```solidity function requestV2() external payable returns (uint64 assignedSequenceNumber); @@ -31,7 +30,7 @@ function requestBasicRandomNumber() external payable { } ``` -## 2. Request with Gas Limit +## 2. Request with custom gas limit ```solidity function requestV2( @@ -59,7 +58,7 @@ function requestWithCustomGas() external payable { } ``` -## 3. Request with Provider +## 3. Request with custom provider ```solidity function requestV2( @@ -69,7 +68,7 @@ function requestV2( This variant allows you to specify a custom entropy provider instead of using the default one. -## 4. Full Custom Request +## 4. Full custom request ```solidity function requestV2( @@ -79,4 +78,4 @@ function requestV2( ) external payable returns (uint64 assignedSequenceNumber); ``` -This is the most flexible variant that allows you to specify all parameters: custom provider, gas limit, and user random number. +This is the most flexible variant that allows you to specify all parameters: custom provider, custom gas limit, and user random number. diff --git a/apps/developer-hub/content/docs/entropy/set-custom-gas-limits.mdx b/apps/developer-hub/content/docs/entropy/set-custom-gas-limits.mdx index 358cfc0c68..bfaa8c7f82 100644 --- a/apps/developer-hub/content/docs/entropy/set-custom-gas-limits.mdx +++ b/apps/developer-hub/content/docs/entropy/set-custom-gas-limits.mdx @@ -1,16 +1,16 @@ --- title: Set Custom Gas Limits description: How to set custom gas limits for Entropy callbacks +icon: Gauge --- import { Step, Steps } from "fumadocs-ui/components/steps"; -import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; -Custom gas limits are useful when your callback function requires more gas than the [default provider limit]TODO(../contract-addresses), or when you want to optimize gas costs for simpler callbacks. +Custom gas limits are useful when your callback function requires more gas than the [default provider limit](./entropy/chainlist), or when you want to optimize gas costs for simpler callbacks. ## Prerequisites -Before following this guide, you should first complete the basic setup from the [Generate Random Numbers in EVM Contracts](./generate-random-numbers-evm.mdx) guide. This guide builds upon that foundation and assumes you have: +Before following this guide, you should first complete the basic setup from the [Generate Random Numbers in EVM Contracts](./entropy/generate-random-numbers-evm.mdx) guide. This guide builds upon that foundation and assumes you have: - Installed the Pyth Entropy Solidity SDK - Set up your contract with the `IEntropyConsumer` interface @@ -31,37 +31,37 @@ You might need custom gas limits in these scenarios: Instead of the basic `requestV2()` method, use the variant that accepts a `gasLimit` parameter: - +``` ### 2. Calculate Fees with Custom Gas Limit When using custom gas limits, you must use the `getFeeV2` variant that accepts a `gasLimit` parameter: - +``` ### 3. Complete Example Here's a complete example showing how to implement custom gas limits: - bool) public processedRequests; -constructor(address entropyAddress) { -entropy = IEntropyV2(entropyAddress); -} - -// Request with custom gas limit for complex callback -function requestComplexRandomNumber() external payable { -uint32 customGasLimit = 200000; // Higher limit for complex operations -uint256 fee = entropy.getFeeV2(customGasLimit); - - require(msg.value >= fee, "Insufficient fee"); + constructor(address entropyAddress) { + entropy = IEntropyV2(entropyAddress); + } - uint64 sequenceNumber = entropy.requestV2{ value: fee }(customGasLimit); - // Store sequence number if needed for tracking + // Request with custom gas limit for complex callback + function requestComplexRandomNumber() external payable { + uint32 customGasLimit = 200000; // Higher limit for complex operations + uint256 fee = entropy.getFeeV2(customGasLimit); -} + require(msg.value >= fee, "Insufficient fee"); -// Request with lower gas limit for simple callback -function requestSimpleRandomNumber() external payable { -uint32 customGasLimit = 50000; // Lower limit for simple operations -uint256 fee = entropy.getFeeV2(customGasLimit); - - require(msg.value >= fee, "Insufficient fee"); + uint64 sequenceNumber = entropy.requestV2{ value: fee }(customGasLimit); + // Store sequence number if needed for tracking + } - uint64 sequenceNumber = entropy.requestV2{ value: fee }(customGasLimit); + // Request with lower gas limit for simple callback + function requestSimpleRandomNumber() external payable { + uint32 customGasLimit = 50000; // Lower limit for simple operations + uint256 fee = entropy.getFeeV2(customGasLimit); -} + require(msg.value >= fee, "Insufficient fee"); -// Complex callback that requires more gas -function entropyCallback( -uint64 sequenceNumber, -address provider, -bytes32 randomNumber -) internal override { -// Prevent duplicate processing -require(!processedRequests[sequenceNumber], "Already processed"); -processedRequests[sequenceNumber] = true; - - // Complex operations that require more gas - for (uint i = 0; i < 10; i++) { - // Simulate complex state changes - // This would require more gas than the default limit + uint64 sequenceNumber = entropy.requestV2{ value: fee }(customGasLimit); } - // Use the random number for your application logic - uint256 randomValue = uint256(randomNumber); - // Your application logic here... + // Complex callback that requires more gas + function entropyCallback( + uint64 sequenceNumber, + address provider, + bytes32 randomNumber + ) internal override { + // Prevent duplicate processing + require(!processedRequests[sequenceNumber], "Already processed"); + processedRequests[sequenceNumber] = true; + + // Complex operations that require more gas + for (uint i = 0; i < 10; i++) { + // Simulate complex state changes + // This would require more gas than the default limit + } + + // Use the random number for your application logic + uint256 randomValue = uint256(randomNumber); + // Your application logic here... + } -} + function getEntropy() internal view override returns (address) { + return address(entropy); + } -function getEntropy() internal view override returns (address) { -return address(entropy); -} } -`} /> +``` ## Gas Limit Constraints @@ -149,27 +147,23 @@ When setting custom gas limits, be aware of these constraints: Test your callback function to determine the actual gas usage: - +``` ### 2. Add Safety Buffer Always add a safety buffer to your estimated gas usage: - +``` ### 3. Handle Gas Limit Errors @@ -185,14 +179,12 @@ Be prepared to handle cases where your gas limit is insufficient: Higher gas limits result in higher fees. Balance your gas needs with cost considerations: - +``` ## Troubleshooting @@ -202,11 +194,10 @@ If you're experiencing issues with custom gas limits: 2. **High fees**: Consider optimizing your callback or using a lower gas limit 3. **Reverts**: Check that your gas limit doesn't exceed the maximum allowed -For more debugging help, see the [Debug Callback Failures](/entropy/debug-callback-failures) guide. +For more debugging help, see the [Debug Callback Failures](./entropy/debug-callback-failures) guide. ## Additional Resources -- [Generate Random Numbers in EVM Contracts](/entropy/generate-random-numbers/evm) - Basic setup guide -- [Best Practices](/entropy/best-practices) - General optimization tips -- [Current Fees](/entropy/current-fees) - Fee information for different chains -- [Contract Addresses](/entropy/contract-addresses) - Entropy contract deployments +- [Generate Random Numbers in EVM Contracts](./entropy/generate-random-numbers-evm) - Basic setup guide +- [Transform Entropy Results](./entropy/transform-entropy-results) - General optimization tips +- [Contract Addresses and Fee Details](./entropy/chainlist) - Entropy contract deployments and fee details diff --git a/apps/developer-hub/content/docs/entropy/transform-entropy-results.mdx b/apps/developer-hub/content/docs/entropy/transform-entropy-results.mdx new file mode 100644 index 0000000000..95b717dc85 --- /dev/null +++ b/apps/developer-hub/content/docs/entropy/transform-entropy-results.mdx @@ -0,0 +1,73 @@ +--- +title: Transform Entropy Results +description: Best practices for transforming entropy results in your applications +icon: Book +--- + +## Generating random values within a specific range + +You can map the random number provided by Entropy into a smaller range using the solidity [modulo operator](https://docs.soliditylang.org/en/latest/types.html#modulo). +Here is a simple example of how to map a random number provided by Entropy into a range between `minRange` and `maxRange` (inclusive). + +```solidity copy +// Maps a random number into a range between minRange and maxRange (inclusive) +function mapRandomNumber( + bytes32 randomNumber, + int256 minRange, + int256 maxRange +) internal pure returns (int256) { + require(minRange <= maxRange, "Invalid range"); + + uint256 range = uint256(maxRange - minRange + 1); + uint256 randomUint = uint256(randomNumber); + + return minRange + int256(randomUint % range); + +} +``` + +Notice that using the modulo operator can distort the distribution of random numbers if it's not a power of 2. This is +negligible for small and medium ranges, but it can be noticeable for large ranges. +For example, if you want to generate a random number between 1 and 52, the probability of having value 5 is approximately +`10^-77` higher than the probability of having value 50 which is infinitesimal. + +## Generating multiple random values in a single transaction + +If you need to generate multiple random values in a single transaction, you can hash the random input provided by Entropy with a unique identifier for each random number. + +In the following example, `mapRandomNumber` is used to generate 6 random attributes for a character. + +```solidity copy +function generateAttributes(bytes32 randomNumber) internal { + int256 strength = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "strength")), + 15, + 20 + ); + int256 stamina = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "stamina")), + 10, + 15 + ); + int256 agility = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "agility")), + 5, + 15 + ); + int256 stealth = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "stealth")), + 0, + 5 + ); + int256 positionX = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "positionX")), + -100, + 100 + ); + int256 positionY = mapRandomNumber( + keccak256(abi.encodePacked(randomNumber, "positionY")), + -100, + 100 + ); +} +``` diff --git a/apps/developer-hub/content/docs/entropy/whats-new-entropyv2.mdx b/apps/developer-hub/content/docs/entropy/whats-new-entropyv2.mdx index 9fb63357e6..44b09268e3 100644 --- a/apps/developer-hub/content/docs/entropy/whats-new-entropyv2.mdx +++ b/apps/developer-hub/content/docs/entropy/whats-new-entropyv2.mdx @@ -4,8 +4,6 @@ description: New features and improvements in Entropy v2 icon: Sparkle --- -import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock"; - ## Key Improvements Pyth Entropy v2 brings new features and improvements that make random number generation more flexible, efficient, and easier to integrate. @@ -19,46 +17,26 @@ Entropy v2 provides multiple ways to request random numbers: - **Custom Provider**: Choose specific entropy providers - **Full Control**: Specify all parameters (provider, gas limit, user random number) -Each of these request types is described in more detail with examples in [Request Callback Variants](request-callback-variants). - -### 2. Improved Fee Structure - -The new version offers more granular fee calculation: - - - -### 3. Enhanced Callback Status +### 2. Enhanced Callback Status Entropy V2 introduces callback statuses, which allow users to track the status of their callbacks. [Pyth Dev-Forum Announcement](https://dev-forum.pyth.network/t/announcing-entropy-v2/324#p-649-enhanced-callback-statuses-2) provides more details on enhanced callback statuses. -### 4. Entropy Debugger +### 3. Entropy Explorer Entropy V2 includes a public Entropy Explorer, that lets teams easily track the status of their callbacks and re-request them if they fail on-chain. - -See [Entropy Explorer](https://entropy-debugger.pyth.network/) to search and debug your callbacks. - -### 5. Gas Optimization - -Improved contract efficiency reduces overall gas costs for entropy requests. +See [Entropy Explorer](https://entropy-explorer.pyth.network/) to search and debug your callbacks. ## Migration Guide If you're upgrading from Entropy v1 to v2: -(TODO: Add links to the interface) -1. Update your imports to use `IEntropyV2` -2. Replace `request()` calls with `requestV2()` -3. Update fee calculation to use `getFeeV2()` +1. Update your imports to use [`IEntropyV2`](https://github.com/pyth-network/pyth-crosschain/blob/main/target_chains/ethereum/entropy_sdk/solidity/IEntropyV2.sol). +2. Replace `request()` calls with [`requestV2()`](https://github.com/pyth-network/pyth-crosschain/blob/f5402df8e44158c519496bcd2fac5d3afe856a0e/target_chains/ethereum/entropy_sdk/solidity/IEntropyV2.sol#L26) +3. Update fee calculation to use [`getFeeV2()`](https://github.com/pyth-network/pyth-crosschain/blob/f5402df8e44158c519496bcd2fac5d3afe856a0e/target_chains/ethereum/entropy_sdk/solidity/IEntropyV2.sol#L135) 4. Test thoroughly with the new interface ## Backward Compatibility diff --git a/apps/developer-hub/content/docs/express-relay/how-express-relay-works/index.mdx b/apps/developer-hub/content/docs/express-relay/how-express-relay-works/index.mdx index 1fdac60df9..101f148b73 100644 --- a/apps/developer-hub/content/docs/express-relay/how-express-relay-works/index.mdx +++ b/apps/developer-hub/content/docs/express-relay/how-express-relay-works/index.mdx @@ -51,12 +51,15 @@ The Express Relay contract collects payment from the Searchers and forwards a sh ## Key Components ### Off-chain Auction Server + The auction server receives bids from searchers and determines winners based on bid amounts and other criteria. It then submits winning transactions to the blockchain in the optimal order. ### On-chain Smart Contract + The Express Relay smart contract validates that transactions have won the auction and enforces payment collection. It also handles revenue distribution between protocols and other stakeholders. ### Permission System + Express Relay uses a permission system to ensure only auction winners can execute protected operations. This prevents frontrunning and ensures MEV capture. ## Benefits for Protocols @@ -82,7 +85,7 @@ Express Relay also provides advantages for searchers: Express Relay is suitable for any protocol with valuable, time-sensitive operations: - **Lending Protocols**: Liquidations of undercollateralized positions -- **Perpetual Protocols**: Liquidations and funding rate updates +- **Perpetual Protocols**: Liquidations and funding rate updates - **DEX Protocols**: Arbitrage opportunities and order book operations - **Derivatives Protocols**: Option exercises and settlement operations - **Cross-chain Protocols**: Bridge operations and relay transactions @@ -116,13 +119,13 @@ There are four types of participants in the Express Relay protocol: icon={} /> - } - /> +} +/> {children}; } diff --git a/apps/developer-hub/src/components/EntropyTable/index.tsx b/apps/developer-hub/src/components/EntropyTable/index.tsx index 5c0f2076cf..4adb3a3053 100644 --- a/apps/developer-hub/src/components/EntropyTable/index.tsx +++ b/apps/developer-hub/src/components/EntropyTable/index.tsx @@ -6,13 +6,20 @@ import type { RowConfig, } from "@pythnetwork/component-library/Table"; import { Table } from "@pythnetwork/component-library/Table"; +import IEntropyV2 from "@pythnetwork/entropy-sdk-solidity/abis/IEntropyV2.json"; import { useEffect, useRef, useState } from "react"; +import type { PublicClient, Abi } from "viem"; +import { createPublicClient, formatEther, http, isAddress } from "viem"; import { FORTUNA_API_URLS } from "./constants"; import type { EntropyDeployment } from "./entropy-api-data-fetcher"; import { fetchEntropyDeployments } from "./entropy-api-data-fetcher"; import CopyAddress from "../CopyAddress"; +function isValidAddress(address: string): address is `0x${string}` { + return isAddress(address); +} + export const EntropyTable = ({ isMainnet }: { isMainnet: boolean }) => { const isLoading = useRef(false); const [state, setState] = useState(State.NotLoaded()); @@ -49,38 +56,44 @@ export const EntropyTable = ({ isMainnet }: { isMainnet: boolean }) => { } }; -type Col = "chain" | "address" | "delay" | "gasLimit"; +type Col = "chain" | "address" | "delay" | "gasLimit" | "fee"; const EntropyTableContent = ({ chains, }: { chains: Record; }) => { + const fees = useEntropyFees(chains); + const columns: ColumnConfig[] = [ { id: "chain", name: "Chain", isRowHeader: true }, { id: "address", name: "Contract" }, { id: "delay", name: "Reveal Delay" }, { id: "gasLimit", name: "Default Gas Limit" }, + { id: "fee", name: "Fee" }, ]; - const rows: RowConfig[] = Object.entries(chains).map( - ([chainName, d]) => ({ + const rows: RowConfig[] = Object.entries(chains) + .sort(([chainNameA], [chainNameB]) => chainNameA.localeCompare(chainNameB)) + .map(([chainName, deployment]) => ({ id: chainName, data: { chain: chainName, - address: d.explorer ? ( + address: deployment.explorer ? ( ) : ( - + ), - delay: d.delay, - gasLimit: d.gasLimit, + delay: deployment.delay, + gasLimit: deployment.gasLimit, + fee: + formatEther(fees[chainName] ?? BigInt(deployment.default_fee)) + + ` ${deployment.nativeCurrency ?? "ETH"}`, }, - }), - ); + })); return ( @@ -120,3 +133,80 @@ const getEntropyDeployments = async ( const url = isMainnet ? FORTUNA_API_URLS.mainnet : FORTUNA_API_URLS.testnet; return await fetchEntropyDeployments(url); }; + +function useEntropyFees( + chains: Record, +): Record { + const [feesByChain, setFeesByChain] = useState< + Record + >({}); + const clientsRef = useRef>(new Map()); + + function getClient(rpc: string): PublicClient { + const cached = clientsRef.current.get(rpc); + if (cached) return cached; + const client = createPublicClient({ + transport: http(rpc, { timeout: 10_000, retryCount: 1 }), + }); + clientsRef.current.set(rpc, client); + return client; + } + + useEffect(() => { + const entries = Object.entries(chains); + if (entries.length === 0) return; + + let isCancelled = false; + + async function loadInBatches() { + const batchSize = 5; + for (let i = 0; i < entries.length; i += batchSize) { + if (isCancelled) return; + const batch = entries.slice(i, i + batchSize); + const results = await Promise.allSettled( + batch.map(async ([chainName, deployment]) => { + if (!deployment.rpc || !isValidAddress(deployment.address)) { + return [chainName, undefined] as const; + } + try { + const client = getClient(deployment.rpc); + const fee = await client.readContract({ + address: deployment.address, + abi: IEntropyV2 as unknown as Abi, + functionName: "getFeeV2", + args: [], + }); + return [chainName, fee] as const; + } catch { + return [chainName, undefined] as const; + } + }), + ); + + const next: Record = {}; + for (const result of results) { + if (result.status === "fulfilled") { + const [chainName, fee] = result.value; + if (typeof fee === "bigint") { + next[chainName] = fee; + } + } + } + if (Object.keys(next).length > 0) { + setFeesByChain((prev) => ({ ...prev, ...next })); + } + } + } + + loadInBatches().catch((error: unknown) => { + // eslint-disable-next-line no-console + console.error("Failed to load entropy fees:", error); + }); + + return () => { + isCancelled = true; + }; + }, [chains]); + + return feesByChain; +} diff --git a/apps/developer-hub/src/components/IntegrationCard/index.module.scss b/apps/developer-hub/src/components/IntegrationCard/index.module.scss new file mode 100644 index 0000000000..f0b2d0e823 --- /dev/null +++ b/apps/developer-hub/src/components/IntegrationCard/index.module.scss @@ -0,0 +1,185 @@ +@use "@pythnetwork/component-library/theme"; + +.card { + display: grid; + grid-template-rows: auto 1fr; + gap: 0.75rem; // 12px + height: 100%; + padding: 1.25rem; // 20px + border: 1px solid var(--color-fd-border); + border-radius: 0.75rem; // 12px + background-color: var(--color-fd-card); + box-shadow: 0 1px 2px 0 rgb(0 0 0 / 5%); + text-decoration: none; + outline: none; + transition: border-color 200ms ease-out; + + &:hover { + border-color: var(--color-fd-accent); + } + + &:focus-visible { + box-shadow: 0 0 0 2px var(--color-fd-accent); + } + + @media (width >= 768px) { + padding: 1.5rem; // 24px + } +} + +.header { + display: flex; + align-items: center; + gap: 0.75rem; // 12px +} + +.iconContainer { + display: flex; + align-items: center; + justify-content: center; + width: 2rem; // 32px + height: 2rem; // 32px + border-radius: 0.375rem; // 6px + + &.blue { + background-color: var(--color-blue-100); + + :global(.dark) & { + background-color: var(--color-blue-900); + } + } + + &.green { + background-color: var(--color-green-100); + + :global(.dark) & { + background-color: var(--color-green-900); + } + } + + &.purple { + background-color: var(--color-purple-100); + + :global(.dark) & { + background-color: var(--color-purple-900); + } + } + + &.yellow { + background-color: var(--color-yellow-100); + + :global(.dark) & { + background-color: var(--color-yellow-900); + } + } +} + +.icon { + width: 1rem; // 16px + height: 1rem; // 16px + + &.blue { + color: var(--color-blue-600); + + :global(.dark) & { + color: var(--color-blue-400); + } + } + + &.green { + color: var(--color-green-600); + + :global(.dark) & { + color: var(--color-green-400); + } + } + + &.purple { + color: var(--color-purple-600); + + :global(.dark) & { + color: var(--color-purple-400); + } + } + + &.yellow { + color: var(--color-yellow-600); + + :global(.dark) & { + color: var(--color-yellow-400); + } + } +} + +.title { + margin: 0; + font-size: 1rem; // 16px + font-weight: 600; + color: var(--color-fd-foreground); + + &.blue { + :global(.group:hover) & { + color: var(--color-blue-600); + } + + :global(.dark .group:hover) & { + color: var(--color-blue-400); + } + } + + &.green { + :global(.group:hover) & { + color: var(--color-green-600); + } + + :global(.dark .group:hover) & { + color: var(--color-green-400); + } + } + + &.purple { + :global(.group:hover) & { + color: var(--color-purple-600); + } + + :global(.dark .group:hover) & { + color: var(--color-purple-400); + } + } + + &.yellow { + :global(.group:hover) & { + color: var(--color-yellow-600); + } + + :global(.dark .group:hover) & { + color: var(--color-yellow-400); + } + } +} + +.arrow { + margin-left: auto; + opacity: 0; + transform: translateX(0); + transition: + opacity 200ms ease-out, + transform 200ms ease-out; + + :global(.group:hover) & { + opacity: 1; + transform: translateX(0.25rem); // 4px + } +} + +.description { + margin: 0; + font-size: 0.875rem; // 14px + color: var(--color-fd-muted-foreground); + line-height: 1.5; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + overflow: hidden; +} diff --git a/apps/developer-hub/src/components/IntegrationCard/index.tsx b/apps/developer-hub/src/components/IntegrationCard/index.tsx index 76c7e7708a..da31b4e9a4 100644 --- a/apps/developer-hub/src/components/IntegrationCard/index.tsx +++ b/apps/developer-hub/src/components/IntegrationCard/index.tsx @@ -1,56 +1,55 @@ +import { clsx } from "clsx"; +import Link from "next/link"; + +import styles from "./index.module.scss"; + type IntegrationCardProps = { href: string; icon: React.ReactNode; title: string; description: string; - colorScheme?: "blue" | "green" | "purple"; -}; - -const colorClasses = { - blue: { - bg: "bg-blue-100 dark:bg-blue-900", - text: "text-blue-600 dark:text-blue-400", - hoverText: "group-hover:text-blue-600 dark:group-hover:text-blue-400", - }, - green: { - bg: "bg-green-100 dark:bg-green-900", - text: "text-green-600 dark:text-green-400", - hoverText: "group-hover:text-green-600 dark:group-hover:text-green-400", - }, - purple: { - bg: "bg-purple-100 dark:bg-purple-900", - text: "text-purple-600 dark:text-purple-400", - hoverText: "group-hover:text-purple-600 dark:group-hover:text-purple-400", - }, + colorScheme?: "blue" | "green" | "purple" | "yellow"; + external?: boolean; + showArrow?: boolean; }; -export const IntegrationCard = ({ +export function IntegrationCard({ href, icon, title, description, colorScheme = "blue", -}: IntegrationCardProps) => { - const colors = colorClasses[colorScheme]; + external, + showArrow = true, +}: IntegrationCardProps) { + const commonProps = { + href, + className: clsx(styles.card, "group"), + "aria-label": title, + }; - return ( - -
-
-
{icon}
+ const content = ( + <> +
+
+
{icon}
-

- {title} -

+

{title}

+ {showArrow && ( + + )}
-

{description}

+

{description}

+ + ); + + return external ? ( +
+ {content} + ) : ( + {content} ); -}; +} diff --git a/apps/developer-hub/src/components/Root/theme.css b/apps/developer-hub/src/components/Root/theme.css index a144044880..faeb5c96bf 100644 --- a/apps/developer-hub/src/components/Root/theme.css +++ b/apps/developer-hub/src/components/Root/theme.css @@ -334,3 +334,16 @@ ); --color-fd-ring: light-dark(var(--color-violet-600), var(--color-violet-400)); } + +/* Global typography: tighten line spacing in docs content */ +:where(.prose) { + line-height: 1.5; +} + +:where(.prose) :where(p, li) { + line-height: 1.5; +} + +:where(.prose) :where(h1, h2, h3, h4, h5, h6) { + line-height: 1.2; +} diff --git a/apps/developer-hub/src/mdx-components.tsx b/apps/developer-hub/src/mdx-components.tsx index c6a3bb3106..89e6db5256 100644 --- a/apps/developer-hub/src/mdx-components.tsx +++ b/apps/developer-hub/src/mdx-components.tsx @@ -3,6 +3,8 @@ import { Tab, Tabs } from "fumadocs-ui/components/tabs"; import defaultMdxComponents from "fumadocs-ui/mdx"; import type { MDXComponents } from "mdx/types"; +import { IntegrationCard } from "./components/IntegrationCard"; + export function getMDXComponents(components?: MDXComponents): MDXComponents { return { ...defaultMdxComponents, @@ -10,6 +12,7 @@ export function getMDXComponents(components?: MDXComponents): MDXComponents { Tab, ...components, InfoBox: InfoBox, + IntegrationCard, // Fuma has a Callout component in `defaultMdxComponents` which we still want to overwrite Callout: InfoBox, }; diff --git a/apps/developer-hub/src/source.ts b/apps/developer-hub/src/source.ts index ad9e33634a..fa7a3cd9ca 100644 --- a/apps/developer-hub/src/source.ts +++ b/apps/developer-hub/src/source.ts @@ -7,6 +7,18 @@ import { Shuffle, DiceSix, Sparkle, + RocketLaunch, + FileText, + MagnifyingGlass, + Shield, + Gauge, + Bug, + ShieldCheck, + ArrowsClockwise, + WarningCircle, + Code, + Book, + CurrencyDollar, } from "@phosphor-icons/react/dist/ssr"; import type { InferMetaType, InferPageType } from "fumadocs-core/source"; import { loader } from "fumadocs-core/source"; @@ -22,6 +34,18 @@ const icons: Record = { Shuffle, DiceSix, Sparkle, + RocketLaunch, + FileText, + MagnifyingGlass, + Shield, + Gauge, + Bug, + ShieldCheck, + ArrowsClockwise, + WarningCircle, + Code, + Book, + CurrencyDollar, }; export const source = loader({ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 934428f0f5..cf943ec2e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -213,6 +213,9 @@ catalogs: jest: specifier: ^29.7.0 version: 29.7.0 + katex: + specifier: ^0.16.22 + version: 0.16.22 lightweight-charts: specifier: ^5.0.5 version: 5.0.5 @@ -515,6 +518,12 @@ importers: '@pythnetwork/component-library': specifier: workspace:* version: link:../../packages/component-library + '@pythnetwork/contract-manager': + specifier: workspace:* + version: link:../../contract_manager + '@pythnetwork/entropy-sdk-solidity': + specifier: workspace:* + version: link:../../target_chains/ethereum/entropy_sdk/solidity '@react-hookz/web': specifier: 'catalog:' version: 25.1.0(react-dom@19.1.0(react@19.1.0))(react@19.1.0) @@ -530,6 +539,9 @@ importers: fumadocs-ui: specifier: 'catalog:' version: 15.3.0(@types/react-dom@19.1.1(@types/react@19.1.0))(@types/react@19.1.0)(next@15.5.0(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.86.1))(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(tailwindcss@4.1.6) + katex: + specifier: 'catalog:' + version: 0.16.22 next: specifier: 'catalog:' version: 15.5.0(@babel/core@7.27.1)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@19.1.0-rc.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(sass@1.86.1) @@ -548,6 +560,12 @@ importers: react-dom: specifier: 'catalog:' version: 19.1.0(react@19.1.0) + rehype-katex: + specifier: ^7.0.1 + version: 7.0.1 + remark-math: + specifier: ^6.0.0 + version: 6.0.0 viem: specifier: 'catalog:' version: 2.34.0(bufferutil@4.0.9)(typescript@5.8.2)(utf-8-validate@6.0.3)(zod@3.24.4) @@ -10977,6 +10995,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/katex@0.16.7': + resolution: {integrity: sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==} + '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} @@ -15680,6 +15701,24 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} + hast-util-from-dom@5.0.1: + resolution: {integrity: sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==} + + hast-util-from-html-isomorphic@2.0.0: + resolution: {integrity: sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + hast-util-to-estree@3.1.3: resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} @@ -15692,9 +15731,15 @@ packages: hast-util-to-string@3.0.1: resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} hasBin: true @@ -16754,6 +16799,10 @@ packages: resolution: {integrity: sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==} engines: {node: '>=18'} + katex@0.16.22: + resolution: {integrity: sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==} + hasBin: true + keccak256@1.0.6: resolution: {integrity: sha512-8GLiM01PkdJVGUhR1e6M/AvWnSqYS0HaERI+K/QtStGDGlSTx2B1zTqZk4Zlqu5TxHJNTxWAdP9Y+WI50OApUw==} @@ -17190,6 +17239,9 @@ packages: mdast-util-gfm@3.1.0: resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + mdast-util-math@3.0.0: + resolution: {integrity: sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==} + mdast-util-mdx-expression@2.0.1: resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} @@ -17369,6 +17421,9 @@ packages: micromark-extension-gfm@3.0.0: resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + micromark-extension-math@3.1.0: + resolution: {integrity: sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==} + micromark-extension-mdx-expression@3.0.1: resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} @@ -19293,6 +19348,9 @@ packages: react: optional: true + rehype-katex@7.0.1: + resolution: {integrity: sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==} + rehype-recma@1.0.0: resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} @@ -19303,6 +19361,9 @@ packages: remark-gfm@4.0.1: resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + remark-math@6.0.0: + resolution: {integrity: sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==} + remark-mdx@3.1.0: resolution: {integrity: sha512-Ngl/H3YXyBV9RcRNdlYsZujAmhsxwzxpDzpDEhFBVAGthS4GDgnctpDjgFl/ULx5UEDzqtW1cyBSNKqYYrqLBA==} @@ -21044,6 +21105,9 @@ packages: unified@11.0.5: resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + unist-util-is@6.0.0: resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} @@ -21053,6 +21117,9 @@ packages: unist-util-position@5.0.0: resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} @@ -21335,6 +21402,9 @@ packages: resolution: {integrity: sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==} engines: {'0': node >=0.6.0} + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + vfile-message@4.0.2: resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==} @@ -21411,6 +21481,9 @@ packages: wcwidth@1.0.1: resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + web-vitals@0.2.4: resolution: {integrity: sha512-6BjspCO9VriYy12z356nL6JBS0GYeEcA457YyRzD+dD6XYCQ75NKhcOHUMHentOE7OcVCIXXDvOm0jKFfQG2Gg==} @@ -36087,6 +36160,8 @@ snapshots: '@types/json5@0.0.29': {} + '@types/katex@0.16.7': {} + '@types/keyv@3.1.4': dependencies: '@types/node': 18.19.86 @@ -43244,6 +43319,47 @@ snapshots: dependencies: function-bind: 1.1.2 + hast-util-from-dom@5.0.1: + dependencies: + '@types/hast': 3.0.4 + hastscript: 9.0.1 + web-namespaces: 2.0.1 + + hast-util-from-html-isomorphic@2.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-dom: 5.0.1 + hast-util-from-html: 2.0.3 + unist-util-remove-position: 5.0.0 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.2.1 + vfile: 6.0.3 + vfile-message: 4.0.2 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.0.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-estree@3.1.3: dependencies: '@types/estree': 1.0.7 @@ -43303,10 +43419,25 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.4 + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.0.0 + space-separated-tokens: 2.0.2 + he@1.2.0: {} header-case@2.0.4: @@ -45194,6 +45325,10 @@ snapshots: jwt-decode@4.0.0: {} + katex@0.16.22: + dependencies: + commander: 8.3.0 + keccak256@1.0.6: dependencies: bn.js: 5.2.1 @@ -45652,6 +45787,18 @@ snapshots: transitivePeerDependencies: - supports-color + mdast-util-math@3.0.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + longest-streak: 3.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + unist-util-remove-position: 5.0.0 + transitivePeerDependencies: + - supports-color + mdast-util-mdx-expression@2.0.1: dependencies: '@types/estree-jsx': 1.0.5 @@ -46051,6 +46198,16 @@ snapshots: micromark-util-combine-extensions: 2.0.1 micromark-util-types: 2.0.2 + micromark-extension-math@3.1.0: + dependencies: + '@types/katex': 0.16.7 + devlop: 1.1.0 + katex: 0.16.22 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + micromark-extension-mdx-expression@3.0.1: dependencies: '@types/estree': 1.0.7 @@ -48561,6 +48718,16 @@ snapshots: '@types/react': 19.1.0 react: 19.1.0 + rehype-katex@7.0.1: + dependencies: + '@types/hast': 3.0.4 + '@types/katex': 0.16.7 + hast-util-from-html-isomorphic: 2.0.0 + hast-util-to-text: 4.0.2 + katex: 0.16.22 + unist-util-visit-parents: 6.0.1 + vfile: 6.0.3 + rehype-recma@1.0.0: dependencies: '@types/estree': 1.0.7 @@ -48582,6 +48749,15 @@ snapshots: transitivePeerDependencies: - supports-color + remark-math@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-math: 3.0.0 + micromark-extension-math: 3.1.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + remark-mdx@3.1.0: dependencies: mdast-util-mdx: 3.0.0 @@ -51006,6 +51182,11 @@ snapshots: trough: 2.2.0 vfile: 6.0.3 + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.0 + unist-util-is@6.0.0: dependencies: '@types/unist': 3.0.3 @@ -51018,6 +51199,11 @@ snapshots: dependencies: '@types/unist': 3.0.3 + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.0.0 + unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.3 @@ -51283,6 +51469,11 @@ snapshots: core-util-is: 1.0.2 extsprintf: 1.3.0 + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + vfile-message@4.0.2: dependencies: '@types/unist': 3.0.3 @@ -51464,6 +51655,8 @@ snapshots: dependencies: defaults: 1.0.4 + web-namespaces@2.0.1: {} + web-vitals@0.2.4: {} web3-bzz@1.10.0(bufferutil@4.0.9)(utf-8-validate@5.0.10): diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 067db2a5e9..65cd66a04c 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -63,6 +63,7 @@ catalog: "@floating-ui/react": ^0.27.6 "@headlessui/react": ^2.2.0 "@heroicons/react": ^2.2.0 + "@katex/katex": ^0.16.9 "@next/third-parties": ^15.3.2 "@phosphor-icons/react": ^2.1.7 "@pythnetwork/client": ^2.22.1 @@ -116,6 +117,7 @@ catalog: highlight.js: ^11.11.1 ip-range-check: ^0.2.0 jest: ^29.7.0 + katex: ^0.16.22 lightweight-charts: ^5.0.5 lucide-react: ^0.487.0 match-sorter: ^8.1.0