How to Stake tokens with Uniswap V3 staking program

8

Mark Curchin • Announcement • posted in Holdex Community
Amplify Protocol, Cryptoverse, Holdex Community
how to stake tokens with uniswapv3 staking program

Previously we covered how to create a Uniswap V3 staking program and we explained the details you need to pay attention to. If you haven’t seen it yet we recommend you give it a look. Moving forward you probably asked yourself, how can your Liquidity providers (LPs) benefit from this incentive program? This time we are packed with real examples to help you get started with the Uniswap V3 staking program.

Depositing and Staking 📥

This part of the guide is directly related to your Liquidity providers (the end-users), therefore you might need to build an interface with the right functionality, so that your Liquidity providers can perform these actions themselves. For now, I will only go through the sequence of actions that a Liquidity provider might need to perform from the interface. But if you are looking for an interface, our team can setup one for you, drop us a line.

Read idle NFT positions

Idle NFT positions are the ERC721 tokens that Liquidity providers have received when they provided liquidity into your Uniswap v3 pair pool. We call them idle because they are not staked and don’t earn your users any rewards. Displaying them in the interface makes it easier for Liquidity providers to start staking in your incentive program.

To read these Idle NFT positions, you will have to read the data from the Staker contract in a multicall type of function, where multiple calls are being executed in a chain using obtained results from previous function returns.

Query the users’ total number of NFT positions from the NonfungiblePositionManager or NFT contract using the balanceOf method. This will allow you to parse a known length of IDs.

Loop over the length and get each NFT position’s ID this user owns, using the tokenOfOwnerByIndex method. The method will return the NFT position ID but will not return the information to which pool pair it belongs to.

Now, we need more detailed information about the NFT position. Query every single position's detailed information from the same contract using the positions method.

Finally, having the detailed information of every single NFT position, you can filter them out by the token0 , token1 and fee properties all together. These 3 properties can be unique only to your Uniswap v3 pool. ( You can also use them to find your pool address )

Deposit Stake

As stated in the official overview , before the user can actually Stake, he must deposit the token into the Staker contract. This will ensure that the liquidity provider won’t withdraw any liquidity while participating in the staking program. Luckily for us, both Deposit and Stake can be performed in a single transaction.

To deposit stake your NFT position you will need to call the safeTransferFrom function from the NonfungiblePositionManager contract (the NFT minter contract) and pass the following parameters:

  1. from - address of current NFT owner
  2. to - address of the Staker contract
  3. tokenId - the ID of the NFT position
  4. data - an array with the IncentiveId

When the transaction is executed, a hook function ( onERC721received ) from the Staker contract will be triggered and create a deposit and stake the tokenId from the user within the incentive program.

Unfortunately, when the incentive program is created, the Staker contract doesn’t return the IncentiveId . Therefore, you will need to implement a local function which simulates the same compute function from Uniswap Staker. Below you will find an example that you can run in Remix to get your incentiveId.

Simulate compute function to receive incentiveId

1. Create a new file IncentiveId.sol and paste the following code:

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity =0.7.6;
pragma abicoder v2;
 
struct IncentiveKey {
   address rewardToken;
   address pool;
   uint256 startTime;
   uint256 endTime;
   address refundee;
}
 
contract IncentiveId {
   /// @notice Calculate the key for a staking incentive
   /// @param key The components used to compute the incentive identifier
   /// @return incentiveId The identifier for the incentive
   function compute(IncentiveKey memory key)  public pure returns (bytes32 incentiveId) {
       return keccak256(abi.encode(key));
   }
}  

2. Build and deploy in your JavaScriptVM

3. Open the tab to interact with your contract and paste the same information you used to create your incentive program:

["REWARD_TOKEN_ADDRESS","POOL_ADDRESS",START_DATE,END_DATE,"REFUNDEE_ADDRESS"]

4. Execute transaction and you will receive your IncentiveID

example of incentiveID

Read staking information 🔍

Read staked NFT positions

Once the NFT positions have been successfully staked, we need to display them in the interface. Problem is, we don’t know what the IDs of the staked positions are. The Staker contract can’t return information about it and the Liquidity provider doesn’t own the NFT anymore either. Which means we can’t use the previous method to read this information.

The solution to go from here is to read this information from a storage. In our case, we are using The Graph subgraph node.

We’ve deployed our own subgraph to sync the staking events and read the information from there.

This is how our Position GQL schema looks like:

type Position @entity {
    id: ID!
    owner: Bytes!
    incentiveId: Bytes
    isIdle: Boolean!
    isStaked: Boolean!
    createdAt: BigInt!
}  

Below is an example of how we populate the Position data:

export function handlePositionCreate(event: Transfer): void {
    let position = Position.load(event.params.tokenId.toHex());

    if (position == null) {
        position = new Position(event.params.tokenId.toHex());

        position.owner = event.params.to;
        position.createdAt = event.block.timestamp;
        position.isStaked = false;
        position.isIdle = true;
    }

    position.save();
}  

And this is how we update Position data on every TokenStaked event:

export function handleTokenStaked(event: TokenStaked): void {
    let position = Position.load(event.params.tokenId.toHex());

    if (position != null) {
        position.isStaked = true;

        position.incentiveId = event.params.incentiveId;

        position.save();
    }
}
  

From here, you should be able to query the information about the NFT positions using GraphQL and filter them by owner (users’ wallet), incentiveId and status isStaked .

Read accrued rewards

Accrued rewards are the tokens the Liquidity provider will receive from the incentive program for staking. They accrue in the Staker contract and the user will have to claim them later. Since we already know the IDs of the staked NFT positions of a user from the previous step, we can successfully query the amount of accrued rewards and display that in the interface.

We’ll need to call the method getRewardInfo from the Staker contract and pass the incentiveTuple and tokenId as parameters. You can loop this method over all NFT positions owned by our user. The reward amount of the position returned by the method will be in Wei format, so you will need to convert that into human-readable format.

Claim rewards ⚡️

Unstake token withdraw

Before your user can claim the accrued rewards, they will need to unstake and withdraw the NFT position. In the Staker contract, these methods are separate functions without the possibility to pass data attribute in order to trigger the other function. It is important for the user to withdraw right after unstaking their positions because otherwise he won’t be able to get back his liquidity from the Uniswap pool.

To solve this challenge, Uniswap offers their own multicall method from the Staker contract that we can use to trigger chained write transactions. The first command we need to perform is unstakeToken and pass the incentiveTuple and tokenId , followed by withdrawToken where we pass the tokenId , to ( users’ Address ) and data ( incentiveId ).

Multicall method will accept only Uint8Array format and in order to pass the previous transactions into data , you will need to arrayify these transactions. An example of implementation can be found below:

let stakerContract = useStakerContract(store, true);

    let incentiveTuple = [
        configInfo.token,
        configInfo.pool,
        configInfo.incentiveStart,
        configInfo.incentiveEnd,
        configInfo.refundee
    ];

    let calls = [
        ethers.utils.arrayify(encodeUnstakeFn(stakerContract, incentiveTuple, tokenId)),
        ethers.utils.arrayify(encodeWithdrawFn(stakerContract, tokenId, account))
    ];

    return stakerContract.functions["multicall(bytes[])"](calls);
}


function encodeUnstakeFn(stakerContract: Contract, incentiveTuple: any[], tokenId: string) {
    return stakerContract.interface.encodeFunctionData("unstakeToken", [incentiveTuple, tokenId])
}

function encodeWithdrawFn(stakerContract: Contract, tokenId: string, account: string) {
    return stakerContract.interface.encodeFunctionData("withdrawToken", [tokenId, account, []])
}  

Read claimable rewards

In the Staker contract, claimable rewards of a Liquidity provider are queried using the rewards method. Pass the addresses of your rewardToken and the liquidityProvider , and the contract will return the total number of tokens.

Claim

FInally, when the Liquidity provider has unstaken and withdrawn the NFT position, he will be able to claim the rewards. To perform a claim, we must call the claimReward method from the Staker contract and pass the rewardToken address, to ( liquidity provider address ) and the amountRequested in Wei. Passing the amountRequested as 0, will claim all the available amount of tokens.

Summary 🤓

If you followed the instruction from our guide, you now have a dApp to run a liquidity mining campaign and incentivise your LPs with juicy rewards. We need to point out that this is not a beginners guide, so, you are looking for a dApp like this, we are here to help, get in touch and our team will help with the setup.

Related articles:

8

Reply
Popular In order Chat mode