How Wreath Works

This document provides an in-depth technical explanation of the Wreath Finance protocol, its smart contract architecture, and the lifecycle of bonds and liquidity.

Target Audience: This page is intended for developers, auditors, and users with a strong technical understanding of DeFi and smart contracts.

I. Core Concepts

  • Decentralized Bond Issuance: Enabling users to gain access to liquidity by offering tokenized bonds directly to other participants.

  • Automated Liquidity Provision: Integrating with Uniswap V3 to automatically create and manage liquidity pools for the project's tokens post-sale.

  • Token Wrapping: Utilizing a WrappedToken contract to manage the project tokens during the bond sale and lock-up period.

  • Liquidity Tokens (LTs): ERC20 tokens representing an investor's share in a bond's success and their claim on future project tokens and/or liquidity pool shares.

II. Smart Contract Architecture

The Wreath protocol is primarily composed of three key smart contracts:

  • BondFactory.sol:

    • Serves as the entry point for creating new bond offerings.

    • Utilizes the Clones (Minimal Proxy) pattern to deploy new Bond contracts efficiently.

    • Manages a registry of all created bonds.

    • Handles the deployment fee.

  • Bond.sol:

    • The core contract representing an individual bond offering. Each bond is a separate instance of this contract.

    • Manages the bond's parameters (project token, raise token, sale duration, price, lock period, etc.).

    • Handles the contribution process from investors.

    • Interacts with WrappedToken.sol to manage the project tokens.

    • Interacts with Uniswap V3 (via INonfungiblePositionManager) to:

      • Create the liquidity pool if it doesn't exist.

      • Add initial liquidity using a portion of the raised funds and the wrapped project tokens.

      • Manage the Uniswap V3 NFT position representing the liquidity.

    • Manages the distribution of LiquidityToken (LTs) to contributors.

    • Facilitates the claiming of project tokens by LT holders after the lock period.

    • Handles the return of unsold tokens to the creator if the raise target is not met.

  • WrappedToken.sol:

    • An ERC20 token contract that wraps the project's original token.

    • The Bond contract locks the project tokens within this WrappedToken contract.

    • The WrappedToken is then used to create the Uniswap V3 liquidity pool alongside the raise token.

    • Includes a lockPeriod after which the wrapped tokens can be unwrapped back to the original project tokens by those holding the WrappedToken (typically the Bond contract or, indirectly, the LT holders after redemption).

    • The Bond contract is the owner and primary interactor with its deployed WrappedToken instance.

  • LiquidityToken.sol:

    • A simple ERC20 token contract, an instance of which is deployed for each Bond.

    • These tokens are minted to contributors proportionally to their investment in the bond.

    • Represents the contributor's right to claim their share of the project tokens (or proceeds from the liquidity pool) after the bond matures and the lock period ends.

    • The Bond contract is the owner and controls the minting and burning of its associated LiquidityToken.

III. Bond Lifecycle

  1. Creation (via BondFactory):

    • A project creator calls BondFactory.createBond() with specified parameters:

      • projectToken: The token the project is offering.

      • tokenAmount: The total amount of project tokens for sale.

      • raiseToken: The token the project wants to raise (e.g., WETH, USDC).

      • pricePerToken: Price of one project token in terms of the raise token. This is used to calculate the raiseTarget for the creator and the amount of LiquidityTokens contributors receive.

      • saleDuration: The period during which contributions are accepted.

      • lockPeriod: The duration for which the liquidity (and thus project tokens for contributors) will be locked. The lock starts from when the initial single-sided liquidity is added.

      • startingTick (for Uniswap V3): Initial price tick for the single-sided liquidity pool, defining the price range for the project's tokens.

      • name, description: Metadata for the bond.

    • The BondFactory deploys a new Bond contract (clone) and new instances of WrappedToken and LiquidityToken.

    • Ownership of the WrappedToken and LiquidityToken contracts is transferred to the newly created Bond contract.

    • The project creator must have pre-approved the BondFactory (or the Bond contract's address, known via deterministic deployment or passed in) to spend their projectToken.

    • The Bond contract's initialize function is called:

      • It pulls the specified tokenAmount of projectToken from the params.factory address (which should be holding/approved them on behalf of the creator) and transfers them to its associated WrappedToken contract.

      • The WrappedToken contract mints an equivalent amount of wrapped versions of these tokens to the Bond contract.

      • Crucially, the _addInitialLiquidity() internal function is called. This function uses all of the Bond contract's newly acquired WrappedToken holdings to create a single-sided liquidity position on Uniswap V3. The raiseToken is not used by the Bond contract for this initial LP. The LP is established entirely with the project's tokens at a specified price range (startingTick).

      • liquidityPositionId (the Uniswap V3 NFT) is stored, and liquidityAddedTime is set, marking the beginning of the lockPeriod.

  2. Contribution Phase (via Bond):

    • Investors discover active bonds.

    • To contribute, an investor calls Bond.contribute() (or contributeETH() if raiseToken is WETH) with the amount of raiseToken they wish to invest.

    • The Bond contract:

      • Checks if the sale is active (current time < saleEndTime) and if the raiseTarget (calculated as tokenAmount * pricePerToken) has not been fully met by previous contributions.

      • Transfers the acceptedAmount of raiseToken directly from the investor to the creator's address. The Bond contract does not hold the raiseToken at any point.

      • Mints LiquidityToken (LTs) to the investor. The amount of LTs is calculated based on their acceptedAmount of raiseToken and the pricePerToken, effectively representing their share of the claim on the project tokens offered through the bond (which are already in the single-sided LP).

      • Updates totalRaised (tracking funds received by the creator) and the investor's contributions mapping.

  3. Sale End (Monitoring totalRaised vs raiseTarget):

    • The sale continues until saleEndTime.

    • The critical factor at saleEndTime (or if totalRaised meets raiseTarget sooner) is the status of totalRaised versus raiseTarget.

    • If raiseTarget is met (or exceeded): The fundraising is considered successful for the creator. The single-sided liquidity position established at initialization remains as is, now effectively "backed" by the successful raise (in the creator's hands). LT holders have a claim on this LP.

    • If raiseTarget is NOT met by saleEndTime:

      • The returnUnsoldTokens() function can be called (typically by the creator or an automated system).

      • This function calculates the "unsold" percentage of the project tokens (based on totalRaised vs raiseTarget).

      • It then removes a proportional amount of liquidity from the single-sided Uniswap V3 LP.

      • The WrappedToken (project tokens) and any raiseToken (if trades occurred against the LP, making it two-sided) retrieved from this portion of the LP are transferred to the creator.

      • The remaining portion of the LP stays, claimable by LT holders who did contribute.

  4. Lock Period:

    • During the lockPeriod (which starts from liquidityAddedTime during initialize), the Uniswap V3 LP position held by the Bond contract is effectively locked.

    • The removeLiquidity function in Bond.sol (used by LT holders to claim) can only be called after liquidityAddedTime + lockPeriod.

    • WrappedToken instances also have their own lockPeriod (initialized with the bond's lockPeriod and liquidityAddedTime from WrappedToken's perspective upon its creation/initial mint by the Bond contract) that governs when external holders can unwrap them.

  5. Maturity & Claiming by LT Holders (via Bond):

    • After saleEndTime AND liquidityAddedTime + lockPeriod have passed:

    • Holders of LiquidityToken (LTs) can call Bond.removeLiquidity(liquidityTokenAmount).

    • The Bond contract:

      • Verifies that the lock period is over.

      • If totalRaised < raiseTarget and returnUnsoldTokens() hasn't been called, it calls returnUnsoldTokens() first to ensure the creator reclaims their share of unsold tokens from the LP.

      • Calculates the LT holder's proportional share of the liquidity in the Uniswap V3 LP (the liquidityPositionId held by the Bond contract). This is based on the liquidityTokenAmount they are redeeming against the totalLiquidityTokens (total LTs issued).

      • Burns the investor's LTs that are being redeemed (after transferring them to the Bond contract).

      • Calls decreaseLiquidity on the Uniswap V3 NonfungiblePositionManager for the Bond's liquidityPositionId, for the user's proportional share.

      • Collects the resulting token0 and token1 (which will be WrappedToken and potentially some raiseToken if trades occurred) from Uniswap V3.

      • Transfers these collected tokens to the LT holder (msg.sender). If WrappedToken is collected, it's passed to IWrappedToken(wrappedToken).burnForLiquidity(amount, msg.sender), which unwraps it to the original projectToken and sends it to the user. Any raiseToken collected is sent directly.

IV. Token Wrapping (WrappedToken.sol)

  • Purpose: To create a distinct representation of the project token that is under the control of the Bond contract and subject to a lock period. This facilitates safer LPing and timed release.

  • Minting: Only the owner (the Bond contract) can mint WrappedTokens. This happens when the Bond contract receives the original projectToken from the creator.

  • Burning/Unwrapping:

    • burn(from, amount): Standard burn function allowing holders to unwrap their WrappedToken back to the originalToken after liquidityAddedTime + lockPeriod. This requires the caller to have WrappedTokens.

    • burnForLiquidity(amount, to): A special function callable only by the bondContract. This allows the Bond contract to burn WrappedTokens it holds (e.g., unsold ones or those retrieved from an LP position) and send the originalToken to a specified recipient (e.g., the creator or an LT holder). This function likely bypasses the public lockPeriod check when called by the Bond contract for its own internal operations.

  • Decimals: The WrappedToken inherits the decimals() of the originalToken to ensure consistency.

V. Liquidity Tokens (LiquidityToken.sol)

  • Representation: ERC20 tokens that signify an investor's stake in a particular bond.

  • Issuance: Minted by the Bond contract to contributors during the sale. The amount is proportional to their contribution.

  • Redemption: After maturity and the lock period, LTs are burned by the Bond contract when an investor claims their share of the underlying projectToken (or LP share).

  • Transferability: LTs are generally transferable, allowing for secondary market activity if desired, though this depends on the platform's design and user expectations.

VI. Key Interactions & Flows

  • Creator to Factory to Bond: Deployment and initialization.

  • Investor to Bond: Contribution and LT minting.

  • Bond to Uniswap V3: Pool creation and liquidity provision.

  • Bond to WrappedToken: Locking project tokens, unwrapping for distribution.

  • Investor (LT Holder) to Bond: Claiming assets post-lockup.

VII. Security Considerations & Trust Assumptions

  • Smart Contract Audits: (Mention if audits have been performed and by whom).

  • Owner Privileges:

    • BondFactory owner can set fees and upgrade implementations (if applicable).

    • Bond contract (as owner of WrappedToken and LiquidityToken) has significant control over these.

  • Immutability: Core bond parameters are immutable after deployment.

  • Reentrancy Guards: Used in Bond.sol to prevent reentrancy attacks during critical operations like contributions and claims.

  • Price Oracles: (Clarify if and how external price oracles are used, e.g., for startingTick or ongoing LP management, though startingTick seems to be a fixed initial parameter). The current contracts suggest startingTick is provided at creation and doesn't rely on an external oracle during the bond's operation for its core sale/LPing logic.

  • Uniswap V3 Interaction Risks: Standard risks associated with interacting with external protocols, though mitigated by using established interfaces and SafeERC20.

Last updated