diff --git a/docs/pages/contracts/ZoraTimedSaleStrategy.mdx b/docs/pages/contracts/ZoraTimedSaleStrategy.mdx new file mode 100644 index 00000000..6e121c24 --- /dev/null +++ b/docs/pages/contracts/ZoraTimedSaleStrategy.mdx @@ -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 diff --git a/docs/pages/contracts/rewards.mdx b/docs/pages/contracts/rewards.mdx index 25e742ef..ffc3db2c 100644 --- a/docs/pages/contracts/rewards.mdx +++ b/docs/pages/contracts/rewards.mdx @@ -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 diff --git a/docs/uml/class-diagram-timedsale.puml b/docs/uml/class-diagram-timedsale.puml new file mode 100644 index 00000000..36229ae1 --- /dev/null +++ b/docs/uml/class-diagram-timedsale.puml @@ -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 \ No newline at end of file diff --git a/docs/vocs.config.ts b/docs/vocs.config.ts index b087c2f8..8d46ae32 100644 --- a/docs/vocs.config.ts +++ b/docs/vocs.config.ts @@ -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",