How to Stake in Uniswap V3. Part I

By akohad Oct18,2022

[ad_1]

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 deployed 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

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 incentive 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 SALAD, respectively.

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 staker contract to accept reward tokens!

SALAD ERC20 contract “approve” function parameters:

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

Now moving on to incentive creation. 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 tuple 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 createIncentive function:

Values passed to etherscan

Some reasons for possible errors:

  • The pool address is incorrect;
  • The incentive start date is in the past;
  • Reward tokens are not approved;
  • The incentive 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 tuple 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-programcontract 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 safeTransferFrom function to transfer the token. Pass the unhashed incentive key to 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):

[ad_2]

Source link

By akohad

Related Post

Leave a Reply

Your email address will not be published. Required fields are marked *