Skip to main content

LooksRare YOLO Smart Contract

TL;DR: The YOLO system is designed to operate a transparent, fair, and decentralized game directly on the Ethereum blockchain. It utilizes smart contracts to handle the process, ensuring trust and integrity. The YOLO system is structured around a series of 'rounds'. Each round represents a single iteration of the game, where participants can enter by making deposits (ETH/ERC-721/ERC-20), and winners are selected at the end.

The likelihood of a participant winning is equivalent to the share of the total pot they deposited. Each deposit value is calculated, and each participant is allocated a number of entry spots proportional to the value deposited. The YOLO contract relies on a few external oracles: Chainlink for fair randomness, Reservoir for ERC-721 pricing data and Uniswap for ERC-20 pricing data.

NOTE: All NFTs in a collection will be valued at the floor price of the collection; rarity is not accounted for.


The contract includes various functions for interacting with the YOLO system, such as depositing into the current round (deposit), drawing the winners (drawWinner), claiming the prizes (claimPrizes), cancelling a round if less than two addresses deposited (cancel), withdrawing the deposit if cancelled (withdrawDeposits) and cancelling a round (if less than two addresses entered) and depositing to a new one (cancelCurrentRoundAndDepositToTheNextRound).


Events are emitted for various actions, such as depositing, withdrawing a deposit, updating the round status, claiming prizes, sending a randomness request etc.

Subgraph Our YOLO Subgraph indexes the events emitted by this contract.

More details about this subgraph can also be found in the YOLO Subgraph documentation.

Deployed contracts addresses


Table of content

YOLO System Overview

The YOLO system is designed to run rounds continuously based on the roundDuration value defined. Users can enter by depositing one or more types of assets (ETH/ERC-721/ERC-20) and receive entries in exchange, the round can successfully end by either reaching the maximum number of participants or deposits allowed per round (maximumNumberOfParticipants/maximumNumberOfDeposits), and there are at least two participants. Once that threshold is reached, a randomness request is sent to Chainlink and the value returned is used to randomly select the round winner. After the winner is selected, a new round is started.

Here is a brief description of how it works:

The contract is initialised with the rounds configurations: These values define things like the duration of each round (roundDuration), the limits on participants per round and deposits (maximumNumberOfDepositsPerRound/maximumNumberOfParticipantsPerRound) and the various oracles addresses and details. The contract owner can adjust these values.

  1. The round is started: The status is set to Open. At this point, people can deposit assets and currencies and receive entries equivalent to the total value of the assets deposited. The Reservoir and Uniswap oracles are used to calculate the assets' value. The price is fetched only with the first deposit, and it will stay the same for the whole round.

  2. The round is successfully completed: The status is set to Drawing. The randomness request is sent to Chainlink; this can take a few minutes to complete based on the network status.

    • The randomness request is completed: The status is set to Drawn. The value returned by Chainlink is used to select the winner of the last round.
  3. The round does not reach two participants: The status is set to Cancelled. The deposits can be withdrawn by calling withdrawDeposits or rolled over to the next round when cancelling a round the current by calling cancelCurrentRoundAndDepositToTheNextRound.

After a round is completed or cancelled, a new one is started.

YOLO configuration

When the contract is first deployed, there are a set of parameters that are required within the constructor and govern the logic of each round. Most of these values can be updated by the contract owner.

Here is the ConstructorCalldata struct:

struct ConstructorCalldata {
// The following values can be updated after the contract has been deployed
address owner;
address operator;
uint40 maximumNumberOfDepositsPerRound;
uint40 maximumNumberOfParticipantsPerRound;
uint40 roundDuration;
uint256 valuePerEntry;
address protocolFeeRecipient;
uint16 protocolFeeBp;
address reservoirOracle;
address erc20Oracle;
uint40 signatureValidityPeriod;

// The following values cannot be updated after the contract has been deployed
bytes32 keyHash;
uint64 subscriptionId;
address vrfCoordinator;
address weth;
address transferManager;


  • owner: The contract’s owner can update all the values outlined above except the currencies allowlist.
  • operator: The contract’s operator can update the allowed currencies.
  • maximumNumberOfDepositsPerRound: The maximum number of deposits per round.
  • maximumNumberOfParticipantsPerRound: The maximum number of participants per round.
  • roundDuration: The duration of each round in seconds. It must be at most 1 hour.
  • valuePerEntry: The value in ETH of one entry.
  • protocolFeeRecipient: The address which will receive the protocol fee.
  • protocolFeeBp: The protocol fee in bps, taken from the total value of a round. At most 2500 (25%).
  • reservoirOracle: The address of Reservoir’s price oracle. erc20Oracle: The address of Uniswap’s price oracle.
  • signatureValidityPeriod: The time in seconds that a given Reservoir signature will be valid for.
  • keyHash/subscriptionId/vrfCoordinator: Chainlinks configuration variables, see Random Numbers: Using Chainlink VRF for more details.
  • weth: The WETH address. Used for the protocol fee transfer function in case the ETH transfer fails.
  • transferManager: The Transfer Manager contract’s address. Used to transfer the assets. For more details, see the transfer manager contract interface. Approvals must be granted to this contract by the participants.

As soon as the contract is deployed, a round will start.

Events emitted

  • ERC20OracleUpdated(address erc20Oracle)
  • MaximumNumberOfDepositsPerRoundUpdated(uint40 maximumNumberOfDepositsPerRound)
  • MaximumNumberOfParticipantsPerRoundUpdated(uint40 maximumNumberOfParticipantsPerRound)
  • ProtocolFeeBpUpdated(uint16 protocolFeeBp)
  • ProtocolFeeRecipientUpdated(address protocolFeeRecipient)
  • ReservoirOracleUpdated(address reservoirOracle)
  • RoundDurationUpdated(uint40 roundDuration)
  • SignatureValidityPeriodUpdated(uint40 signatureValidityPeriod)
  • ValuePerEntryUpdated(uint256 valuePerEntry)
  • RoundStatusUpdated(roundId, RoundStatus.Open)

Deposit assets

Once a round has started, users can deposit one or more assets/currencies (ETH/ERC-20/ERC-721) by calling the function deposit(uint256 roundId, DepositCalldata[] calldata deposits). This function accepts a roundId and an array of DepositCalldata. Not needed for ETH deposits.

The DepositCalldata struct includes the following properties:

  • tokenType: Either 1 for ERC-20 or 2 for ERC-721 (ETH does not need a DepositCalldata to be provided, the value sent alongside the transaction will be automatically deposited as ETH).
  • tokenAddress: The asset’s address.
  • tokenIdsOrAmounts: The asset’s ids for ERC-721 or the amount for ERC-20.
    • If the type is ERC-20 the tokenIdsOrAmounts length must be 1.
  • reservoirOracleFloorPrice: An object of type ReservoirOracleFloorPrice that can be retrieved via the Reservoir API (see the section below for more details) with the following properties:
    • id: The response’s id.
    • payload: The response’s payload.
    • timestamp: The response’s timestamp.
    • signature: The response’s signature.

How to retrieve the floor price data via the Reservoir API

The signed collection’s floor price data can be retrieved via the Reservoir API.

  • Use the Collection floor endpoint.
  • Set the kind to TWAP.
  • Set the twapSeconds to 86400.
  • Set the useNonFlaggedFloorAsk to false.

DepositCalldata struct:

struct ReservoirOracleFloorPrice {
bytes32 id;
bytes payload;
uint256 timestamp;
bytes signature;

ReservoirOracleFloorPrice struct:

struct DepositCalldata {
TokenType tokenType;
address tokenAddress;
uint256[] tokenIdsOrAmounts;
ReservoirOracleFloorPrice reservoirOracleFloorPrice;

NOTE: All NFTs in a collection will be valued at the floor price of the collection; rarity is not accounted for.

Events emitted

  • Deposited(msg.sender, roundId, totalEntriesCount)
  • RandomnessRequested(roundId, requested) if the deposit reached the limit per round
  • RoundStatusUpdated(roundId, RoundStatus.Drawing) if the deposit reached the limit per round

Draw winner

Anyone can trigger for the winner to be drawn if the round’s cutoffTime has been reached but not the maximum participants/deposits threshold, as long as there are at least 2 participants. This can be achieved by calling the function drawWinner().

Events emitted

  • RandomnessRequested(roundId, requested)
  • RoundStatusUpdated(roundId, RoundStatus.Drawing)

Claim prizes

Once a round has been completed successfully, the winner can claim the prizes by calling the function claimPrizes(ClaimPrizesCalldata[] calldata claimPrizesCalldata); it can be used to claim multiple rounds at once. The function accepts an array of ClaimPrizesCalldata.

The ClaimPrizesCalldata struct includes the following properties:

  • roundId: The id of the round won.
  • prizeIndices: An array of indices of prizes to claim.

ClaimPrizesCalldata struct:

struct ClaimPrizesCalldata {
uint256 roundId;
uint256[] prizeIndices;

NOTE: When calling the claim function, it might be required to send some ETH to cover the protocol fee, if the pot won does not have enough ETH prizes for that.

The function getClaimPrizesPaymentRequired(ClaimPrizesCalldata[] calldata claimPrizesCalldata) accepts the same input as the claimPrizes function, and it returns the amount of ETH to send along when claiming the prizes (this function does not validate claimPrizesCalldata for duplicated entries, it’s the caller responsibility to ensure correct usage to avoid incorrect values).

Events emitted

  • PrizesClaimed(perRoundClaimPrizesCalldata.roundId, msg.sender, prizeIndices)

Cancel a round

Anyone can cancel a round if one of the following two conditions are met:

  • The round’s cutoffTime has been reached, and there are less than 2 participants.
  • The randomness request to Chainlink has not been fulfilled within 1 day.

There are 3 different cancel functions:

  • cancel()
  • cancelCurrentRoundAndDepositToTheNextRound(DepositCalldata[] calldata deposits)
  • cancelAfterRandomnessRequest()

The cancelCurrentRoundAndDepositToTheNextRound function requires a DepositCalldata array.

For more details on DepositCalldata, see the deposit assets section.

Events emitted

  • RoundStatusUpdated(roundId, RoundStatus.Cancelled) current round cancelled
  • RoundStatusUpdated(roundId, RoundStatus.Open) new round started
  • Deposited(msg.sender, roundId, totalEntriesCount) if the deposit has been rolled over.

Withdraw deposits

After a round has been cancelled, the participants can withdraw their deposits by calling withdrawDeposits(uint256 roundId, uint256[] calldata depositIndices).

The function accepts a roundId and an array of deposit indices.

Events emitted

  • DepositsWithdrawn(roundId, msg.sender, depositIndices)

Variables update

The contract has a group of functions which are used to update some of the variables initially set at deployment. See YOLO configuration for more details.

Update Functions

Function NameFunction InputAccess control
updateRoundDurationuint40 _roundDurationowner
updateSignatureValidityPerioduint40 _signatureValidityPeriodowner
updateValuePerEntryuint256 _valuePerEntryowner
updateProtocolFeeRecipientaddress _protocolFeeRecipientowner
updateProtocolFeeBpuint16 _protocolFeeBpowner
updateMaximumNumberOfDepositsPerRounduint40 _maximumNumberOfDepositsPerRoundowner
updateMaximumNumberOfParticipantsPerRounduint40 _maximumNumberOfParticipantsPerRoundowner
updateReservoirOracleaddress _reservoirOracleowner
updateERC20Oracleaddress _erc20Oracleowner
updateCurrenciesStatusaddress[] currencies, bool isAllowedoperator