Mint a New Position
Input Parameters
To mint a new position, we use the nonFungiblePositionManager
and call mint
.
For the sake of this example, we're hard coding the token amounts to be minted. In production, this would be a user-configurable function argument.
/// @notice Calls the mint function defined in periphery, mints the same amount of each token. For this example we are providing 1000 DAI and 1000 USDC in liquidity
/// @return tokenId The id of the newly minted ERC721
/// @return liquidity The amount of liquidity for the position
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function mintNewPosition()
external
returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
)
{
// For this example, we will provide equal amounts of liquidity in both assets.
// Providing liquidity in both assets means liquidity will be earning fees and is considered in-range.
uint256 amount0ToMint = 1000;
uint256 amount1ToMint = 1000;
Calling Mint
Here we approve the nonfungiblePositionManager
to use the contracts' tokens, then populate the MintParams
struct and assign it to a local variable params
that will be passed to the nonfungiblePositionManager
when we call mint
.
By using
TickMath.MIN_TICK
andTickMath.MAX_TICK
, we are providing liquidity across the whole range of the pool. In production you may want to specify a more concentrated position.We set
amount0Min
andamount1Min
to zero for the example - but this would be a vulnerability in production. A function callingmint
with no slippage protection would be vulnerable to a frontrunning attack designed to execute themint
call at an inaccurate price.For a more secure practice the developer would need to implement a slippage estimation process.
Note that this function will not initialize a pool where one does not yet exist.
// Approve the position manager
TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), amount0ToMint);
TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), amount1ToMint);
INonfungiblePositionManager.MintParams memory params =
INonfungiblePositionManager.MintParams({
token0: DAI,
token1: USDC,
fee: poolFee,
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
amount0Desired: amount0ToMint,
amount1Desired: amount1ToMint,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
});
// Note that the pool defined by DAI/USDC and fee tier 0.3% must already be created and initialized in order to mint
(tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params);
Updating The Deposit Mapping And Refunding The Calling Address
Now we can call the internal function we previously wrote in Setting Up Your Contract. After that, we can take any liquidity leftover from minting and refund it to msg.sender
.
// Create a deposit
_createDeposit(msg.sender, tokenId);
// Remove allowance and refund in both assets.
if (amount0 < amount0ToMint) {
TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), 0);
uint256 refund0 = amount0ToMint - amount0;
TransferHelper.safeTransfer(DAI, msg.sender, refund0);
}
if (amount1 < amount1ToMint) {
TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), 0);
uint256 refund1 = amount1ToMint - amount1;
TransferHelper.safeTransfer(USDC, msg.sender, refund1);
}
}
The Full Example
/// @notice Calls the mint function defined in periphery, mints the same amount of each token. For this example we are providing 1000 DAI and 1000 USDC in liquidity
/// @return tokenId The id of the newly minted ERC721
/// @return liquidity The amount of liquidity for the position
/// @return amount0 The amount of token0
/// @return amount1 The amount of token1
function mintNewPosition()
external
returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
)
{
// For this example, we will provide equal amounts of liquidity in both assets.
// Providing liquidity in both assets means liquidity will be earning fees and is considered in-range.
uint256 amount0ToMint = 1000;
uint256 amount1ToMint = 1000;
// Approve the position manager
TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), amount0ToMint);
TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), amount1ToMint);
INonfungiblePositionManager.MintParams memory params =
INonfungiblePositionManager.MintParams({
token0: DAI,
token1: USDC,
fee: poolFee,
tickLower: TickMath.MIN_TICK,
tickUpper: TickMath.MAX_TICK,
amount0Desired: amount0ToMint,
amount1Desired: amount1ToMint,
amount0Min: 0,
amount1Min: 0,
recipient: address(this),
deadline: block.timestamp
});
// Note that the pool defined by DAI/USDC and fee tier 0.3% must already be created and initialized in order to mint
(tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params);
// Create a deposit
_createDeposit(msg.sender, tokenId);
// Remove allowance and refund in both assets.
if (amount0 < amount0ToMint) {
TransferHelper.safeApprove(DAI, address(nonfungiblePositionManager), 0);
uint256 refund0 = amount0ToMint - amount0;
TransferHelper.safeTransfer(DAI, msg.sender, refund0);
}
if (amount1 < amount1ToMint) {
TransferHelper.safeApprove(USDC, address(nonfungiblePositionManager), 0);
uint256 refund1 = amount1ToMint - amount1;
TransferHelper.safeTransfer(USDC, msg.sender, refund1);
}
}