⚗ī¸Liquidity Providers

Incentives

Maximize: swap fees, non-toxic order flow

Minimize: toxic order flow, uncompensated MEV

Liquidity providers are the lifeblood of the DeFi system. Uniswap V2 full-range LP unlocked a lot of new features like composable LP positions in other protocols, price discovery of new tokens, and the ability to passively LP.

IL & Liquidity Providers

It is well known that passive liquidity providers (LPs) tend to get a raw deal on AMMs today. They are exposed to adverse selection and usually not compensated enough in trading fees from uninformed flow to make up for it.

Drawbacks of LPing on Uniswap V3

In a TWAMM, all of the price-insensitive long-term flow goes through the LP pool which accrues trading fees. Note that profits earned and fees incurred by arbitrageurs are implicitly paid for by the long-term order, in the form of slippage.

Understanding Toxic Order Flow

Identifying toxic order flow and attracting large uninformed (non-toxic) order flow is the holy grail for market makers and liquidity providers. However, this is a hard problem for DEXs to solve without adding gas-inefficient on-chain logic or relying on off-chain computations.

In most DEXs, all swaps go through the same interface and fee bucket as shown in the diagram above. In this scenario, passive liquidity providers eat the toxic order flow because arbitrageurs exploit outdated pool prices without compensating LPs with the commensurate MEV extracted.

Dual Swap Paradigm

Our TWAMM implementation has an elegant incentive structure baked in:

  • The short-term swap interface is somewhat more costly gas-wise than a Uniswap V2 swap and will primarily be used to correct pool prices by arbitrageurs. So, we collect and pass on a portion of the MEV extracted via a proprietary arbitrageur swap interface.

  • The long-term swap interface proxies trades that happen over multiple blocks & are inherently uninformed non-toxic flow. This user pays a standard low fee that outcompetes incumbents like OTCs on fees, and other implementations on gas costs.

Full Range Liquidity + MEV Resistance

Democratizing the Edge for Passive LPs

Now that we have a DeFi protocol that has built-in MEV extraction and novel trade execution for large uninformed order flow, we are finally able to even the playing field for passive LPs providing full range liquidity.

Sophisticated market makers have been hedging the downside of providing liquidity in DEXs by arbitraging the very same pools. With our TWAMM implementation, passive LPs will be able to hedge against impermanent loss by collecting and automatically re-investing MEV rewards from arbitrageurs.

Automatic MEV Re-investment

An important part of our implementation is a design feature that transfers a portion of MEV tokens (WETH) from arbitrage partners to LPs. The design balances gas use and composability while eliminating the incentive to use flash loans for just-in-time liquidity attacks to capture MEV rewards.

How to use

Code Example

/// @notice Join function with additional checks in place to ensure users don't lose funds by interacting
///         with TWAMM pools improperly. This function is only for normal Joins < JoinType.Join > 
///         and will be more gas expensive due to the additional checks.
/// @param  _liquidity0 the amount of token0 liquidity to be added
/// @param  _liquidity1 the amount of token1 liquidity to be added
/// @param  _token0 the address of token0
/// @param. _token1 the address of token1
/// @param  _poolType the type of liquidity pool
/// @param  _to the address where LP tokens should be sent to
///
function join(
  uint256 _liquidity0,
  uint256 _liquidity1,
  address _token0,
  address _token1,
  address _poolType,
  address _to
) external {
  // balancer vault
  address constant VAULT = 0xBA12222222228d8Ba445958a75a0704d566BF2C8;
  // mainnet factory
  address constant FACTORY = 0xD64c9CD98949C07F3C85730a37c13f4e78f35E77;
  // get pool from factory
  address pool = ICronV1PoolFactory(FACTORY).getPool(_token0, _token1, _poolType);
  // setup information for pool join
  bytes memory userData = getJoinUserData(uint256(ICronV1PoolEnums.JoinType.Join), _liquidity0, _liquidity1);
  bytes32 poolId = ICronV1Pool(pool).POOL_ID();
  (IERC20[] memory tokens, , ) = IVault(VAULT).getPoolTokens(poolId);
  IAsset[] memory assets = _convertERC20sToAssets(tokens);
  uint256[] memory maxAmountsIn = new uint256[](tokens.length);
  for (uint256 i; i < tokens.length; i++) {
    maxAmountsIn[i] = type(uint256).max;
  }
  bool fromInternalBalance = false;
  // approve tokens to be used by vault
  // ensure correct ordering of assets matches _token0 & _token1
  // this assumes _token0 == tokens[0] and _token1 == tokens[1]
  IERC20(tokens[0]).approve(VAULT, _liquidity0);
  IERC20(tokens[1]).approve(VAULT, _liquidity1);
  // send join pool request to the pool via vault
  IVault(VAULT).joinPool(
    poolId,
    user,
    payable(user),
    IVault.JoinPoolRequest(assets, maxAmountsIn, userData, fromInternalBalance)
  );
}

Periphery

Who needs this?

💸Yield AggregatorsđŸĒ‚Airdrops Damper

Token Price Discovery

DAO Treasuries

Retail & Passive LPs

Last updated