You are currently viewing How to Stake in Uniswap V3.  Part I

How to Stake in Uniswap V3. Part I

Uniswap announced its 3rd version of the swap/staking protocol. With the new model, the liquidity provision mechanism was also updated. There are no more ERC20 LP Tokens as they were replaced by ERC721 NFTs. However, most of the already staking contracts relied on the former standard, which rises the question — how do I distribute rewards for wallets that provide liquidity for my custom tokens in V3?

There is a Uniswap V3 staker contract — which does all the calculations and distributes tokens proportionally. However, currently, there is no staking UI, therefore it is quite challenging to figure out the incentive mechanism creation and NFT position staking.

This article covers Uniswap V3 contract interaction steps. By the end, the reader should know how to create incentives and store correct data for future integrations in DAPPs.

Several documents describe the Uniswap V3 staking solution to a great extent (linked in the references). Still, there were some new issues to solve and relevant information to find out. I decided to write simple instructions with real transactions and examples that included everything I needed to create the incentive in one place. Hope it helps 🙂

Testnet of choice: Goerli

Main topics:

  • Main data/contract introduction
  • Step 1. Pool creation
  • Step 2. Incentive creation
  • Step 3. Staking
  • Step 4. Unstaking and Claiming Rewards

UNISWAP Staking Steps

There are several contracts to be acquainted with before proceeding:

  1. Uniswap V3 ERC721 (NFT position manager) contract: 0xc364…11fe88
    Used to transfer/stake tokens.
  2. Uniswap V3 staker contract: 0xe34…c6fE65
    Used for creation, staking / unstaking, and withdrawing.

An exemplary data:

  1. Token 0: 0xbfb…6a8c69
  2. Token 1: ETH (native currency)
  3. Reward token: 0xFe0f…54623f

For the sake of clarity — in this example, token 0 and reward token are named: PIZZA and SALADrespectively.

1. Pool Creation

If you already have the Uniswap V3 pool (or position) — you can skip this section and proceed to incentive creation. Just make sure to know the pool address.

Before creating any staking program — the user has to initialize the pool by creating the first position in the Uniswap V3 app.

The NFT position replaces LP token logic and acts as proof of liquidity provision —read more in Uniswap V3 blog).

High-level schema of Uniswap V3 pool creation logic

After pool creation — everything from the regular user’s perspective is the same. The user provides PIZZA and ETH tokens, then the Uniswap contract deposits them into the pool and creates a new position, ready for staking.

High-level schema of Uniswap V3 liquidity provision logic

Result:

  1. PIZZA / ETH Pool Address: 0xb97a…d171e1.
    The pool address is stored in event logs and the factory contract.
  2. PIZZA / ETH Position: tokenID — 33332. You can check it out in OpenSea.

2. Incentive creation

High-level schema of Uniswap V3 staking incentive creation logic

Before creating an incentive – approve the V3 stake contract to accept reward tokens!

SALAD ERC20 contract “approve” function parameters:

spender: 0xe34139463bA50bD61336E0c446Bd8C0867c6fE65 //(staker)
amount: 1000000000000000000000 //(1000 SALAD)

Now moving on to incentive creations. There are several properties in the staking incentive object (with current examples):

  • Reward token address
    Example: 0xFe0f…54623f (SALAD)
  • Start date / End date — timestamp without milliseconds. You can see the conversion example in the epoch converter.
    Example: 1663607753, 1666022484
  • Pool address
    Example: 0xb97a… d171e1 (PIZZA/ETH).
  • Refundee — address of the owner that will receive all non-distributed reward tokens after the incentive period ends.
    Example: 0xa3b8…ba2e5f

Now form those values ​​in the tuple format:

["Reward token ", "pool address", start date, end date, "refundee"]

In this case, the incentive key tube is:

Key: ["0xFe0fA96d3F01315cFf559B5F06Ac4f58A254623f","0xb97a651a6d0c164d4355a61f7fb9a43a32d171e1", 1663607753, 1666022484, "0xa3b8EAaB1eaF1E6DEAB42b864C93C70E2Eba2e5f"]

Another field is the reward token amount. Make sure to pass the absolute value. For example: 1000 tokens = 1000 * 10 ** 18. You can try the conversion here.

Reward: 1000000000000000000000

Now we can proceed to the createIncentivefunction:

Values ​​passed to etherscan

Some reasons for possible errors:

  • The pool address is incorrect;
  • The start date is in the past incentive;
  • Reward tokens are not approved;
  • The period is too long (maximum incentive duration is 63072000 seconds or 730 days).

Successful transaction example — 0x0247…e29a2e.

Incentive keys:

After creating the incentive, the owner should save/encode the incentive key tuple. Safe transfer functions or views use encoded key values.

The incentive key tube to encode is:

["0xFe0fA96d3F01315cFf559B5F06Ac4f58A254623f","0xb97a651a6d0c164d4355a61f7fb9a43a32d171e1", 1663607753, 1666022484, "0xa3b8EAaB1eaF1E6DEAB42b864C93C70E2Eba2e5f"]

There is a contract example in Mark Curchin’s blog , which I used as a base for computing the required incentive hash. Updated functions:

  • Calculating method for the unhashed incentive key;
  • Decoding method to retrieve the tuple (for self-check).

You can copy and paste it in Remix IDE (deploy it in local javascript VM) and check the values:

// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity ^0.8.0;
pragma abicoder v2;
// Referenced from: https://holdex.io/c/learn/how-to-stake-tokens-with-uniswap-v3-staking-program
contract EncodeIncentivesUniV3 {
// Used for encoding/decoding
struct IncentiveKey {
address rewardToken;
address pool;
uint256 startTime;
uint256 endTime;
address refundee;
}
// Return value used for staking via safeTransfer in the "NFT position manager" contract.
function computeUnhashedKey(IncentiveKey memory key) public pure returns (bytes memory) {
return abi.encode(key);
}
// Return value used for staking view functions in "Uniswap V3 staker" contract.
function compute(IncentiveKey memory key) public pure returns (bytes32 incentiveId) {
return keccak256(computeUnhashedKey(key));
}
// Decoding unhashed key.
function decode(bytes memory data) public pure returns (IncentiveKey memory) {
return abi.decode(data, (IncentiveKey));
}
}

Example in remix:

Result:

  • Unhashed incentive key will use this as data for staking via the NFT position manager:
0x000000000000000000000000fe0fa96d3f01315cff559b5f06ac4f58a254623f000000000000000000000000b97a651a6d0c164d4355a61f7fb9a43a32d171e1000000000000000000000000000000000000000000000000000000006328a3c900000000000000000000000000000000000000000000000000000000634d7c54000000000000000000000000a3b8eaab1eaf1e6deab42b864c93c70e2eba2e5f
  • Incentive key: will be used for views:
0x04e5c56d3f7dd9987947c7d3c9a1ab4302ec85f4440fa26ba09ad8cc8c72721a
  • Decoded unhashed incentive key: for self-check 🙂
tuple(address,address,uint256,uint256,address): 0xFe0fA96d3F01315cFf559B5F06Ac4f58A254623f,
0xb97a651A6d0C164d4355A61f7FB9a43a32d171E1,
1663607753,
1666022484,
0xa3b8EAaB1eaF1E6DEAB42b864C93C70E2Eba2e5f

3. Staking

Users with one NFT position can stake in many incentives for the same pool (if they exist). That is why sometimes users can unstake tokens from the staking incentive and leave them in the staker contract.

One position staked in multiple incentives

However, for simplicity, in this example, we cover only 1 to 1 staking (one position in one incentive).

Now, the staking can happen using either staker or NFT positions manager. However, it is easier with the latter (fewer steps due to implemented hook in the staker contract):

High-level schema of Uniswap staking from user’s perspective

general idea:

1. Approve NFT position token:

to: 0xe34139463bA50bD61336E0c446Bd8C0867c6fE65 //(staker)
tokenId: 33332 //(position created after putting PIZZA/ETH tokens)

2. Use the safeTransferFromfunction to transfer the token. Pass the unhashed incentive keyto the contract so it would stake it automatically). Main values:

from: 0xa3b8EAaB1eaF1E6DEAB42b864C93C70E2Eba2e5f //(owner of token)
to: 0xe34139463bA50bD61336E0c446Bd8C0867c6fE65 //(staker)
tokenId: 33332 //(position created after putting PIZZA/ETH tokens)
_data: 0x000000000000000000000000fe0fa96d3f01315cff559b5f06ac4f58a254623f000000000000000000000000b97a651a6d0c164d4355a61f7fb9a43a32d171e1000000000000000000000000000000000000000000000000000000006328a3c900000000000000000000000000000000000000000000000000000000634d7c54000000000000000000000000a3b8eaab1eaf1e6deab42b864c93c70e2eba2e5f
// unhashed incentive key

Example:

Etherscan example of safeTransferFrom function

To confirm staking success and see preliminary reward information, call the getRewardInfo function (pass tuple incentive id ):

However, users can claim the rewards only after the token is unstaked.

The number of staked tokens in this particular incentive can be checked in the incentives view (by passing hashed incentive id ):

4. Unstaking and Claiming Rewards

Users can unstake tokens, but the contract will not automatically transfer NFT back to the owner (unless it is unsticked via client-side using a multicall function. More about this will be in Part II).

All unstaking / withdrawing steps:

  1. unstakeToken by passing incentive tuple:

2. claim rewards by passing reward token address and amount (put 0 to retrieve all accumulated tokens).

3. Withdraw the token:

That is it 🙂

The article aimed to introduce Uniswap V3 staking logic and interacting components before proceeding with the next part of front-end / web3 integration.

If you have any questions, feel free to ask 🙂

And make sure to check out mentioned articles (linked below).

References

  1. Uniswap V3 blog: https://uniswap.org/blog/uniswap-v3;
  2. Uniswap V3 documentation:https://docs.uniswap.org/protocol/reference/periphery/staker/UniswapV3Stacker
  3. Mark Curchin Uniswap V3 staking article:https://holdex.io/c/learn/how-to-stake-tokens-with-uniswap-v3-staking-program

Special thanks to Justinas for the help 🙂

New to trading? Try crypto trading bots or copy trading


How to Stake in Uniswap V3. Part I was originally published in Coinmonks on Medium, where people are continuing the conversation by highlighting and responding to this story.

Leave a Reply