Create a custom Router
A custom Router is a smart contract which interacts with the Balancer Vault and utilizes the Vaults function in unique combinations. The deployment of a custom Router is beneficial for various projects in the DeFi space. To name some verticals this could be:
- DEX aggregators, which want to tweak the default interaction via the Balancer Router in a certain way to deliver best prices to their users.
- DeFi projects wanting to provide seamless liquidity migrations of their users from various Dexes to Balancer in order to participate from the deep liquidity offered on Balancer
- DeFi projects looking to provide liquidity on Balancer in custom proportions across multiple pools with more granular control metrics as part of the liquidity provisioning
- DeFi projects looking to enhance the liquidity mining experience for LPs by introducing a better staking and migration flow.
The main work custom routers in the outlined examples above have in common is that:
- They utilize multiple Vault interactions based on the required use-case
- They add additional control flows and external interactions to the Router smart contract for granular liquidity operations
Usage
The following custom Router displays how LPs can migrate pool liquidity to a new pool with the same tokens and stake it in a gauge without the need to transfer tokens except the BPT.
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.4;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol";
import {
RemoveLiquidityParams,
RemoveLiquidityKind,
AddLiquidityKind,
SwapKind,
AddLiquidityParams
} from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol";
interface IMockLiquidityGauge {
function deposit(uint256 value, address forWhom, bool willClaim) external;
}
/**
* @title MigrationRouter
* @notice Router for migrating liquidity from one pool to another and staking the new BPT
* @dev This contract utilizes proportional remove liquidity and unbalanced add liquidity to ensure
* accrued credit on withdrawal is perfectly canceled out via debt accrued on the add liquidity operation, ensuring no
* ERC20 Tokens (Except the BPT) need to be transferred.
*/
contract MigrationRouter {
IVault private immutable _vault;
constructor(address vault) {
_vault = IVault(vault);
}
/**
* @param poolToExit the pool the LP removes liquidity from
* @param exactAmountsInOfExit exact amount of BPT to burn for share of the pool's tokens.
* @param minAmountsOutOfExit minimum amount of tokens to receive
* @param minBptAmountOutOfJoin minimum amount of BPT to receive for the pool to be joined
* @param poolToJoin address of the pool to join
* @param gaugeToStakeIn address of the gauge to stake in
* @param sender address of the user
*/
function migrate8020PoolAndStake(
address poolToExit,
uint256 exactAmountsInOfExit, //BptIn
uint256[] calldata minAmountsOutOfExit, //tokensOut
uint256 minBptAmountOutOfJoin,
address poolToJoin,
address gaugeToStakeIn,
address sender
) external {
_vault.lock(
abi.encodeWithSignature(
"migrate8020PoolAndStakeHook(address,uint256,uint256[],uint256,address,address,address)",
poolToExit,
exactAmountsInOfExit,
minAmountsOutOfExit,
minBptAmountOutOfJoin,
poolToJoin,
gaugeToStakeIn,
sender
)
);
}
/**
* @param poolToExit the pool the LP removes liquidity from
* @param exactAmountsInOfExit exact amount of BPT to burn for share of the pool's tokens.
* @param minAmountsOutOfExit minimum amount of tokens to receive
* @param minBptAmountOutOfJoin minimum amount of BPT to receive for the pool to be joined
* @param poolToJoin address of the pool to join
* @param gaugeToStakeIn address of the gauge to stake in
* @param sender address of the user
*/
function migrate8020PoolAndStakeHook(
address poolToExit,
uint256 exactAmountsInOfExit,
uint256[] calldata minAmountsOutOfExit,
uint256 minBptAmountOutOfJoin,
address poolToJoin,
address gaugeToStakeIn,
address sender
) external {
// user already has BPT in possession as it was unstaked previously
// removeLiquidity
RemoveLiquidityParams memory params = RemoveLiquidityParams({
pool: poolToExit,
from: sender,
maxBptAmountIn: exactAmountsInOfExit,
minAmountsOut: minAmountsOutOfExit,
kind: RemoveLiquidityKind.PROPORTIONAL,
userData: "0x"
});
(, uint256[] memory amountsOut, ) = _vault.removeLiquidity(params);
// addLiquidity
AddLiquidityParams memory addLiquidityParams = AddLiquidityParams({
pool: poolToJoin,
to: address(this),
maxAmountsIn: amountsOut,
minBptAmountOut: minBptAmountOutOfJoin,
kind: AddLiquidityKind.UNBALANCED,
userData: "0x"
});
(, uint256 bptAmountOut, ) = _vault.addLiquidity(addLiquidityParams);
// bpt has been minted to Router and Router stakes for the `sender`
IMockLiquidityGauge(gaugeToStakeIn).deposit(bptAmountOut, sender, false);
}
}