diff --git a/packages/connectors/contracts/liquidity/convex/ConvexConnector.sol b/packages/connectors/contracts/liquidity/convex/ConvexConnector.sol
new file mode 100644
index 00000000..91f2b008
--- /dev/null
+++ b/packages/connectors/contracts/liquidity/convex/ConvexConnector.sol
@@ -0,0 +1,104 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity ^0.8.0;
+
+import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+import '@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol';
+
+import '@mimic-fi/v3-helpers/contracts/math/FixedPoint.sol';
+
+import './ICvxPool.sol';
+import './ICvxBooster.sol';
+
+/**
+ * @title ConvexConnector
+ */
+contract ConvexConnector {
+ using FixedPoint for uint256;
+
+ // Convex booster
+ ICvxBooster public immutable booster;
+
+ /**
+ * @dev Creates a new Convex connector
+ */
+ constructor(ICvxBooster _booster) {
+ booster = _booster;
+ }
+
+ /**
+ * @dev Claims Convex pool rewards for a Curve pool
+ */
+ function claim(address pool) external returns (address[] memory tokens, uint256[] memory amounts) {
+ (, ICvxPool cvxPool) = _findCvxPoolInfo(pool);
+ IERC20 crv = IERC20(cvxPool.crv());
+
+ uint256 initialCrvBalance = crv.balanceOf(address(this));
+ cvxPool.getReward(address(this));
+ uint256 finalCrvBalance = crv.balanceOf(address(this));
+
+ amounts = new uint256[](1);
+ amounts[0] = finalCrvBalance - initialCrvBalance;
+
+ tokens = new address[](1);
+ tokens[0] = address(crv);
+ }
+
+ /**
+ * @dev Deposits Curve pool tokens into Convex
+ * @param pool Address of the Curve pool to join Convex
+ * @param amount Amount of Curve pool tokens to be deposited into Convex
+ */
+ function join(address pool, uint256 amount) external returns (uint256) {
+ if (amount == 0) return 0;
+ (uint256 poolId, ICvxPool cvxPool) = _findCvxPoolInfo(pool);
+
+ // Stake in Convex
+ uint256 initialCvxPoolTokenBalance = cvxPool.balanceOf(address(this));
+ IERC20(pool).approve(address(booster), amount);
+ require(booster.deposit(poolId, amount), 'CONVEX_BOOSTER_DEPOSIT_FAILED');
+ uint256 finalCvxPoolTokenBalance = cvxPool.balanceOf(address(this));
+ return finalCvxPoolTokenBalance - initialCvxPoolTokenBalance;
+ }
+
+ /**
+ * @dev Withdraws Curve pool tokens from Convex
+ * @param pool Address of the Curve pool to exit from Convex
+ * @param amount Amount of Convex tokens to be withdrawn
+ */
+ function exit(address pool, uint256 amount) external returns (uint256) {
+ if (amount == 0) return 0;
+ (, ICvxPool cvxPool) = _findCvxPoolInfo(pool);
+
+ // Unstake from Convex
+ uint256 initialPoolTokenBalance = IERC20(pool).balanceOf(address(this));
+ require(cvxPool.withdraw(amount, true), 'CONVEX_CVX_POOL_WITHDRAW_FAILED');
+ uint256 finalPoolTokenBalance = IERC20(pool).balanceOf(address(this));
+ return finalPoolTokenBalance - initialPoolTokenBalance;
+ }
+
+ /**
+ * @dev Finds the Convex pool information associated to the given Curve pool
+ */
+ function _findCvxPoolInfo(address pool) internal view returns (uint256 poolId, ICvxPool cvxPool) {
+ for (uint256 i = 0; i < booster.poolLength(); i++) {
+ (address lp, , address rewards, bool shutdown, ) = booster.poolInfo(i);
+ if (lp == pool && !shutdown) {
+ return (i, ICvxPool(rewards));
+ }
+ }
+ revert('CONVEX_CVX_POOL_NOT_FOUND');
+ }
+}
diff --git a/packages/connectors/contracts/liquidity/convex/ICvxBooster.sol b/packages/connectors/contracts/liquidity/convex/ICvxBooster.sol
new file mode 100644
index 00000000..d5b7cd17
--- /dev/null
+++ b/packages/connectors/contracts/liquidity/convex/ICvxBooster.sol
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity ^0.8.0;
+
+interface ICvxBooster {
+ function poolLength() external view returns (uint256);
+
+ function poolInfo(uint256 i)
+ external
+ view
+ returns (address lpToken, address gauge, address rewards, bool shutdown, address factory);
+
+ function deposit(uint256 pid, uint256 amount) external returns (bool);
+}
diff --git a/packages/connectors/contracts/liquidity/convex/ICvxPool.sol b/packages/connectors/contracts/liquidity/convex/ICvxPool.sol
new file mode 100644
index 00000000..4f98dd4f
--- /dev/null
+++ b/packages/connectors/contracts/liquidity/convex/ICvxPool.sol
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+
+pragma solidity ^0.8.0;
+
+import '@openzeppelin/contracts/token/ERC20/IERC20.sol';
+
+interface ICvxPool is IERC20 {
+ function crv() external view returns (address);
+
+ function getReward(address account) external;
+
+ function withdraw(uint256 amount, bool claim) external returns (bool);
+}
diff --git a/packages/connectors/test/liquidity/convex/ConvexConnector.arbitrum.ts b/packages/connectors/test/liquidity/convex/ConvexConnector.arbitrum.ts
new file mode 100644
index 00000000..4b985f6f
--- /dev/null
+++ b/packages/connectors/test/liquidity/convex/ConvexConnector.arbitrum.ts
@@ -0,0 +1,74 @@
+import { advanceTime, deploy, fp, impersonate, instanceAt, MONTH, toUSDC } from '@mimic-fi/v3-helpers'
+import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/dist/src/signer-with-address'
+import { expect } from 'chai'
+import { Contract } from 'ethers'
+
+/* eslint-disable no-secrets/no-secrets */
+
+const CRV = '0x11cDb42B0EB46D95f990BeDD4695A6e3fA034978'
+const POOL = '0x7f90122BF0700F9E7e1F688fe926940E8839F353'
+const CVX_POOL = '0x971E732B5c91A59AEa8aa5B0c763E6d648362CF8'
+const BOOSTER = '0xF403C135812408BFbE8713b5A23a04b3D48AAE31'
+
+const WHALE = '0xf403c135812408bfbe8713b5a23a04b3d48aae31'
+
+describe('ConvexConnector - 2CRV', function () {
+ let whale: SignerWithAddress
+ let connector: Contract, pool: Contract, cvxPool: Contract, crv: Contract
+
+ const JOIN_AMOUNT = toUSDC(100)
+
+ before('impersonate whale', async () => {
+ whale = await impersonate(WHALE, fp(10))
+ })
+
+ before('deploy connector', async () => {
+ connector = await deploy('ConvexConnector', [BOOSTER])
+ crv = await instanceAt('IERC20', CRV)
+ pool = await instanceAt('I2CrvPool', POOL)
+ cvxPool = await instanceAt('ICvxPool', CVX_POOL)
+ })
+
+ it('deploys the connector correctly', async () => {
+ expect(await connector.booster()).to.be.equal(BOOSTER)
+ })
+
+ it('joins the connector', async () => {
+ await pool.connect(whale).transfer(connector.address, JOIN_AMOUNT)
+
+ const previousPoolBalance = await pool.balanceOf(connector.address)
+ const previousCvxPoolBalance = await cvxPool.balanceOf(connector.address)
+
+ await connector.join(POOL, JOIN_AMOUNT)
+
+ const currentPoolBalance = await pool.balanceOf(connector.address)
+ expect(currentPoolBalance).to.be.equal(previousPoolBalance.sub(JOIN_AMOUNT))
+
+ const currentCvxPoolBalance = await cvxPool.balanceOf(connector.address)
+ expect(currentCvxPoolBalance).to.be.equal(previousCvxPoolBalance.add(JOIN_AMOUNT))
+ })
+
+ it('accrues rewards over time', async () => {
+ const previousCrvBalance = await crv.balanceOf(connector.address)
+
+ await advanceTime(MONTH)
+ await connector.claim(POOL)
+
+ const currentCrvBalance = await crv.balanceOf(connector.address)
+ expect(currentCrvBalance).to.be.gt(previousCrvBalance)
+ })
+
+ it('exits with a 50%', async () => {
+ const previousPoolBalance = await pool.balanceOf(connector.address)
+ const previousCvxPoolBalance = await cvxPool.balanceOf(connector.address)
+
+ const amountIn = previousCvxPoolBalance.div(2)
+ await connector.exit(POOL, amountIn)
+
+ const currentCvxPoolBalance = await cvxPool.balanceOf(connector.address)
+ expect(currentCvxPoolBalance).to.be.equal(previousCvxPoolBalance.sub(amountIn))
+
+ const currentPoolBalance = await pool.balanceOf(connector.address)
+ expect(currentPoolBalance).to.be.equal(previousPoolBalance.add(amountIn))
+ })
+})