Protocol
Guides
Providing liquidity

Providing liquidity

Adding liquidity

Anyone can add liquidity to a pool, as long as the expiry has not been reached yet. Portfolio provides some utility functions that can help liquidity providers calculate the optimal amounts they need to provide to the allocate function:

  • getMaxLiquidity calculates the exact amount of liquidity that can be provided from an amount of asset and quote tokens.
  • getLiquidityDeltas calculates the exact amount of asset and quote tokens that are required to provide an exact amount of liquidity.

Together both functions can be used to calculate exactly how much asset and quote tokens need to be provided for a given amount of liquidity.

// Assuming we want to allocate into the pool `1099511627777`:
uint64 poolId = 1099511627777;
 
// Let's check how many tokens we have.
uint256 assetBalance = IERC20(asset).balanceOf(address(this));
uint256 quoteBalance = IERC20(quote).balanceOf(address(this));
 
// Now we check the maximum amount of liquidity we can get from both amounts
uint128 maxLiquidity = portfolio.getMaxLiquidity(poolId, assetBalance, quoteBalance);
 
// Let's use this liquidity amount to get some more precise deltas.
(uint128 deltaAsset, uint128 deltaQuote) = portfolio.getLiquidityDeltas(poolId, int128(maxLiquidity));
ℹ️

Note that using getMaxLiquidity and getLiquidityDeltas onchain is not ideal because pools can be manipulated to alter the results.

Once we calculate all of these amounts, we can simply call the allocate function by providing the following parameters:

NameTypeDescription
useMaxboolPassing true will use the asset and quote amounts of the transient balance.
poolIduint64Id of the pool where the liquidity will be allocated into.
deltaLiquidityuint128Quantity of liquidity to mint in WAD units.
maxDeltaAssetuint128Maximum quantity of asset tokens paid in WAD units.
maxDeltaQuoteuint128Maximum quantity of quote tokens paid in WAD units.
ℹ️

Passing 0 as a poolId will act as a magic value and will use the poolId from _getLastPoolId. Note that this feature should only be used with multicall to avoid being tricked into allocating into the wrong pool.

This is what a complete allocation looks like:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
 
import "portfolio/interfaces/IPortfolio.sol";
import "portfolio/interfaces/IERC20.sol";
 
contract AllocateExample {
    IPortfolio public portfolio;
    address public asset;
    address public quote;
 
    constructor(address portfolio_, address asset_, address quote_) {
        portfolio = IPortfolio(portfolio_);
        asset = asset_;
        quote = quote_;
    }
 
    function allocate() external {
        // Assuming we want to allocate into the pool `1099511627777`:
        uint64 poolId = 1099511627777;
 
        // Let's check how many tokens we have.
        uint256 assetBalance = IERC20(asset).balanceOf(address(this));
        uint256 quoteBalance = IERC20(quote).balanceOf(address(this));
 
        // Now we check the maximum amount of liquidity we can get from our
        // current token balances.
        uint128 maxLiquidity =
            portfolio.getMaxLiquidity(poolId, assetBalance, quoteBalance);
 
        // Let's use this liquidity amount to get some more precise deltas.
        (uint128 deltaAsset, uint128 deltaQuote) =
            portfolio.getLiquidityDeltas(poolId, int128(maxLiquidity));
 
        // We allow the portfolio contract to move our tokens.
        IERC20(asset).approve(address(portfolio), deltaAsset);
        IERC20(quote).approve(address(portfolio), deltaQuote);
 
        // Finally, we call the `allocate` function.
        portfolio.allocate(false, poolId, maxLiquidity, deltaAsset, deltaQuote);
    }
}

You can check out the full code source here (opens in a new tab).

Removing liquidity

Removing liquidity is as simple as calling the deallocate function and passing the right parameters:

NameTypeDescription
useMaxboolPass true to remove all the liquidity of the position.
poolIduint64Id of the pool to remove the liquidity from.
deltaLiquidityuint128Quantity of liquidity to burn in WAD units.
minDeltaAssetuint128Minimum quantity of asset tokens to receive in WAD units.
minDeltaQuoteuint128Minimum quantity of quote tokens to receive in WAD units.

Using the positions function, we can check how much liquidity a user owns:

uint128 liquidity = portfolio.positions(user, poolId);

Alternatively, the same functions that we used to allocate can be used again to calculate how much liquidity we might want to deallocate and how much asset and quote tokens we will receive:

(uint128 minDeltaAsset, uint128 minDeltaQuote) = portfolio.getLiquidityDeltas(poolId, int128(liquidity));

The following example shows how to allocate and deallocate funds:

// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;
 
import "portfolio/interfaces/IPortfolio.sol";
import "portfolio/interfaces/IERC20.sol";
 
contract DeallocateExample {
    IPortfolio public portfolio;
    address public asset;
    address public quote;
 
    constructor(address portfolio_, address asset_, address quote_) {
        portfolio = IPortfolio(portfolio_);
        asset = asset_;
        quote = quote_;
    }
 
    function deallocate() external {
        // Assuming we want to allocate into the pool `1099511627777`:
        uint64 poolId = 1099511627777;
 
        // Let's check how many tokens we have.
        uint256 assetBalance = IERC20(asset).balanceOf(address(this));
        uint256 quoteBalance = IERC20(quote).balanceOf(address(this));
 
        // Now we check the maximum amount of liquidity we can get from our
        // current token balances.
        uint128 maxLiquidity =
            portfolio.getMaxLiquidity(poolId, assetBalance, quoteBalance);
 
        // Let's use this liquidity amount to get some more precise deltas.
        (uint128 deltaAsset, uint128 deltaQuote) =
            portfolio.getLiquidityDeltas(poolId, int128(maxLiquidity));
 
        // We allow the portfolio contract to move our tokens.
        IERC20(asset).approve(address(portfolio), deltaAsset);
        IERC20(quote).approve(address(portfolio), deltaQuote);
 
        // Finally, we call the `allocate` function.
        portfolio.allocate(false, poolId, maxLiquidity, deltaAsset, deltaQuote);
 
        // Since we were the first liquidity provider, a part of our liquidity
        // was burnt, we need to remove that from our calculations:
        uint128 liquidity = maxLiquidity - uint128(1e9);
 
        // To avoid being sandwiched, we can check the minimum amount of
        // tokens that we can expect from our liquidity balance.
        (uint128 minDeltaAsset, uint128 minDeltaQuote) =
            portfolio.getLiquidityDeltas(poolId, int128(liquidity));
 
        // We can now deallocate the liquidity we just allocated.
        // Note that we have to remove the burnt liquidity (1e9), since we were
        // the first to provide liquidity in this pool.
        portfolio.deallocate(false, poolId, liquidity, 0, 0);
    }
}

You can check out the full code source here (opens in a new tab).

© 2023 Primitive™