A React component that provides a reward button with Ethereum token rewards. Built with wagmi, AppKit, and ethers.js for seamless Web3 integration.
React Reward Button is a drop-in, beautifully styled button powered by shadcn/ui and Reown wallet integration β but with a twist: every click can reward your users with real tokens.
-
π¨ Beautiful by Design
Built on top of shadcn/ui's Button component β fully customizable with variants, sizes, and accessible design patterns out of the box. -
π Seamless Wallet Integration with Reown
Handles wallet connection automatically using the Reown React library. Users connect once and are ready to receive rewards instantly. -
πͺ Compatible with Any ERC-20 Token
Reward users in your own token or any existing ERC-20 token on supported EVM chains (Polygon recommended). -
β‘ One Click = Real Value
Trigger token rewards instantly when users interact with the button β perfect for gamified actions, engagement campaigns, or loyalty flows. -
βοΈ Flexible Gas Modes
Choose how rewards are delivered:- Developer-funded (gasless for user)
- Or user-funded (MetaMask/Reown confirmation per claim)
-
π§© Plug-and-Play Integration
Drop it into any React project like a regular button. Add reward props, and you're done β no backend required unless you want one.
Try the demo here: React Reward Button Demo
- π Web3 Integration: Built with wagmi and AppKit for modern Web3 functionality
- π° ERC20 Token Rewards: Send any ERC20 token as rewards
- π Wallet Connection: Seamless wallet connection with AppKit
- βοΈ Gas Fee Options: Choose who pays gas fees (sender or receiver)
- π¨ Customizable UI: Multiple variants, sizes, and styling options
- π Type Safe: Full TypeScript support with comprehensive types
- π± Mobile Friendly: Works on all devices with responsive design
- βΏ Accessible: Built with accessibility standards in mind
- π Performance: Optimized for production use
β AI-friendly: Works great with ChatGPT/Cursor/GitHub Copilot
npm install react-reward-button
This library requires the following peer dependencies:
npm install wagmi @tanstack/react-query ethers @reown/appkit @reown/appkit-adapter-wagmi react react-dom
Wrap your app with the necessary providers:
import { WagmiProvider } from 'wagmi';
import { mainnet, polygon, sepolia } from 'wagmi/chains';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { createAppKit } from '@reown/appkit/react';
import { WagmiAdapter } from '@reown/appkit-adapter-wagmi';
const queryClient = new QueryClient();
// Get your project ID from https://cloud.reown.com/
const projectId = 'YOUR_PROJECT_ID';
// Configure networks
const networks = [mainnet, polygon, sepolia];
// Set up the Wagmi adapter
const wagmiAdapter = new WagmiAdapter({
projectId,
networks,
});
// Create the AppKit instance
createAppKit({
adapters: [wagmiAdapter],
networks,
projectId,
metadata: {
name: 'My App',
description: 'My App Description',
url: 'https://myapp.com',
icons: ['https://myapp.com/icon.png']
}
});
function App() {
return (
<WagmiProvider config={wagmiAdapter.wagmiConfig}>
<QueryClientProvider client={queryClient}>
{/* Your app components */}
</QueryClientProvider>
</WagmiProvider>
);
}
import { RewardButton } from 'react-reward-button';
function MyComponent() {
const handleRewardClaimed = (txHash: string, amount: string) => {
console.log('Reward claimed!', { txHash, amount });
};
const handleRewardFailed = (error: Error) => {
console.error('Reward failed:', error);
};
return (
<RewardButton
tokenAddress="0xA0b86a33E6441357C49c74aA8F4b3f0B7C6d6b2c" // Example USDC address
rewardAmount="1000000" // 1 USDC (6 decimals)
senderAddress="0x..." // Your wallet address holding the tokens
senderPrivateKey="0x..." // Your private key (keep secure!)
onRewardClaimed={handleRewardClaimed}
onRewardFailed={handleRewardFailed}
tokenSymbol="USDC"
showRewardAmount={true}
>
Claim 1 USDC Reward
</RewardButton>
);
}
Prop | Type | Default | Description |
---|---|---|---|
tokenAddress |
string |
undefined |
The Ethereum address of the reward token contract |
rewardAmount |
string |
undefined |
The amount of tokens to reward (in wei or token units) |
recipientAddress |
string |
undefined |
The recipient address (falls back to connected wallet) |
senderAddress |
string |
undefined |
The sender wallet address that holds the reward tokens |
senderPrivateKey |
string |
undefined |
The private key of the sender wallet |
rpcUrl |
string |
undefined |
RPC URL for the network |
onReward |
() => void | Promise<void> |
undefined |
Callback for non-Web3 mode |
onRewardClaimed |
(txHash: string, amount: string) => void |
undefined |
Called when reward is successfully claimed |
onRewardFailed |
(error: Error) => void |
undefined |
Called when reward claim fails |
onRewardStarted |
() => void |
undefined |
Called when reward claim is initiated |
showRewardAmount |
boolean |
true |
Whether to show the reward amount on the button |
tokenSymbol |
string |
'TOKEN' |
Custom token symbol to display |
requireConnection |
boolean |
true |
Whether to require wallet connection |
loadingText |
string |
'Claiming Reward...' |
Custom loading text |
userPaysGas |
boolean |
false |
Whether user pays gas fees (vs sender) |
isLoading |
boolean |
false |
External loading state |
children |
React.ReactNode |
'Claim Reward' |
Button content |
variant |
'default' | 'secondary' | 'outline' | 'ghost' | 'destructive' |
'default' |
Button variant |
size |
'default' | 'sm' | 'lg' | 'icon' |
'default' |
Button size |
disabled |
boolean |
false |
Whether the button is disabled |
className |
string |
undefined |
Additional CSS classes |
import { RewardButton } from 'react-reward-button';
function BasicReward() {
return (
<RewardButton
tokenAddress="0xA0b86a33E6441357C49c74aA8F4b3f0B7C6d6b2c"
rewardAmount="1000000" // 1 USDC (6 decimals)
senderAddress="0x742d35Cc6634C0532925a3b8D372C468F8c9d5b7"
senderPrivateKey="0x..." // Keep this secure!
tokenSymbol="USDC"
onRewardClaimed={(txHash, amount) => {
console.log(`Reward claimed! Amount: ${amount}, TX: ${txHash}`);
}}
>
Claim 1 USDC
</RewardButton>
);
}
// ETH-based reward
<RewardButton
tokenAddress="0x..." // WETH address
rewardAmount={ethers.parseEther("0.01")} // 0.01 ETH
senderAddress="0x..."
senderPrivateKey="0x..."
tokenSymbol="WETH"
>
Claim 0.01 WETH
</RewardButton>
// Custom ERC20 token
<RewardButton
tokenAddress="0x..." // Your token address
rewardAmount="100000000000000000000" // 100 tokens (18 decimals)
senderAddress="0x..."
senderPrivateKey="0x..."
tokenSymbol="MYTOKEN"
showRewardAmount={true}
>
Claim Daily Reward
</RewardButton>
// Sender pays gas fees (default)
<RewardButton
tokenAddress="0x..."
rewardAmount="1000000"
senderAddress="0x..."
senderPrivateKey="0x..."
userPaysGas={false} // Sender pays gas
>
Free Reward (No Gas Required)
</RewardButton>
// User pays gas fees
<RewardButton
tokenAddress="0x..."
rewardAmount="1000000"
senderAddress="0x..."
senderPrivateKey="0x..."
userPaysGas={true} // User pays gas
>
Claim Reward (You Pay Gas)
</RewardButton>
<RewardButton
tokenAddress="0x..."
rewardAmount="1000000"
senderAddress="0x..."
senderPrivateKey="0x..."
variant="outline"
size="lg"
className="my-custom-styles"
style={{
backgroundColor: '#ff6b6b',
borderRadius: '12px',
padding: '16px 32px'
}}
>
π Special Reward
</RewardButton>
import { useState } from 'react';
import { RewardButton } from 'react-reward-button';
function RewardWithErrorHandling() {
const [error, setError] = useState<string | null>(null);
const [success, setSuccess] = useState(false);
return (
<div>
<RewardButton
tokenAddress="0x..."
rewardAmount="1000000"
senderAddress="0x..."
senderPrivateKey="0x..."
onRewardClaimed={(txHash, amount) => {
setSuccess(true);
setError(null);
console.log('Success!', { txHash, amount });
}}
onRewardFailed={(error) => {
setError(error.message);
setSuccess(false);
}}
onRewardStarted={() => {
setError(null);
setSuccess(false);
}}
>
Claim Reward
</RewardButton>
{error && <p style={{ color: 'red' }}>Error: {error}</p>}
{success && <p style={{ color: 'green' }}>Reward claimed successfully!</p>}
</div>
);
}
If you don't provide tokenAddress
and rewardAmount
, it works as a regular button:
import { RewardButton } from 'react-reward-button';
function RegularButton() {
return (
<RewardButton
onReward={() => {
console.log('Regular button clicked!');
}}
variant="secondary"
>
Regular Button
</RewardButton>
);
}
For development, you can use environment variables:
# .env
VITE_REOWN_PROJECT_ID=your_project_id
VITE_NETWORK=mainnet
VITE_RPC_URL=https://mainnet.infura.io/v3/your_key
import { mainnet, polygon, sepolia, arbitrum } from 'wagmi/chains';
const networks = [
mainnet, // Ethereum Mainnet
polygon, // Polygon
sepolia, // Ethereum Sepolia Testnet
arbitrum, // Arbitrum One
];
- Never expose private keys in client-side code - The
senderPrivateKey
should only be used in secure environments - Use environment variables for sensitive data
- Consider using a backend service for production applications
- Validate all inputs and amounts before processing
- Test thoroughly on testnets before mainnet deployment
- Ethereum Mainnet
- Polygon
- Arbitrum
- Optimism
- Sepolia Testnet
- Polygon Mumbai Testnet
- Any EVM-compatible network
- Chrome 90+
- Firefox 88+
- Safari 14+
- Edge 90+
- Mobile browsers with Web3 wallet support
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
MIT License - see the LICENSE file for details.
- π Issues: GitHub Issues
- π¬ Discussions: GitHub Discussions
- π Documentation: Full Documentation
Made with β€οΈ for the Web3 community