Skip to content

Zora Timed Sale Strategy

The Zora Sale Timed Sale Strategy introduces a new mint fee and enables a secondary market that is powered by Uniswap V3. New tokens minted will have a mint fee of 0.000111 ETH (✧111).

Upon calling setSale() a sale will be created for the Zora 1155 NFT along with creating an ERC20z token and a Uniswap V3 Pool.

After the sale has ended launchMarket() will be called to start the secondary market. This will deploy liquidity into the Uniswap V3 pool and enable the buying and selling of ERC20s on the secondary market as a result.

Fee breakdown

RecipientAmount
Creator0.0000555 eth (✧55)
Create Referral0.0000111 eth (✧11)
Mint Referral0.0000222 eth (✧22)
Zora0.0000111 eth (✧11)
Market0.0000111 eth (✧11)

The market reward fee of ✧11 per mint is used to bootstrap liquidity for the Uniswap V3 pool. As soon as the secondary market is launched the mint fees earned by the market will be deposited into the pool thus ensuring that there is liquidity as soon as the pool starts.

Contract Overview

The ZoraTimedSaleStrategy contract is used to create a timed sale for a Zora 1155 token. When it is configured for an 1155 contract and token via the setSale function, specifying the saleStart and saleEnd of the sale, it creates a new ERC20z token and corresponding Uniswap V3 pool for a WETH pair with the ERC20z with a 1% fee.

To mint an 1155 token, the mint function is called on the ZoraTimedSaleStrategy contract with the mint fee of 0.000111 eth x quantity to mint sent with the call. In the mint call, 0.0000111 eth x quantity is held in escrow to bootstrap liquidity for the Uniswap V3 pool, the rest is distributed as rewards via the ProtocolRewards contract, and x quantity of 1155s is minted to the recipient.

When the sale has ended, the launchMarket function can be called to launch the secondary market, which mints ERC20z tokens, wraps the escrowed ETH as WETH, and deposits the WETH and a portion of the minted ERC20z to the Uniswap V3 pool to launch the secondary market.

contract ZoraTimedSaleStrategyImpl {
  /// @notice Deprecated - use SalesConfigV2
  struct SalesConfig {
    /// @notice Unix timestamp for the sale start
    uint64 saleStart;
    /// @notice Unix timestamp for the sale end
    uint64 saleEnd;
    /// @notice The ERC20Z name
    string name;
    /// @notice The ERC20Z symbol
    string symbol;
  }
 
  struct SalesConfigV2 {
    /// @notice Unix timestamp for the sale start
    uint64 saleStart;
    /// @notice The amount of time after the `minimumMarketEth` is reached until the secondary market can be launched
    uint64 marketCountdown;
    /// @notice The amount of ETH required to launch a market
    uint256 minimumMarketEth;
    /// @notice The ERC20Z name
    string name;
    /// @notice The ERC20Z symbol
    string symbol;
  }
 
  /// @notice Deprecated - use setSaleV2
  /// Called by an 1155 collection to set the sale config for a given token
  /// @dev Additionally creates an ERC20Z and Uniswap V3 pool for the token
  /// @param tokenId The collection token id to set the sale config for
  /// @param salesConfig The sale config to set
  function setSale(uint256 tokenId, SalesConfig calldata salesConfig) external {
    //...
  }
 
  /// @notice Called by an 1155 collection to set the sale config for a given token
  /// @dev Additionally creates an ERC20Z and Uniswap V3 pool for the token
  /// @param tokenId The collection token id to set the sale config for
  /// @param salesConfig The sale config to set
  function setSaleV2(
    uint256 tokenId,
    SalesConfigV2 calldata salesConfig
  ) external {
    //...
  }
 
  /// @notice Called by a collector to mint a token
  /// @param mintTo The address to mint the token to
  /// @param quantity The quantity of tokens to mint
  /// @param collection The address of the 1155 token to mint
  /// @param tokenId The ID of the token to mint
  /// @param mintReferral The address of the mint referral
  /// @param comment The optional mint comment
  function mint(
    address mintTo,
    uint256 quantity,
    address collection,
    uint256 tokenId,
    address mintReferral,
    string calldata comment
  ) external payable nonReentrant {
    //...
  }
 
  /// @notice Called by anyone upon the end of a primary sale to launch the secondary market.
  /// @param collection The 1155 collection address
  /// @param tokenId The 1155 token id
  function launchMarket(address collection, uint256 tokenId) external {
    //...
  }
}

When the liquidity is deposited into the pool to launch the market, the Royalties contract is set as the Uniswap LP position owner. When tokens are swapped in the pool, the Royalties contract can earn fees via these positions it holds in the liquidity pools. Creators can withdraw 75% of the 1% fee through the claim() or claimFor() function on the contract.

/// @title Royalties
/// @notice Manages the royalty distribution for Zora 1155 secondary markets on Uniswap V3
contract Royalties is ERC721Holder {
  /// @notice Claim royalties for a creator.
  /// Must be called by the creator reward recipient for the
  /// corresponding 1155 contract and token.
  /// @param erc20z ERC20Z address to claim royalties from
  /// @param recipient The recipient address
  function claim(
    address erc20z,
    address payable recipient
  ) external nonReentrant {
    //...
  }
 
  /// @notice Claim royalties for a creator
  /// @param erc20z ERC20Z address to claim royalties from
  function claimFor(address erc20z) external nonReentrant {
    //...
  }
}

Math

The logic for launching the secondary market is designed with a few requirements:

  • The secondary market starting price should be 0.000111 eth per token
  • There should be an equal amount of ERC20 to ERC1155 tokens minted/total supply
  • Each owned ERC1155 should be backed and unwrappable by 1 ERC20, and vice versa.

To achieve this, the following logic is used. For the sake of simplicity, one ERC20 represents 10^18 units:

  • For each 1155 minted, 0.0000111 eth is escrowed to be used to deposit liquidity into the Uniswap V3 pool
  • When the sale ends and the market is launched, one ERC20 is minted for each 1155 minted. An additional 10% of ERC20 is minted to provide liquidity for the LP position, and the corresponding 10% of 1155s is minted to match the ERC20 total supply.
  • That 0.0000111 eth x quantity is wrapped in WETH, and the 10% of the ERC20 is deposited with the WETH to create the initial liquidity in the Uniswap V3 pool which results in a price of 0.000111 eth per ERC20.
  • Since ERC1155 quantities are in whole numbers, all amounts are rounded up when minting additional for launching the market, and the excess erc20 and erc1155 is burned.

A mint threshold has been introduced with the v2 sales configuration. A token must receive a minimum of X mints before a countdown of Y time can start. Once Y time has been reached the secondary market will begin.

Additional Things That Can be Done

On the ERC20z contract, the wrap() function converts a quantity of Zora 1155 tokens to their corresponding ERC20z tokens. Similarly unwrap() converts a quantity of ERC20z tokens back to the corresponding 1155 tokens.

Wrapping and unwrapping is 1:1, fractional values are not possible.

Deployed Deterministic Addresses

The ZoraTimedSaleStrategy minter and supporting Royalties contracts are deployed deterministically to multiple networks:

  • Deployed to chains: Zora, Base, Ethereum Mainnet, Optimism, Arbitrum One, Blast, Zora Sepolia, Base Sepolia, Sepolia
  • Zora Timed Sale Strategy on all chains: 0x777777722D078c97c6ad07d9f36801e653E356Ae
  • Royalties on all chains: 0x77777771DF91C56c5468746E80DFA8b880f9719F

Requirements

To use this sale strategy the Zora 1155 contract needs to be at least version 2.12.3 or greater