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:
-
Node (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.
We will use this directory as the working directory for the rest of the tutorial.
Let's initialize a new project in coin-flip
by running forge init contracts
.
This will create a new directory in coin-flip
named contracts/src
, which will contain the smart contract code.
mkdir coin-flip
cd coin-flip
forge init contracts
Now we will install the Pyth Entropy SDK in the contracts
directory.
cd contracts
npm init -y
npm install @pythnetwork/entropy-sdk-solidity
Add a remappings.txt
file to contracts
directory with the following content to tell Foundry where to find the Pyth Entropy SDK.
@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.
// contracts/src/CoinFlip.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
import "@pythnetwork/entropy-sdk-solidity/IEntropyV2.sol";
import "@pythnetwork/entropy-sdk-solidity/IEntropyConsumer.sol";
contract CoinFlip is IEntropyConsumer {
event FlipRequested(uint64 sequenceNumber);
event FlipResult(uint64 sequenceNumber, 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);
}
}
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 IEntropyV2
which is an interface imported from the Entropy SDK.
Request a coin flip
Copy the following code into 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
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
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
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/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.
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.
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 transient 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: