Entropy
Create your first Entropy app on EVM

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 (opens in a new tab).

  • Run forge --version to confirm it is installed. You should get an output similar to forge 0.2.0 (23aa303 2023-11-28T00:35:15.730515000Z)

Node (version > 18) - https://nodejs.org/en/download (opens in a new tab)

  • Run node -v to confirm. You should get an output with version >= v18.0.0.

Getting Started

Create a directory named coin-flip in your filesystem. Open a terminal with coin-flip as the working directory and run forge init contracts to create a new Solidity project. You will see a new directory in coin-flip named contracts. contracts/src is where all your contract code will be.

Run cd contracts to make it your terminal’s working directory — the following commands will need to be run from here.

Next, install the Pyth Entropy SDK by running the following commands.

npm init -y
npm install @pythnetwork/entropy-sdk-solidity

Add a remappings.txt file to contracts directory with the following content.

@pythnetwork/entropy-sdk-solidity/=node_modules/@pythnetwork/entropy-sdk-solidity

Implementation

Create a new file CoinFlip.sol in contracts/src directory and add the following code into it to start

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "@pythnetwork/entropy-sdk-solidity/IEntropy.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
 
contract CoinFlip is IEntropyConsumer {
  event FlipRequested(uint64 sequenceNumber);
  event FlipResult(uint64 sequenceNumber, bool isHeads);
 
  IEntropy entropy;
  address provider;
 
  constructor(address _entropy, address _provider) {
    entropy = IEntropy(_entropy);
    provider = _provider;
  }
 
  // This method is required by the IEntropyConsumer interface
  function getEntropy() internal view override returns (address) {
    return address(entropy);
  }
}
 

The code implements aCoinFlip 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 IEntropy which is an interface imported from the Entropy SDK. The constructor also takes a provider argument. We will see how to populate these later.

Request a coin flip

Copy the following code into CoinFlip.sol.

contract CoinFlip {
  // ... prior code omitted
 
  function request(bytes32 userRandomNumber) external payable {
    // get the required fee
    uint128 requestFee = entropy.getFee(provider);
    // 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.requestWithCallback{ value: requestFee }(
      provider,
      userRandomNumber
    );
 
    // emit event
    emit FlipRequested(sequenceNumber);
  }
}
 

Users will invoke the request method to initiate a coin flip with a request fee and passes in a userRandomNumber argument — we’ll see how to generate this later. The method first retrieves the fee required to request a random number from Entropy. It then include the fee in the requestWithCallback method call to entropy. Finally, the method emits a FlipRequested event with a sequenceNumber. This event is also defined in the code snippet above.

Handle the callback

Copy the following code into CoinFlip.sol.

contract CoinFlip {
  // ... prior code omitted
 
  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

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.

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:

export ADDRESS=<address from above>
export PRIVATE_KEY=<your private key from above>
export RPC_URL="https://sepolia.optimism.io"

Next, use the Superchain Faucet (opens in a new tab) 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

cast balance $ADDRESS -r $RPC_URL -e

The final step before deploying is to get the arguments for the contract's constructor: the Entropy contract address (opens in a new tab) for Optimism Sepolia and the Provider address (opens in a new tab). We will also export these values as environment variables for convenience:

export ENTROPY_ADDRESS=0x4821932D0CDd71225A6d914706A621e0389D7061
export PROVIDER_ADDRESS=0x6CC14824Ea2918f5De5C2f75A9Da968ad4BD6344

Finally, let's deploy the contracts. Run the following command:

forge create src/CoinFlip.sol:CoinFlip \
--private-key $PRIVATE_KEY \
--rpc-url $RPC_URL \
--constructor-args $ENTROPY_ADDRESS $PROVIDER_ADDRESS

You should see an output similar to:

[⠢] 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:

export COINFLIP_ADDRESS=<Deployed to address from above>

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.

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.

const { Web3 } = require("web3");
const CoinFlipAbi = require("../contracts/out/CoinFlip.sol/CoinFlip.json");
const EntropyAbi = require("@pythnetwork/entropy-sdk-solidity/abis/IEntropy.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.

async main() {
  // ... prior code omitted
 
  // Request a random number
 
  // Generate user random number
  const userRandomNumber = web3.utils.randomHex(32);
 
  const fee = await entropyContract.methods.getFee(process.env["PROVIDER_ADDRESS"]).call()
  console.log(`fee         : ${fee}`);
 
  const requestReceipt = await coinFlipContract.methods
    .request(userRandomNumber)
    .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.

async main() {
  // ... prior code omitted
 
  let fromBlock = requestReceipt.blockNumber;
  const intervalId = setInterval(async () => {
    const currentBlock = await web3.eth.getBlockNumber();
 
    if(fromBlock > currentBlock) {
      return;
    }
 
    // 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);
}

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:

fee         : 101
request tx  : 0xde0dce36a3c149b189aba8b29cad98375a62a811e65efdae28b28524da59cfb6
sequence    : 42
result      : Tails

Note that: the script can fail due to some rpc issues. You can run the script again to get the expected result.

Next Steps

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:

Last updated on