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.
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 thisWrappedToken
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 theWrappedToken
(typically theBond
contract or, indirectly, the LT holders after redemption).The
Bond
contract is the owner and primary interactor with its deployedWrappedToken
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 associatedLiquidityToken
.
III. Bond Lifecycle
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 theraiseTarget
for the creator and the amount ofLiquidityToken
s 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 newBond
contract (clone) and new instances ofWrappedToken
andLiquidityToken
.Ownership of the
WrappedToken
andLiquidityToken
contracts is transferred to the newly createdBond
contract.The project creator must have pre-approved the
BondFactory
(or theBond
contract's address, known via deterministic deployment or passed in) to spend theirprojectToken
.The
Bond
contract'sinitialize
function is called:It pulls the specified
tokenAmount
ofprojectToken
from theparams.factory
address (which should be holding/approved them on behalf of the creator) and transfers them to its associatedWrappedToken
contract.The
WrappedToken
contract mints an equivalent amount of wrapped versions of these tokens to theBond
contract.Crucially, the
_addInitialLiquidity()
internal function is called. This function uses all of theBond
contract's newly acquiredWrappedToken
holdings to create a single-sided liquidity position on Uniswap V3. TheraiseToken
is not used by theBond
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, andliquidityAddedTime
is set, marking the beginning of thelockPeriod
.
Contribution Phase (via
Bond
):Investors discover active bonds.
To contribute, an investor calls
Bond.contribute()
(orcontributeETH()
ifraiseToken
is WETH) with the amount ofraiseToken
they wish to invest.The
Bond
contract:Checks if the sale is active (current time <
saleEndTime
) and if theraiseTarget
(calculated astokenAmount * pricePerToken
) has not been fully met by previous contributions.Transfers the
acceptedAmount
ofraiseToken
directly from the investor to thecreator
's address. TheBond
contract does not hold theraiseToken
at any point.Mints
LiquidityToken
(LTs) to the investor. The amount of LTs is calculated based on theiracceptedAmount
ofraiseToken
and thepricePerToken
, 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'scontributions
mapping.
Sale End (Monitoring
totalRaised
vsraiseTarget
):The sale continues until
saleEndTime
.The critical factor at
saleEndTime
(or iftotalRaised
meetsraiseTarget
sooner) is the status oftotalRaised
versusraiseTarget
.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 bysaleEndTime
: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
vsraiseTarget
).It then removes a proportional amount of liquidity from the single-sided Uniswap V3 LP.
The
WrappedToken
(project tokens) and anyraiseToken
(if trades occurred against the LP, making it two-sided) retrieved from this portion of the LP are transferred to thecreator
.The remaining portion of the LP stays, claimable by LT holders who did contribute.
Lock Period:
During the
lockPeriod
(which starts fromliquidityAddedTime
duringinitialize
), the Uniswap V3 LP position held by theBond
contract is effectively locked.The
removeLiquidity
function inBond.sol
(used by LT holders to claim) can only be called afterliquidityAddedTime + lockPeriod
.WrappedToken
instances also have their ownlockPeriod
(initialized with the bond'slockPeriod
andliquidityAddedTime
fromWrappedToken
's perspective upon its creation/initial mint by theBond
contract) that governs when external holders can unwrap them.
Maturity & Claiming by LT Holders (via
Bond
):After
saleEndTime
ANDliquidityAddedTime + lockPeriod
have passed:Holders of
LiquidityToken
(LTs) can callBond.removeLiquidity(liquidityTokenAmount)
.The
Bond
contract:Verifies that the lock period is over.
If
totalRaised < raiseTarget
andreturnUnsoldTokens()
hasn't been called, it callsreturnUnsoldTokens()
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 theBond
contract). This is based on theliquidityTokenAmount
they are redeeming against thetotalLiquidityTokens
(total LTs issued).Burns the investor's LTs that are being redeemed (after transferring them to the Bond contract).
Calls
decreaseLiquidity
on the Uniswap V3NonfungiblePositionManager
for theBond
'sliquidityPositionId
, for the user's proportional share.Collects the resulting
token0
andtoken1
(which will beWrappedToken
and potentially someraiseToken
if trades occurred) from Uniswap V3.Transfers these collected tokens to the LT holder (
msg.sender
). IfWrappedToken
is collected, it's passed toIWrappedToken(wrappedToken).burnForLiquidity(amount, msg.sender)
, which unwraps it to the originalprojectToken
and sends it to the user. AnyraiseToken
collected is sent directly.
IV. Token Wrapping (WrappedToken.sol
)
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 mintWrappedToken
s. This happens when theBond
contract receives the originalprojectToken
from the creator.Burning/Unwrapping:
burn(from, amount)
: Standard burn function allowing holders to unwrap theirWrappedToken
back to theoriginalToken
afterliquidityAddedTime + lockPeriod
. This requires the caller to haveWrappedToken
s.burnForLiquidity(amount, to)
: A special function callable only by thebondContract
. This allows theBond
contract to burnWrappedToken
s it holds (e.g., unsold ones or those retrieved from an LP position) and send theoriginalToken
to a specified recipient (e.g., the creator or an LT holder). This function likely bypasses the publiclockPeriod
check when called by theBond
contract for its own internal operations.
Decimals: The
WrappedToken
inherits thedecimals()
of theoriginalToken
to ensure consistency.
V. Liquidity Tokens (LiquidityToken.sol
)
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 underlyingprojectToken
(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 ofWrappedToken
andLiquidityToken
) 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, thoughstartingTick
seems to be a fixed initial parameter). The current contracts suggeststartingTick
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