Power Token Liquidity Mining for Developers

Using the Power Token concept for your own liquidity rewards


Below documentation and code it refers to; are provided under the BSD-3-Clause License.


The below documentation's goal is to give you an overview of how to work with the Power Tokens so that you can learn how they are used in the IPOR ecosystem and draw inspiration for your project. If something is unclear to you, contact the maintaining community via discord. Most current community links can be found on the project's website: https://www.ipor.io.


Power Token is built for modularity. The structure is based on the Power Token, which acts as a foundation and from where the user's Power Token balance can be delegated to modules built on top.

The first component described below is Liquidity Mining. It is a module allowing the distribution of the Power Token to the stakes of a platform's liquidity token (or any other token for that matter) and the Power Token. Abstracting the logic of Power Token use from the Power Token balance management allows for greater flexibility and future growth of Power Token applications within your protocol ecosystem.

Details about the implementation of the IPOR LIquidity mining example can be found in the Liquidity mining

Forking and working with the repository

Repository: https://github.com/IPOR-Labs/ipor-power-tokens


Until now, the Power Tokens and Liquidity Mining module have been audited by Zokyo and Ackee, and reports can be found in the audits section.


The model relies on a governance token staked into the Power Token contract. Once the user stakes his governance tokens (in the case of IPOR, that would be the IPOR token), he receives a balance of Power Tokens denominated to the underlying token at a 1:1 ratio.

FUNCTIONS used for staking

Non-transferability and delegation

Once staked, Power Tokens, although not transferable, can be delegated. Delegating is assigning part of one's Power Token balance to a particular staking pool.


function delegateToLiquidityMining(
    address[] calldata ipTokens,
    uint256[] calldata pwIporAmounts
) external;

or you can delegate and stake ipTokens in one function call:

function delegateAndStakeToLiquidityMining(
    address[] calldata ipTokens,
    uint256[] calldata pwIporAmounts,
    uint256[] calldata ipTokenAmounts
) external;

When delegated, another contract can manage the delegated balance however it sees fit. In liquidity mining, delegated balance is used to calculate rewards in various staking pools.

Adding tokens to the PowerToken balance externally.

Power Tokens are convertible to the Governance Tokens at a 1:1 ratio. However, there is a way to transfer additional tokens to the Power Token contract without minting any new Power Tokens. Such action will result in all current Power Tokens holders seeing a slightly higher Power Tokens balance as the additional underlying tokens would be distributed proportionally to all Power Tokens holders. This could be done if you would like to distribute Governance tokens to all holders of Power Tokens.

To make such action simple ERC20 transfer of the Governance Tokens has to be made to the address of the Power Token contract. Such transfer will automatically be reflected in the balances of Power Token holders.

In Power Token, the redemption fee (see below) is an example of using this functionality.

Redemption (fee, cool-off)

The Power Token contract allows for two kinds of redemption of Power Tokens for the underlying Governance Tokens, which can be tuned to the project's needs:

  • Immediate withdrawal with a fee

  • No-fee, 2-step redemption with a cool-down period.

If a staker redeems his Power Tokens for Governance Tokens with a fee, that fee is automatically redistributed to the remaining holders of Power Tokens.

function unstake(uint256 pwTokenAmount) external;

Suppose they decide to go with the cool-down period. In that case, first, they need to request the redemption by calling the coolDown function and executing the final redemption function after a predefined cool-down period.

function coolDown(uint256 pwTokenAmount) external;
function redeem() external;

To redeem the tokens, they must be undelegated from any module they have previously delegated to.

Canceling the cooldown is possible by calling the function

function cancelCooldown()


The admin can configure both the cool-down period and the immediate redemption fee. Fee can be set dynamically by calling a function:

function setUnstakeWithoutCooldownFee(uint256 unstakeWithoutCooldownFee) external;

The cooldown period can be set by changing the constant:

uint256 public constant COOL_DOWN_IN_SECONDS = 2 * 7 * 24 * 60 * 60;

Staking pools

Fist component built on top of Power Tokens in the above repository is a LiquidityMining.sol contract. As an admin, you can appoint tokens that would be used for the pools in conjunction with the Power Token.


function addLpToken(address lpToken) external;

If you would like to deactivate the pool, you to call the function:

function removeLpToken(address lpToken) external;

After the pool has been deactivated, no user can stake liquidity tokens or delegate Power Tokens. From this point onwards, stakers can only remove their tokens from the pool. The administrator should also set rewards to 0 prior to deactivating the staking pool.


As a contract administrator, you can manage the rate at which the tokens are issued to the participants in the Liquidity Mining program. Rewards are calculated in Power Token (staked version of Governance Token) on every block when participants are staking their tokens. In case when there are no users, rewards are not issued.

How to set the rewards


function setRewardsPerBlock(address lpToken, uint32 pwTokenAmount) external;

How to change the rewards

Changing rewards can be done at any time. Executing function setRewardsPerBlock will trigger rebalancing of the GlobalRewardsIndicators struct, which changes the rate of rewards issued.

Rewards balance

To supply the rewards to the rewards contract, you have to make an ERC20 transfer of Governance token to the balance of the liquidityMining contract.

The power Token Liquidity Mining contract does not mandate setting a fixed duration of the rewards period. It is up to the administrator to keep issuing or stop issuing the rewards. It is important, however, to ensure enough governance tokens to cover the rewards that liquidity mining participants can claim. As the administrator, you can top up this balance when you see it appropriate. This approach allows you not to keep all the balance in the rewards contract but rather feed the tokens periodically from the cold wallet.

For example, if you're planning to run liquidity mining for 3 years and keep issuing 1 token per block for a given block, it will mean that you will issue 7.884.000 (assuming 12-second blocks). You can either deposit 7.884.000 tokens upfront or split them into tranches and deposit them every so often. If you decide to deposit quarterly (12*657.000), you could follow this schedule:

time of depositamount

at the beginning


by the end of 1st quarter




by the end of last quarter


What if the rewards balance is not enough?

As mentioned above, ensuring sufficient tokens to cover rewards is essential. If the administrator does not provide sufficient rewards, and all rewards are withdrawn to prevent the staked Power Tokens from being withdrawn as "rewards" by users, the Liquidity Mining contract would prevent any reward claiming. The stakers, at this point, would not be able to use standard ways to unstake their liquidity tokens. Instead, they would be able to use unstakeAndAllocatepwTokens. By doing that, stakers can retrieve their liquidity tokens, enable undelegating Power Tokens and set aside the calculated rewards until they can be claimed later when the contract administrator makes the overdue transfer.

  • function unstakeAndAllocatePwTokens(address lpToken, uint256 lpTokenAmount) external;
  • function claimAllocatedPwTokens() external;

Adjusting the Power Up curve

Staker's power-up depends on the ratio between LP Tokens and Power Tokens they have in a particular staking pool. The exact value is drawn on the logarithmic curve defined in the "math section."

The function can be modified to suit particular project needs at inception but can also be modified when the project is running. It's important to note that the user's power-up will only be updated when it interacts with the mining contract, such as claim, stake, unstake etc.

Although the power-up function can be overridden completely, the logarithmic model has several advantages described in "Tokenomics." It can be easily adjusted by modifying horizontal and vertical shift constants:

/// @notice Gets Horizontal shift param used in Liquidity Mining equations.
/// @dev To pre-calculate this value from uint256, use {MiningCalculation._toQuadruplePrecision()} method.
/// @dev 0.5 = ABDKMathQuad.div(ABDKMathQuad.fromUInt(5), ABDKMathQuad.fromUInt(10))
/// @dev Notice! uint256 value before calculation has the following constraints: 0.5 <= Horizontal Shift <= 10^3
/// @return horizontal shift - value represented in bytes16, quadruple precision, 128 bits, it takes into consideration 18 decimals
function _getHorizontalShift() internal pure virtual returns (bytes16) {
    return 0x3ffe0000000000000000000000000000;

/// @notice Gets vertical shift param used in Liquidity Mining equations.
/// @dev To pre-calculate this value from uint256, use {MiningCalculation._toQuadruplePrecision()} method.
/// @dev 1.4 = ABDKMathQuad.div(ABDKMathQuad.fromUInt(14), ABDKMathQuad.fromUInt(10))
/// @dev Notice! uint256 value before calculation has following constraints: 10^(-4) <= Vertical Shift <= 3
/// @return vertical shift - value represented in bytes16, quadruple precision, 128 bits, it takes into consideration 18 decimals
function _getVerticalShift() internal pure virtual returns (bytes16) {
    return 0x3fff6666666666666666666666666666;


function calculateAccountPowerUp(
        uint256 accountPwTokenAmount,
        uint256 accountLpTokenAmount,
        bytes16 verticalShift,
        bytes16 horizontalShift

Rebalancing users

function updateIndicators(
    address account, 
    address[] calldata lpTokens) external override nonReentrant whenNotPaused

This function can be used by the user can rebalance their own position if they think that they could take advantage of a higher power-up, or other users can rebalance each other if they think that would lead to a particular user getting more adequate rewards hence freeing rewards for other users.

Contracts administration

  • Admin-only functions

    • Power Token

      • appoint liquidity mining contract address

      • set PauseManager

      • transfer ownership (for example, time lock controller)

      • renounce ownership

    • Liquidity Mining

      • function setRewardsPerBlock(address ipToken, uint32 iporTokenAmount)
        function addIpTokenAsset(address ipToken) external
        function removeIpTokenAsset(address ipToken) external
        function setPauseManager(address newPauseManagerAddr) external
        • renounce ownership

        • transfer ownership (for example, time lock controller)

        • set PauseManager

  • Pause Manager

    • pause/unpause

Pasuable Liquidity Mining

When the Liquidity Mining contract is paused, the following functions are affected.

  • Staking and unstacking of liquidity tokens is not possible

  • Claiming rewards

  • Rewards are still calculated if, as a contract administrator, you wish to stop the issuance of the rewards, then before the pause, you should set rewards to 0

  • Delegating and undelegating of Power Tokens will not work on the Power Token contract.

Pausable Power Token

When Power Token is paused, the following functionalities are affected:

  • delegating and undelegaing

  • invoking cooldown function

  • canceling cooldown

  • redeeming of Power Tokens for Governance Tokens

  • Stake and unstaking of Governance Tokens

  • Liquidity mining can not issue rewards, although the rewards are calculated.

Last updated