> For the complete documentation index, see [llms.txt](https://docs.dragonswap.app/dragonswap/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.dragonswap.app/dragonswap/resources/developer-resources/smart-contracts/dragonswapv2/staker/overview.md).

# Overview

The liquidity mining staker design is comprised of one canonical position staking contract, Staker. The technical reference for this contract is [here](/dragonswap/resources/developer-resources/smart-contracts/dragonswapv2/staker/dragonswapv2staker.md) and the source code is [here](https://github.com/dragonswap-app/v2-staker).

### Data Structures[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#data-structures) <a href="#data-structures" id="data-structures"></a>

```solidity
struct Incentive {
  uint128 totalRewardUnclaimed;
  uint128 numberOfStakes;
  uint160 totalSecondsClaimedX128;
}

struct Deposit {
  address owner;
  uint96 numberOfStakes;
}

struct Stake {
  uint160 secondsPerLiquidityInsideInitialX128;
  uint128 liquidity;
}

struct IncentiveKey {
        IERC20Minimal rewardToken;
        IDragonswapV2Pool pool;
        uint256 startTime;
        uint256 endTime;
        address refundee;
}
```

State:

```solidity
IDragonswapV2Factory public immutable factory;
INonfungiblePositionManager public immutable nonfungiblePositionManager;

/// @dev bytes32 refers to the return value of IncentiveId.compute
mapping(bytes32 => Incentive) public incentives;

/// @dev deposits[tokenId] => Deposit
mapping(uint256 => Deposit) public deposits;

/// @dev stakes[tokenId][incentiveHash] => Stake
mapping(uint256 => mapping(bytes32 => Stake)) public stakes;

/// @dev rewards[rewardToken][msg.sender] => uint256
mapping(address => mapping(address => uint256)) public rewards;
```

Params:

```solidity
struct CreateIncentiveParams {
  address rewardToken;
  address pool;
  uint256 startTime;
  uint256 endTime;
  uint128 totalReward;
}

struct EndIncentiveParams {
  address creator;
  address rewardToken;
  address pool;
  uint256 startTime;
  uint256 endTime;
}

```

### Incentives[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#incentives) <a href="#incentives" id="incentives"></a>

#### `createIncentive(CreateIncentiveParams memory params)`[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#createincentivecreateincentiveparams-memory-params) <a href="#createincentivecreateincentiveparams-memory-params" id="createincentivecreateincentiveparams-memory-params"></a>

`createIncentive` creates a liquidity mining incentive program. The key used to look up an Incentive is the hash of its immutable properties.

**Check:**

* Incentive with these params does not already exist
* Timestamps: `params.endTime >= params.startTime`, `params.startTime >= block.timestamp`
* Incentive with this ID does not already exist.

**Effects:**

* Sets `incentives[key] = Incentive(totalRewardUnclaimed=totalReward, totalSecondsClaimedX128=0, rewardToken=rewardToken)`

**Interaction:**

* Transfers `params.totalReward` from `msg.sender` to self.
  * Make sure there is a check here and it fails if the transfer fails
* Emits `IncentiveCreated`

#### `endIncentive(EndIncentiveParams memory params)`[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#endincentiveendincentiveparams-memory-params) <a href="#endincentiveendincentiveparams-memory-params" id="endincentiveendincentiveparams-memory-params"></a>

`endIncentive` can be called by anyone to end an Incentive after the `endTime` has passed, transferring `totalRewardUnclaimed` of `rewardToken` back to `refundee`.

**Check:**

* `block.timestamp > params.endTime`
* Incentive exists (`incentive.totalRewardUnclaimed != 0`)

**Effects:**

* deletes `incentives[key]` (This is a new change)

**Interactions:**

* safeTransfers `totalRewardUnclaimed` of `rewardToken` to the incentive creator `msg.sender`
* emits `IncentiveEnded`

### Deposit/Withdraw Token[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#depositwithdraw-token) <a href="#depositwithdraw-token" id="depositwithdraw-token"></a>

**Interactions**

* `nonfungiblePositionManager.safeTransferFrom(sender, this, tokenId)`
  * This transfer triggers the onERC721Received hook

#### `onERC721Received(address, address from, uint256 tokenId, bytes calldata data)`[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#onerc721receivedaddress-address-from-uint256-tokenid-bytes-calldata-data) <a href="#onerc721receivedaddress-address-from-uint256-tokenid-bytes-calldata-data" id="onerc721receivedaddress-address-from-uint256-tokenid-bytes-calldata-data"></a>

**Check:**

* Make sure sender is dragonswapV2 nft

**Effects:**

* Creates a deposit for the token setting deposit `owner` to `from`.
  * Setting `owner` to `from` ensures that the owner of the token also owns the deposit. Approved addresses and operators may first transfer the token to themselves before depositing for deposit ownership.
* If `data.length>0`, stakes the token in one or more incentives

#### `withdrawToken(uint256 tokenId, address to, bytes memory data)`[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#withdrawtokenuint256-tokenid-address-to-bytes-memory-data) <a href="#withdrawtokenuint256-tokenid-address-to-bytes-memory-data" id="withdrawtokenuint256-tokenid-address-to-bytes-memory-data"></a>

**Checks**

* Check that a Deposit exists for the token and that `msg.sender` is the `owner` on that Deposit.
* Check that `numberOfStakes` on that Deposit is 0.

**Effects**

* Delete the Deposit `delete deposits[tokenId]`.

**Interactions**

* `safeTransferFrom` the token to `to` with `data`.
* emit `DepositTransferred(token, deposit.owner, address(0))`

### Stake/Unstake/Rewards[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#stakeunstakerewards) <a href="#stakeunstakerewards" id="stakeunstakerewards"></a>

#### `stakeToken`[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#staketoken) <a href="#staketoken" id="staketoken"></a>

**Check:**

* `deposits[params.tokenId].owner == msg.sender`
* Make sure incentive actually exists and has reward that could be claimed (incentive.rewardToken != address(0))
  * Check if this check can check totalRewardUnclaimed instead
* Make sure token not already staked

#### `claimReward`[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#claimreward) <a href="#claimreward" id="claimreward"></a>

**Interactions**

* `msg.sender` to withdraw all of their reward balance in a specific token to a specified `to` address.
* emit RewardClaimed(to, reward)

#### `unstakeToken`[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#unstaketoken) <a href="#unstaketoken" id="unstaketoken"></a>

To unstake an NFT, you call `unstakeToken`, which takes all the same arguments as `stake`.

**Checks**

* It checks that you are the owner of the Deposit
* It checks that there exists a `Stake` for the provided key (with exists=true).

**Effects**

* Deletes the Stake.
* Decrements `numberOfStakes` for the Deposit by 1.
* `totalRewardsUnclaimed` is decremented by `reward`.
* `totalSecondsClaimed` is incremented by `seconds`.
* Increments `rewards[rewardToken][msg.sender]` by the amount given by `getRewardInfo`.

#### `getRewardInfo`[​](https://docs.uniswap.org/contracts/v3/reference/periphery/staker/Design#getrewardinfo) <a href="#getrewardinfo" id="getrewardinfo"></a>

* It computes `secondsInsideX128` (the total liquidity seconds for which rewards are owed) for a given Stake, by:
  * using`snapshotCumulativesInside` from the DragonswapV2 core contract to get `secondsPerLiquidityInRangeX128`, and subtracting `secondsPerLiquidityInRangeInitialX128`.
  * Multiplying that by `stake.liquidity` to get the total seconds accrued by the liquidity in that period
* Note that X128 means it's a `UQ32X128`.
* It computes `totalSecondsUnclaimed` by taking `max(endTime, block.timestamp) - startTime`, casting it as a Q128, and subtracting `totalSecondsClaimedX128`.
* It computes `rewardRate` for the Incentive casting `incentive.totalRewardUnclaimed` as a Q128, then dividing it by `totalSecondsUnclaimedX128`.
* `reward` is then calculated as `secondsInsideX128` times the `rewardRate`, scaled down to a regular uint128.
