create-your-first-entropy-app.mdx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  1. ---
  2. title: Create your first Entropy app on EVM
  3. description: Build a coin flip example using Pyth Entropy
  4. icon: RocketLaunch
  5. ---
  6. import { DynamicCodeBlock } from "fumadocs-ui/components/dynamic-codeblock";
  7. In this tutorial we will implement and deploy a coin flip contract which will use entropy to generate a random output.
  8. ## Preliminaries
  9. Before we start, please make sure you have the following tools installed:
  10. - [Foundry](https://book.getfoundry.sh/getting-started/installation).
  11. - [Node](https://nodejs.org/en/download). Run `node -v{:js}` to confirm. You should get an output with version >= `v18.0.0`.
  12. ## Getting Started
  13. Create a directory named `coin-flip{:bash}` in your filesystem.
  14. We will use this directory as the working directory for the rest of the tutorial.
  15. Let's initialize a new project in `coin-flip{:bash}` by running `forge init contracts{:bash}`.
  16. This will create a new directory in `coin-flip{:bash}` named `contracts/src`, which will contain the smart contract code.
  17. <DynamicCodeBlock
  18. lang="bash"
  19. code={`\
  20. mkdir coin-flip
  21. cd coin-flip
  22. forge init contracts
  23. `}
  24. />
  25. Now we will install the Pyth Entropy SDK in the `contracts` directory.
  26. <DynamicCodeBlock
  27. lang="bash"
  28. code={`\
  29. cd contracts
  30. npm init -y
  31. npm install @pythnetwork/entropy-sdk-solidity
  32. `}
  33. />
  34. Add a `remappings.txt` file to `contracts` directory with the following content to tell Foundry where to find the Pyth Entropy SDK.
  35. <DynamicCodeBlock
  36. lang="text"
  37. code={`\
  38. @pythnetwork/entropy-sdk-solidity/=node_modules/@pythnetwork/entropy-sdk-solidity
  39. `}
  40. />
  41. ## Implementation
  42. Create a new file `CoinFlip.sol{:solidity}` in `contracts/src` directory and add the following code into it to start.
  43. <DynamicCodeBlock lang="solidity" code={`\
  44. // contracts/src/CoinFlip.sol
  45. // SPDX-License-Identifier: UNLICENSED
  46. pragma solidity ^0.8.13;
  47. import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol";
  48. import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
  49. contract CoinFlip is IEntropyConsumer {
  50. event FlipRequested(uint64 sequenceNumber);
  51. event FlipResult(uint64 sequenceNumber, bool isHeads);
  52. IEntropyV2 entropy;
  53. constructor(address \_entropy) {
  54. entropy = IEntropyV2(\_entropy);
  55. }
  56. // This method is required by the IEntropyConsumer interface
  57. function getEntropy() internal view override returns (address) {
  58. return address(entropy);
  59. }
  60. }
  61. `} />
  62. The code implements a`CoinFlip` contract which inherits the `IEntropyConsumer` interface.
  63. We have also defined some events, properties and a constructor to instantiate the contract.
  64. One of the properties is of type `IEntropyV2` which is an interface imported from the Entropy SDK.
  65. ### Request a coin flip
  66. Copy the following code into `CoinFlip.sol{:solidity}`.
  67. <DynamicCodeBlock lang="solidity" code={`\
  68. contract CoinFlip {
  69. // ... prior code omitted
  70. function request() external payable {
  71. // get the required fee
  72. uint128 requestFee = entropy.getFeeV2();
  73. // check if the user has sent enough fees
  74. if (msg.value < requestFee) revert("not enough fees");
  75. // pay the fees and request a random number from entropy
  76. uint64 sequenceNumber = entropy.requestV2{ value: requestFee }();
  77. // emit event
  78. emit FlipRequested(sequenceNumber);
  79. }
  80. }
  81. `} />
  82. Users will invoke the `request` method to initiate a coin flip, paying a fee in the process.
  83. The method first retrieves the fee required to request a random number from Entropy.
  84. It then includes the fee in the `requestV2{:bash}` method call to Entropy.
  85. Finally, the method emits a `FlipRequested{:bash}` event with a `sequenceNumber`. This event is also defined in the code snippet above.
  86. ### Handle the callback
  87. Copy the following code into `CoinFlip.sol{:solidity}`.
  88. <DynamicCodeBlock lang="solidity" code={`\
  89. contract CoinFlip {
  90. // ... prior code omitted
  91. function entropyCallback(
  92. uint64 sequenceNumber,
  93. // If your app uses multiple providers, you can use this argument
  94. // to distinguish which one is calling the app back. This app only
  95. // uses one provider so this argument is not used.
  96. address \_providerAddress,
  97. bytes32 randomNumber
  98. ) internal override {
  99. bool isHeads = uint256(randomNumber) % 2 == 0;
  100. emit FlipResult(sequenceNumber, isHeads);
  101. }
  102. }
  103. `} />
  104. Implement `entropyCallback` method which is required by the `IEntropyConsumer` Interface. Entropy calls back this method to fulfill a request. Entropy will call back this
  105. method with the `sequenceNumber` of the request, the `_providerAddress` from which the random number was requested and the generated `randomNumber`.
  106. Finally, the method emits a `FlipResult` event with the result of the flip.
  107. Yay! you have successfully implemented a coin flip contract.
  108. ## Deploy
  109. First, create a new wallet
  110. <DynamicCodeBlock
  111. lang="bash"
  112. code={`\
  113. cast wallet new
  114. `}
  115. />
  116. 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.
  117. <DynamicCodeBlock
  118. lang="bash"
  119. code={`\
  120. Successfully created new keypair.
  121. Address: 0xB806824fdA4b2b6631e9B87a86d42C9dfd04D129
  122. Private key: 0x0d510c72fd2279155c717eb433ae598a83cfb34b09c2ada86bc424b481082023
  123. `}
  124. />
  125. 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:
  126. <DynamicCodeBlock
  127. lang="bash"
  128. code={`\
  129. export ADDRESS=<address from above>
  130. export PRIVATE_KEY=<your private key from above>
  131. export RPC_URL="https://sepolia.optimism.io"
  132. `}
  133. />
  134. 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
  135. <DynamicCodeBlock
  136. lang="bash"
  137. code={`\
  138. cast balance $ADDRESS -r $RPC_URL -e
  139. `}
  140. />
  141. The final step before deploying is to get the arguments for the contract's constructor: the [Entropy contract address](https://docs.pyth.network/entropy/contract-addresses) for Optimism Sepolia and [the Provider address](https://docs.pyth.network/entropy/contract-addresses). We will also export these values as environment variables for convenience:
  142. ```bash copy
  143. export ENTROPY_ADDRESS=0x4821932D0CDd71225A6d914706A621e0389D7061
  144. ```
  145. Finally, let's deploy the contracts. Run the following command:
  146. <DynamicCodeBlock
  147. lang="bash"
  148. code={`\
  149. forge create src/CoinFlip.sol:CoinFlip \
  150. --private-key $PRIVATE_KEY \
  151. --rpc-url $RPC_URL \
  152. --constructor-args $ENTROPY_ADDRESS
  153. `}
  154. />
  155. You should see an output similar to:
  156. <DynamicCodeBlock
  157. lang="bash"
  158. code={`\
  159. [⠢] Compiling...
  160. [⠔] Compiling 28 files with 0.8.23
  161. [⠑] Solc 0.8.23 finished in 3.40s
  162. Compiler run successful!
  163. Deployer: 0xfa57d0f2CBDA2729273F2a431E4FeDAc656d0402
  164. Deployed to: 0x8676ba0Dd492AB9813BC21D5Dce318427d1d73ae
  165. Transaction hash: 0x2178aa6d402c94166a93e81822248d00dd003827675ebd49b3c542970f5a0189
  166. `}
  167. />
  168. Let's export the coin flip contract address as environment variable for later use:
  169. <DynamicCodeBlock
  170. lang="bash"
  171. code={`\
  172. export COINFLIP_ADDRESS=<Deployed to address from above>
  173. `}
  174. />
  175. Congratulations you have successfully implemented and deployed a CoinFlip contract.
  176. ## Interact from Javascript
  177. 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.
  178. Run the following to initialise a new project and install required libraries.
  179. <DynamicCodeBlock
  180. lang="bash"
  181. code={`\
  182. npm init -y
  183. npm install web3 @pythnetwork/entropy-sdk-solidity
  184. `}
  185. />
  186. Create a `script.js` file in `app` and add the following code to the script.
  187. <DynamicCodeBlock lang="javascript" code={`\
  188. const { Web3 } = require("web3");
  189. const CoinFlipAbi = require("../contracts/out/CoinFlip.sol/CoinFlip.json");
  190. const EntropyAbi = require("@pythnetwork/entropy-sdk-solidity/abis/IEntropyV2.json");
  191. async function main() {
  192. const web3 = new Web3(process.env["RPC_URL"]);
  193. const { address } = web3.eth.accounts.wallet.add(
  194. process.env["PRIVATE_KEY"]
  195. )[0];
  196. web3.eth.defaultBlock = "finalized";
  197. const coinFlipContract = new web3.eth.Contract(
  198. CoinFlipAbi.abi,
  199. process.env["COINFLIP_ADDRESS"]
  200. );
  201. const entropyContract = new web3.eth.Contract(
  202. EntropyAbi,
  203. process.env["ENTROPY_ADDRESS"]
  204. );
  205. }
  206. main();
  207. `} />
  208. 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.
  209. Next, add the following code to the main method to request a flip from the CoinFlip contract.
  210. <DynamicCodeBlock lang="javascript" code={`\
  211. async main() {
  212. // ... prior code omitted
  213. // Request a random number
  214. const fee = await entropyContract.methods.getFeeV2().call()
  215. console.log(\`fee: \${fee}\`);
  216. const requestReceipt = await coinFlipContract.methods
  217. .request()
  218. .send({
  219. value: fee,
  220. from: address,
  221. });
  222. console.log(\`request tx: \${requestReceipt.transactionHash}\`);
  223. // Read the sequence number for the request from the transaction events.
  224. const sequenceNumber =
  225. requestReceipt.events.FlipRequested.returnValues.sequenceNumber;
  226. console.log(\`sequence: \${sequenceNumber}\`);
  227. }
  228. `} />
  229. 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.
  230. Finally, add the following code snippet to get the flip result.
  231. <DynamicCodeBlock lang="javascript" code={`\
  232. async main() {
  233. // ... prior code omitted
  234. let fromBlock = requestReceipt.blockNumber;
  235. const intervalId = setInterval(async () => {
  236. const currentBlock = await web3.eth.getBlockNumber();
  237. if(fromBlock > currentBlock) {
  238. return;
  239. }
  240. // Get 'FlipResult' events emitted by the CoinFlip contract for given block range.
  241. const events = await coinFlipContract.getPastEvents("FlipResult", {
  242. fromBlock: fromBlock,
  243. toBlock: currentBlock,
  244. });
  245. fromBlock = currentBlock + 1n;
  246. // Find the event with the same sequence number as the request.
  247. const event = events.find(event => event.returnValues.sequenceNumber === sequenceNumber);
  248. // If the event is found, log the result and stop polling.
  249. if(event !== undefined) {
  250. console.log(\`result: \${event.returnValues.isHeads ? 'Heads' : 'Tails'}\`);
  251. clearInterval(intervalId);
  252. }
  253. }, 1000);
  254. }
  255. `} />
  256. 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.
  257. That's it, Let's run the script with the command `node script.js` . You should get an output similar to:
  258. <DynamicCodeBlock
  259. lang="bash"
  260. code={`\
  261. fee : 101
  262. request tx : 0xde0dce36a3c149b189aba8b29cad98375a62a811e65efdae28b28524da59cfb6
  263. sequence : 42
  264. result : Tails
  265. `}
  266. />
  267. Note that: the script can fail due to transient RPC issues. You can run the script again to get the expected result.
  268. ## Next Steps
  269. 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.
  270. You can learn more about Entropy from the following links:
  271. - [Protocol Design](https://docs.pyth.network/entropy/protocol-design)
  272. - [Best Practices](https://docs.pyth.network/entropy/best-practices)