Creating a unique smart contract that incentivizes liquidity providers to earn decent liquidity requires a creative approach. One concept is to design a contract that offers rewards not only in the form of trading fees but also additional incentives like staking rewards, governance tokens, or even NFT rewards.
Here’s a basic framework for a "Liquidity Incentive Contract" that offers multiple layers of rewards:
- Dual Reward System: Liquidity providers earn rewards in both the base token and a governance token.
- Staking Boost: Liquidity providers can stake their LP tokens to earn higher rewards.
- NFT Rewards: Milestone-based rewards in the form of NFTs for top liquidity providers.
- Flexible Withdrawal: Allows liquidity providers to withdraw their funds anytime but penalizes early withdrawals by reducing rewards.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@uniswap/v2-periphery/contracts/interfaces/IUniswapV2Router02.sol";
import "@uniswap/v2-core/contracts/interfaces/IUniswapV2Factory.sol";
contract LiquidityIncentiveContract is Ownable {
using SafeERC20 for ERC20;
IUniswapV2Router02 public uniswapRouter;
address public lpToken;
address public rewardToken;
address public governanceToken;
struct Provider {
uint256 stakedAmount;
uint256 rewards;
uint256 lastUpdated;
}
mapping(address => Provider) public providers;
uint256 public totalStaked;
uint256 public rewardRate = 10; // 10% annual reward rate
uint256 public governanceRewardRate = 5; // 5% annual governance reward rate
ERC721 public nftReward; // NFT reward for milestones
event Stake(address indexed user, uint256 amount);
event Withdraw(address indexed user, uint256 amount, uint256 reward);
constructor(
address _router,
address _rewardToken,
address _governanceToken,
ERC721 _nftReward
) {
uniswapRouter = IUniswapV2Router02(_router);
rewardToken = _rewardToken;
governanceToken = _governanceToken;
nftReward = _nftReward;
lpToken = IUniswapV2Factory(uniswapRouter.factory()).getPair(
rewardToken,
uniswapRouter.WETH()
);
require(lpToken != address(0), "LP token does not exist");
}
function stake(uint256 _amount) external {
require(_amount > 0, "Amount must be greater than 0");
ERC20(lpToken).safeTransferFrom(msg.sender, address(this), _amount);
updateRewards(msg.sender);
providers[msg.sender].stakedAmount += _amount;
totalStaked += _amount;
emit Stake(msg.sender, _amount);
}
function withdraw(uint256 _amount) external {
require(providers[msg.sender].stakedAmount >= _amount, "Insufficient staked amount");
updateRewards(msg.sender);
uint256 reward = providers[msg.sender].rewards;
uint256 govReward = calculateGovernanceReward(msg.sender);
providers[msg.sender].rewards = 0;
providers[msg.sender].stakedAmount -= _amount;
totalStaked -= _amount;
ERC20(lpToken).safeTransfer(msg.sender, _amount);
ERC20(rewardToken).safeTransfer(msg.sender, reward);
ERC20(governanceToken).safeTransfer(msg.sender, govReward);
emit Withdraw(msg.sender, _amount, reward + govReward);
}
function updateRewards(address _provider) internal {
Provider storage provider = providers[_provider];
if (provider.stakedAmount > 0) {
uint256 timeElapsed = block.timestamp - provider.lastUpdated;
provider.rewards += (provider.stakedAmount * rewardRate * timeElapsed) / 365 days / 100;
}
provider.lastUpdated = block.timestamp;
}
function calculateGovernanceReward(address _provider) internal view returns (uint256) {
Provider storage provider = providers[_provider];
uint256 timeElapsed = block.timestamp - provider.lastUpdated;
return (provider.stakedAmount * governanceRewardRate * timeElapsed) / 365 days / 100;
}
function claimNFTReward(uint256 milestone) external {
require(providers[msg.sender].stakedAmount >= milestone, "Milestone not reached");
nftReward.safeTransferFrom(address(this), msg.sender, milestone);
}
function setRewardRates(uint256 _rewardRate, uint256 _governanceRewardRate) external onlyOwner {
rewardRate = _rewardRate;
governanceRewardRate = _governanceRewardRate;
}
function getTotalRewards(address _provider) external view returns (uint256) {
return providers[_provider].rewards + calculateGovernanceReward(_provider);
}
receive() external payable {}
}
- Staking Mechanism: Users can stake their LP tokens to start earning rewards.
- Dual Rewards: Rewards are distributed in the form of both a base token and a governance token.
- NFT Milestones: If a user stakes above certain thresholds, they can claim NFTs as rewards.
- Reward Rates: The contract allows setting different reward rates for base and governance tokens.
- Flexible Withdrawal: Users can withdraw their staked amount and rewards at any time, making the system attractive for liquidity providers.
- Deploy the
LiquidityIncentiveContract
on a blockchain network where Uniswap V2 is available. - Ensure the
rewardToken
,governanceToken
, andnftReward
are deployed and passed as parameters during the deployment. - Adjust the reward rates based on the desired incentive model.
- You can adjust the reward rates, staking mechanics, or even integrate additional features like time-locked rewards or compound interest.
- Modify the NFT rewards logic to provide different NFTs based on milestones, like staking duration or amount.
- Properly audit and test the contract before deploying to production.
- Liquidity incentives should be well-calculated to ensure the long-term sustainability of the system.
This framework should provide a decent starting point for building a liquidity incentive program with unique features!
To create a simple and intuitive user interface (UI) for interacting with the LiquidityIncentiveContract
, you can use a combination of HTML, CSS, and JavaScript (with web3.js for interacting with the smart contract). Below is a basic example of what the UI could look like.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Liquidity Incentive</title>
<link rel="stylesheet" href="styles.css">
</head>
<body>
<div class="container">
<h1>Liquidity Incentive Program</h1>
<div class="account-info">
<label>Connected Account:</label>
<span id="account"></span>
</div>
<div class="balance-info">
<label>Your Staked LP Tokens:</label>
<span id="staked-balance">0</span>
</div>
<div class="reward-info">
<label>Your Earned Rewards:</label>
<span id="rewards-balance">0</span>
</div>
<div class="actions">
<h2>Actions</h2>
<div class="form-group">
<label for="stake-amount">Stake LP Tokens:</label>
<input type="number" id="stake-amount" placeholder="Enter amount">
<button onclick="stakeTokens()">Stake</button>
</div>
<div class="form-group">
<label for="withdraw-amount">Withdraw LP Tokens:</label>
<input type="number" id="withdraw-amount" placeholder="Enter amount">
<button onclick="withdrawTokens()">Withdraw</button>
</div>
<button onclick="claimRewards()">Claim Rewards</button>
<button onclick="claimNFT()">Claim NFT Reward</button>
</div>
<div id="status" class="status"></div>
</div>
<script src="https://cdn.jsdelivr.net/gh/ethereum/web3.js/dist/web3.min.js"></script>
<script src="script.js"></script>
</body>
</html>
body {
font-family: Arial, sans-serif;
background-color: #f4f4f4;
color: #333;
margin: 0;
padding: 20px;
}
.container {
max-width: 600px;
margin: auto;
padding: 20px;
background-color: #fff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
}
h1 {
text-align: center;
color: #007bff;
}
h2 {
color: #555;
}
.form-group {
margin-bottom: 20px;
}
label {
font-weight: bold;
}
input[type="number"] {
width: calc(100% - 120px);
padding: 10px;
margin-right: 10px;
border: 1px solid #ccc;
border-radius: 4px;
}
button {
padding: 10px 20px;
background-color: #007bff;
color: #fff;
border: none;
border-radius: 4px;
cursor: pointer;
}
button:hover {
background-color: #0056b3;
}
.status {
margin-top: 20px;
font-weight: bold;
color: green;
}
const contractAddress = "YOUR_CONTRACT_ADDRESS";
const contractABI = [
// Add your contract ABI here
];
let web3;
let contract;
let account;
async function loadWeb3() {
if (window.ethereum) {
web3 = new Web3(window.ethereum);
await ethereum.request({ method: 'eth_requestAccounts' });
} else {
alert("Please install MetaMask to use this DApp!");
}
}
async function loadContract() {
contract = new web3.eth.Contract(contractABI, contractAddress);
const accounts = await web3.eth.getAccounts();
account = accounts[0];
document.getElementById("account").innerText = account;
updateUI();
}
async function updateUI() {
const stakedBalance = await contract.methods.providers(account).call();
document.getElementById("staked-balance").innerText = web3.utils.fromWei(stakedBalance.stakedAmount, 'ether');
const rewards = await contract.methods.getTotalRewards(account).call();
document.getElementById("rewards-balance").innerText = web3.utils.fromWei(rewards, 'ether');
}
async function stakeTokens() {
const amount = document.getElementById("stake-amount").value;
if (amount > 0) {
const weiAmount = web3.utils.toWei(amount, 'ether');
await contract.methods.stake(weiAmount).send({ from: account });
document.getElementById("status").innerText = "Stake successful!";
updateUI();
} else {
document.getElementById("status").innerText = "Please enter a valid amount!";
}
}
async function withdrawTokens() {
const amount = document.getElementById("withdraw-amount").value;
if (amount > 0) {
const weiAmount = web3.utils.toWei(amount, 'ether');
await contract.methods.withdraw(weiAmount).send({ from: account });
document.getElementById("status").innerText = "Withdraw successful!";
updateUI();
} else {
document.getElementById("status").innerText = "Please enter a valid amount!";
}
}
async function claimRewards() {
await contract.methods.claimRewards().send({ from: account });
document.getElementById("status").innerText = "Rewards claimed!";
updateUI();
}
async function claimNFT() {
await contract.methods.claimNFTReward(1).send({ from: account }); // Change milestone as needed
document.getElementById("status").innerText = "NFT claimed!";
}
window.onload = async () => {
await loadWeb3();
await loadContract();
};
- Load Web3: The
loadWeb3
function connects to the Ethereum network using MetaMask. - Load Contract: The
loadContract
function initializes the contract instance and updates the UI with the user’s staked balance and rewards. - Stake/Withdraw Tokens: Functions to stake and withdraw tokens, updating the UI and displaying status messages.
- Claim Rewards/NFT: Functions to claim rewards and NFTs.
- Update UI: The
updateUI
function refreshes the displayed information whenever an action is performed.
- Replace
YOUR_CONTRACT_ADDRESS
with the deployed contract address. - Add your contract ABI in the
contractABI
array in the JavaScript file. - Host the files on a static server or open the
index.html
file directly in a browser with MetaMask installed.
This UI should be a good starting point for interacting with your smart contract and providing an intuitive experience for liquidity providers.