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 toforge 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: