Skip to content

Commit

Permalink
Timed Sale Strategy Docs (#674)
Browse files Browse the repository at this point in the history
* feat: wip of timed sale strategy docs

* updated outline

* feat: add royalties overview

* fix: fn code

* fix: sparks symbol

* fix: intro

* Zora Timed Sale Strategy added contract overview, code and math (#685)

* added some contract overview and math. going to add some diagrams

* fixed grammar

* wip on class diagram

* added link to support docs

* added deployed chaings

* feat: add v2 sales config

* fix: spelling

* fix: more spelling

* feat: add setSaleV2

* fix: syntax

* feat: add liquidity comment

* fix: sparks

---------

Co-authored-by: Dan Oved <[email protected]>
  • Loading branch information
IsabellaSmallcombe and oveddan authored Sep 4, 2024
1 parent b18083b commit d9cb0ec
Show file tree
Hide file tree
Showing 4 changed files with 219 additions and 5 deletions.
171 changes: 171 additions & 0 deletions docs/pages/contracts/ZoraTimedSaleStrategy.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
# 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](https://support.zora.co/en/categories/577345-secondary-market)
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

| Recipient | Amount |
| --------------- | -------------------- |
| Creator | 0.0000555 eth (✧55) |
| Create Referral | 0.0000111 eth (✧11) |
| Mint Referral | 0.0000222 eth (✧22) |
| Zora | 0.0000111 eth (✧11) |
| Market | 0.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 `startTime` and `endTime` 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](./rewards) 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.

```solidity
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.

```solidity
/// @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 visa 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
10 changes: 5 additions & 5 deletions docs/pages/contracts/rewards.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,11 @@ Rewards v1.1 is deployed at the same address on all networks.

| Recipient | Amount |
| --------------- | ------------ |
| Creator | 0.000333 ETH |
| Create Referral | 0.000111 ETH |
| Mint Referral | 0.000111 ETH |
| First Minter | 0.000111 ETH |
| Zora | 0.000111 ETH |
| Creator | 0.0000555 ETH |
| Create Referral | 0.0000111 ETH |
| Mint Referral | 0.0000222 ETH |
| Zora | 0.0000111 ETH |
| Market | 0.0000111 ETH |

#### Paid Mint

Expand Down
39 changes: 39 additions & 0 deletions docs/uml/class-diagram-timedsale.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

@startuml
class ZoraTimedSaleStrategy {
}

class ERC20z {
}

class Royalties {
}

class ZoraCreator1155 {
}

class UniswapLP {

}

class Creator {
}

ZoraTimedSaleStrategy --> ERC20z: creates
ZoraTimedSaleStrategy --> ERC20z: deposits market reward

ZoraTimedSaleStrategy --> UniswapLP: creates

ERC20z --> UniswapLP: deposits liquidity

ERC20z --> Royalties: sets as liquidity owner

Royalties --> UniswapLP: withdraws liquidity

Creator --> Royalties: withdraws royalties





@enduml
4 changes: 4 additions & 0 deletions docs/vocs.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ export default defineConfig({
text: "Permission",
link: "/contracts/Permissions1155",
},
{
text: "Timed Sale with Secondary",
link: "/contracts/ZoraTimedSaleStrategy",
},
{
text: "ERC20 Minter",
link: "/contracts/ERC20Minter",
Expand Down

0 comments on commit d9cb0ec

Please sign in to comment.