Skip to content

Instantly share code, notes, and snippets.

@shopglobal
Last active August 21, 2024 22:35
Show Gist options
  • Save shopglobal/8110861c8a226c0dc745aa8c2c8502f1 to your computer and use it in GitHub Desktop.
Save shopglobal/8110861c8a226c0dc745aa8c2c8502f1 to your computer and use it in GitHub Desktop.
Dual Layer rewards mechanics

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:

Key Features:

  1. Dual Reward System: Liquidity providers earn rewards in both the base token and a governance token.
  2. Staking Boost: Liquidity providers can stake their LP tokens to earn higher rewards.
  3. NFT Rewards: Milestone-based rewards in the form of NFTs for top liquidity providers.
  4. Flexible Withdrawal: Allows liquidity providers to withdraw their funds anytime but penalizes early withdrawals by reducing rewards.

Solidity Contract Example:

// 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 {}
}

Explanation:

  1. Staking Mechanism: Users can stake their LP tokens to start earning rewards.
  2. Dual Rewards: Rewards are distributed in the form of both a base token and a governance token.
  3. NFT Milestones: If a user stakes above certain thresholds, they can claim NFTs as rewards.
  4. Reward Rates: The contract allows setting different reward rates for base and governance tokens.
  5. Flexible Withdrawal: Users can withdraw their staked amount and rewards at any time, making the system attractive for liquidity providers.

Steps to Deploy:

  • Deploy the LiquidityIncentiveContract on a blockchain network where Uniswap V2 is available.
  • Ensure the rewardToken, governanceToken, and nftReward are deployed and passed as parameters during the deployment.
  • Adjust the reward rates based on the desired incentive model.

Customization:

  • 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.

Considerations:

  • 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.

HTML (index.html)

<!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>

CSS (styles.css)

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;
}

JavaScript (script.js)

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();
};

Explanation:

  1. Load Web3: The loadWeb3 function connects to the Ethereum network using MetaMask.
  2. Load Contract: The loadContract function initializes the contract instance and updates the UI with the user’s staked balance and rewards.
  3. Stake/Withdraw Tokens: Functions to stake and withdraw tokens, updating the UI and displaying status messages.
  4. Claim Rewards/NFT: Functions to claim rewards and NFTs.
  5. Update UI: The updateUI function refreshes the displayed information whenever an action is performed.

Setup Instructions:

  1. Replace YOUR_CONTRACT_ADDRESS with the deployed contract address.
  2. Add your contract ABI in the contractABI array in the JavaScript file.
  3. 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment