Smart Contract Integration
Token Rewards System

Token Rewards System

Integrate a blockchain token rewards system that allows users to view token information, check pending rewards, and claim their rewards on Base Mainnet.

Overview

The Token Rewards System enables users to:

  1. View token information (no wallet required)
  2. Check pending rewards (without connecting wallet)
  3. Claim rewards (with wallet connection)

Contract Details

Block Explorer Links

Contract ABI

[
  {
    "inputs": [],
    "name": "name",
    "outputs": [{"type": "string"}],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "symbol",
    "outputs": [{"type": "string"}],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "lpTokenId",
    "outputs": [{"type": "uint256"}],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "feeReceiver",
    "outputs": [{"type": "address"}],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "factoryCreator",
    "outputs": [{"type": "address"}],
    "stateMutability": "view",
    "type": "function"
  },
  {
    "inputs": [],
    "name": "get_rewards",
    "outputs": [
      {"type": "uint256", "name": "tokensToFeeReceiver"},
      {"type": "uint256", "name": "ethToFactoryCreator"}
    ],
    "stateMutability": "nonpayable",
    "type": "function"
  },
  {
    "inputs": [{"type": "address", "name": "account"}],
    "name": "balanceOf",
    "outputs": [{"type": "uint256"}],
    "stateMutability": "view",
    "type": "function"
  }
]

Implementation Guide

Installation

npm install ethers

1. Get Token Information (Read-only)

Fetch basic token information without requiring a wallet connection.

import { ethers } from 'ethers';
 
const TOKEN_ADDRESS = '0x937f6Af292875cC2a3b64bFC31B09d5A50B1cF1a';
const BASE_RPC = 'https://mainnet.base.org';
 
const TOKEN_ABI = [
  'function name() view returns (string)',
  'function symbol() view returns (string)',
  'function lpTokenId() view returns (uint256)',
  'function feeReceiver() view returns (address)',
  'function factoryCreator() view returns (address)',
  'function get_rewards() returns (uint256 tokensToFeeReceiver, uint256 ethToFactoryCreator)',
  'function balanceOf(address) view returns (uint256)'
];
 
async function getTokenInfo(tokenAddress) {
  const provider = new ethers.JsonRpcProvider(BASE_RPC);
  const token = new ethers.Contract(tokenAddress, TOKEN_ABI, provider);
  
  const [name, symbol, lpTokenId, feeReceiver, factoryCreator] = await Promise.all([
    token.name(),
    token.symbol(),
    token.lpTokenId(),
    token.feeReceiver(),
    token.factoryCreator()
  ]);
  
  return { 
    name, 
    symbol, 
    lpTokenId: lpTokenId.toString(), 
    feeReceiver, 
    factoryCreator 
  };
}

2. Check Pending Rewards (Simulation)

Check pending rewards without executing a transaction using staticCall().

async function checkPendingRewards(tokenAddress) {
  const provider = new ethers.JsonRpcProvider(BASE_RPC);
  const token = new ethers.Contract(tokenAddress, TOKEN_ABI, provider);
  
  try {
    const [tokensToReceiver, ethToCreator] = await token.get_rewards.staticCall();
    
    return {
      tokenRewards: ethers.formatEther(tokensToReceiver),
      ethRewards: ethers.formatEther(ethToCreator),
      tokenRewardsRaw: tokensToReceiver.toString(),
      ethRewardsRaw: ethToCreator.toString()
    };
  } catch (error) {
    console.error('Error checking rewards:', error);
    return { tokenRewards: '0', ethRewards: '0' };
  }
}

3. Claim Rewards (Requires Wallet)

Execute the transaction to claim rewards. Requires wallet connection.

async function claimRewards(tokenAddress) {
  if (!window.ethereum) {
    throw new Error('No wallet found. Please install MetaMask.');
  }
  
  const provider = new ethers.BrowserProvider(window.ethereum);
  await provider.send('eth_requestAccounts', []);
  const signer = await provider.getSigner();
  
  const token = new ethers.Contract(tokenAddress, TOKEN_ABI, signer);
  
  try {
    const tx = await token.get_rewards();
    console.log('Transaction sent:', tx.hash);
    
    const receipt = await tx.wait();
    console.log('Transaction confirmed:', receipt);
    
    return { success: true, txHash: receipt.hash };
  } catch (error) {
    console.error('Error claiming rewards:', error);
    return { success: false, error: error.message };
  }
}

React Component Example

Complete React component implementation:

import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
 
export default function RewardsPanel({ tokenAddress }) {
  const [tokenInfo, setTokenInfo] = useState(null);
  const [pendingRewards, setPendingRewards] = useState(null);
  const [isClaiming, setIsClaiming] = useState(false);
  
  useEffect(() => {
    // Fetch token info once
    getTokenInfo(tokenAddress).then(setTokenInfo);
    
    // Poll rewards every 30 seconds
    const interval = setInterval(() => {
      checkPendingRewards(tokenAddress).then(setPendingRewards);
    }, 30000);
    
    // Initial fetch
    checkPendingRewards(tokenAddress).then(setPendingRewards);
    
    return () => clearInterval(interval);
  }, [tokenAddress]);
  
  const handleClaim = async () => {
    setIsClaiming(true);
    try {
      const result = await claimRewards(tokenAddress);
      if (result.success) {
        alert(\`Rewards claimed! TX: \${result.txHash}\`);
        // Refresh rewards after claiming
        checkPendingRewards(tokenAddress).then(setPendingRewards);
      } else {
        alert(\`Error: \${result.error}\`);
      }
    } finally {
      setIsClaiming(false);
    }
  };
  
  return (
    <div className="rewards-panel">
      <h2>{tokenInfo?.name} ({tokenInfo?.symbol})</h2>
      
      <div className="pending-rewards">
        <h3>Pending Rewards</h3>
        <p>Token Fees: {pendingRewards?.tokenRewards || '0'} tokens</p>
        <p>ETH Fees: {pendingRewards?.ethRewards || '0'} ETH</p>
      </div>
      
      <button 
        onClick={handleClaim} 
        disabled={isClaiming}
        className="claim-button"
      >
        {isClaiming ? 'Claiming...' : 'Claim Rewards'}
      </button>
      
      <div className="fee-info">
        <small>Token fees → {tokenInfo?.feeReceiver}</small><br/>
        <small>ETH fees → {tokenInfo?.factoryCreator}</small>
      </div>
    </div>
  );
}

UI/UX Requirements

Essential Features

  1. Token Information Display

    • Show token name and symbol prominently
    • Display LP Token ID
    • Show fee receiver addresses
  2. Pending Rewards Display

    • Real-time updates (poll every 30 seconds)
    • Show both token fees and ETH fees separately
    • Format numbers for readability
  3. Claim Functionality

    • "Claim Rewards" button (only clickable if rewards > 0)
    • Show transaction status: Pending → Confirmed/Failed
    • Disable button during transaction
  4. Fee Distribution Info

    • "Token fees go to Fee Receiver: [address]"
    • "ETH fees go to Factory Creator: [address]"
  5. Auto-refresh

    • Refresh rewards display after successful claim
    • Continue polling for new rewards
  6. Error Handling

    • No wallet installed
    • Wrong network
    • Transaction rejection
    • Contract errors

How It Works

Rewards Distribution

  1. Anyone can call get_rewards() - No special permissions required
  2. Automatic distribution - Rewards go to the correct recipients
  3. Token feesfeeReceiver address
  4. ETH feesfactoryCreator address
  5. The caller doesn't receive anything - They just trigger the distribution

Important Notes

  • Use staticCall() for preview to avoid actual execution
  • Always verify the chain is Base (8453) before transactions
  • Rewards accumulate from trading activity on Uniswap V4
  • No gas optimization needed - the caller pays gas but gets no rewards

Testing

Test Token

Address: 0x937f6Af292875cC2a3b64bFC31B09d5A50B1cF1a

Expected Results:

  • Name: "Hi-hat Token"
  • Symbol: "HIHAT"
  • LP Token ID: 518214
  • Token fees pending: ~24.35 tokens
  • ETH fees pending: ~0.0000834 ETH

Testing Checklist

  • ✓ Token info loads without wallet
  • ✓ Pending rewards display correctly
  • ✓ Rewards update every 30 seconds
  • ✓ Claim button connects wallet
  • ✓ Transaction status shows correctly
  • ✓ Rewards refresh after claim
  • ✓ Error messages are clear

Error Handling

Common errors and solutions:

ErrorCauseSolution
"No wallet found"MetaMask not installedPrompt user to install wallet
"Wrong network"Not on BasePrompt to switch to Base
"Transaction rejected"User cancelledAllow retry
"Insufficient funds"Not enough ETH for gasShow balance warning
"Contract error"VariousDisplay error message

Dependencies

{
  "dependencies": {
    "ethers": "^6.0.0"
  }
}

Optional Wallet Libraries

For better wallet connection experience:

# wagmi + RainbowKit (React)
npm install wagmi @rainbow-me/rainbowkit
 
# web3modal (Multi-framework)
npm install @web3modal/wagmi
 
# ConnectKit (React)
npm install connectkit

Best Practices

  1. Performance

    • Cache token info (it doesn't change)
    • Use efficient polling intervals (30s recommended)
    • Debounce claim button clicks
  2. User Experience

    • Show loading states clearly
    • Display transaction links to Basescan
    • Format large numbers for readability
    • Use tooltips for technical terms
  3. Security

    • Validate chain ID before transactions
    • Handle all error cases
    • Never expose private keys
    • Use HTTPS only
  4. Mobile Responsiveness

    • Test on mobile devices
    • Ensure buttons are easily clickable
    • Display addresses in truncated format
    • Support mobile wallet apps

Support

For issues or questions: