The Liquidity Provider Lifecycle

It's helpful to first check out the basics about Pools and Custodies before breaking in here.

Earning Yield

Ultimately the goal of the LP is to earn yield on their crypto. Currently we only offer support for yield on stablecoins, but look to support non-stables in the future. On the Imperial website, there is a Liquidity tab where any LP provider can swab USDT/C for LP tokens. There are a fixed number of LP tokens in existence, created de-novo for specifically this task and each Pool uses the same LP token and has a USDT and USDC Custody.

While each Pool started with some number of LP tokens and stables in it's Custodies, we expect due to fluctuating rates of returns between the Pools (which represent different volatility products) to cause net flows of stables and LP tokens between them by arbitrageurs overtime, leading to an overall average rate of return between all of the Pools.

The Imperial website will allow LPs to choose the best current Pool against which to presently trade with.

Infrastructure

The actual swapping is done against the VolPerp contract, using the swap endpoint, which takes the following accounts and signature:

pub struct Swap<'info> {
    #[account(mut)]
    pub user: AccountInfo<'info>,
    #[account(mut, associated_token::mint = custody_token_account.mint, associated_token::authority = custody)]
    pub custody_token_account: Account<'info, TokenAccount>,
    #[account(mut, associated_token::mint = custody_token_account.mint, associated_token::authority = user)]
    pub user_custody_token_account: Account<'info, TokenAccount>,
    #[account(mut, associated_token::mint = lp_token_account.mint, associated_token::authority = user)]
    pub user_lp_token_account: Account<'info, TokenAccount>,
    #[account(mut, associated_token::mint = lp_token_account.mint, associated_token::authority = pool)]
    pub lp_token_account: Account<'info, TokenAccount>,
    #[account(has_one = lp_token_account, seeds=[b"pool", pool.name.as_bytes().as_ref(), pool.state.key().as_ref(), pool.lp_mint.key().as_ref()], bump)]
    pub pool: Account<'info, Pool>,
    #[account(mut, has_one = pool, has_one = custody_token_account, seeds=[b"custody", pool.key().as_ref(), custody.mint.key().as_ref()], bump)]
    pub custody: Account<'info, Custody>,
    pub transfer_authority: Signer<'info>,
    pub token_program: Program<'info, Token>,
}

pub fn handle_swap<'info>(
    ctx: Context<'_, '_, 'info, 'info, Swap<'info>>,
    amount_in: u64,
    min_amount_out: u64,
    lp_in: bool,
) -> Result<()> { /*..*/ }

Using this instruction, any LP can swap LP tokens in or out for stablecoins against a specific Pool and Custody using a constant product K = xy curve.

Note that the actual authority moving tokens in from the user is always the transfer_authority, a separate signing account that is intended to be transient, and needs to be granted that authority using a separate approve and revoke instruction in the transaction, a common design pattern.

How is Yield Generated?

Concretely, yield is generated in primarily in two ways.

  1. When a trader makes a poor trade and collateral is left behind after a Position closes. This increases the number of stablecoins in the Custodies which means more stablecoins per unit LP to be swapped.

  2. When certain fees are collected such as opening, closing, trade impact, or borrowing fees.

    1. Opening and closing fees have identical settings and closely adhere to the constants set by the industry, ie Jupiter Perps

    2. Borrowing fees are set by periodically checking T-bill rates and setting a min-max range for the dual slope model based on a reasonable range for a secured instrument in an offsetted window off the T-bill (say, +2 to +10)

    3. For now trade impact is set to 0.

Due to the way some tokens are set aside for every Position above and beyond those loaned, in effect not all tokens being used by traders are interest-bearing, lowering the effective interest rate of the system as a whole.

While being less capital efficient, it is a safer system that can be tweaked towards an optimum safety-return trade-off over time as we collect more data.

Last updated