Handles the redemption process for Tap Ants Vouchers.
The VoucherRedeemContract manages the redemption of vouchers for tokens or other rewards. It supports both admin-controlled and self-service redemption processes, allowing users to exchange their vouchers for redemption tokens.
0x6cD3B9C6a28851377FCf305D3C269C328797Cc5E
Initializes the redemption contract with an initial owner.
initialOwner
- Initial owner addressSets whether a voucher token is supported for redemption.
voucherToken
- Address of the voucher tokensupported
- Boolean indicating if the voucher is supportedOnly callable by the contract owner
Sets the redemption token for a voucher token.
voucherToken
- Address of the voucher tokenredemptionToken
- Address of the token to be issued on redemptionOnly callable by the contract owner
Admin function to redeem/burn voucher tokens and issue new tokens.
voucherToken
- Address of voucher token to redeemaccount
- Account to redeem fromamount
- Amount to redeemdata
- Additional data for redemptionOnly callable by the contract owner
Self-service redemption function for users.
voucherToken
- Address of voucher token to redeemamount
- Amount to redeemdata
- Additional data for redemptionEmergency withdrawal of redemption tokens in case of issues.
token
- Token to withdrawto
- Address to send tokens toamount
- Amount to withdrawOnly callable by the contract owner
import { useAccount, useWriteContract, useReadContract, useWaitForTransactionReceipt } from 'wagmi'
import { parseEther } from 'viem'
import { useState } from 'react'
// Contract addresses
const VOUCHER_ADDRESS = '0x4223f47b1EB3bDB97d0117Ae50e2cC65309c22AE'
const REDEEM_ADDRESS = '0x6cD3B9C6a28851377FCf305D3C269C328797Cc5E'
// ABIs (simplified for example)
const voucherAbi = [
{
name: 'approve',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'spender', type: 'address' },
{ name: 'amount', type: 'uint256' }
],
outputs: [{ type: 'bool' }]
},
{
name: 'allowance',
type: 'function',
stateMutability: 'view',
inputs: [
{ name: 'owner', type: 'address' },
{ name: 'spender', type: 'address' }
],
outputs: [{ type: 'uint256' }]
}
]
const redeemAbi = [
{
name: 'selfRedeem',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'voucherToken', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'data', type: 'bytes' }
],
outputs: []
}
]
export function RedeemVouchers() {
const { address } = useAccount()
const [amount, setAmount] = useState('')
const [status, setStatus] = useState('')
// Check allowance
const { data: allowance } = useReadContract({
address: VOUCHER_ADDRESS,
abi: voucherAbi,
functionName: 'allowance',
args: [address, REDEEM_ADDRESS],
enabled: !!address,
})
// Approve vouchers
const { data: approveHash, writeContract: approve } = useWriteContract()
// Wait for approval transaction
const { isLoading: isApproving, isSuccess: isApproved } = useWaitForTransactionReceipt({
hash: approveHash,
})
// Redeem vouchers
const { data: redeemHash, writeContract: redeem } = useWriteContract()
// Wait for redeem transaction
const { isLoading: isRedeeming, isSuccess: isRedeemed } = useWaitForTransactionReceipt({
hash: redeemHash,
})
const handleRedeem = async () => {
if (!amount) return
setIsRedeeming(true)
setStatus('Starting redemption process...')
try {
const amountInWei = parseEther(amount)
// Check if we need to approve first
if (!allowance || allowance < amountInWei) {
setStatus('Approving vouchers...')
await approve({
address: VOUCHER_ADDRESS,
abi: voucherAbi,
functionName: 'approve',
args: [REDEEM_ADDRESS, amountInWei],
})
// Wait for approval to be confirmed
setStatus('Waiting for approval confirmation...')
}
// Now redeem the vouchers
setStatus('Redeeming vouchers...')
await redeem({
address: REDEEM_ADDRESS,
abi: redeemAbi,
functionName: 'selfRedeem',
args: [VOUCHER_ADDRESS, amountInWei, '0x'],
})
// Wait for redemption to be confirmed
setStatus('Waiting for redemption confirmation...')
if (isRedeemed) {
setStatus('Vouchers redeemed successfully!')
setAmount('')
}
} catch (error) {
console.error('Error redeeming vouchers:', error)
setStatus('Error: ' + (error.message || 'Failed to redeem vouchers'))
} finally {
setIsRedeeming(false)
}
}
return (
<div className="p-4 border rounded-lg">
<h2 className="text-xl font-bold mb-4">Redeem Vouchers</h2>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">Amount to Redeem</label>
<input
type="text"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Enter amount"
className="w-full p-2 border rounded"
disabled={isApproving || isRedeeming}
/>
</div>
<button
onClick={handleRedeem}
disabled={!amount || isApproving || isRedeeming}
className="w-full bg-blue-600 text-white py-2 px-4 rounded disabled:opacity-50"
>
{isApproving ? 'Approving...' : isRedeeming ? 'Redeeming...' : 'Redeem Vouchers'}
</button>
{status && (
<div className="mt-4 p-3 bg-gray-100 rounded text-sm">
{status}
</div>
)}
</div>
)
}
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
import { parseEther } from 'viem'
import { useState } from 'react'
// Contract address
const REDEEM_ADDRESS = '0x6cD3B9C6a28851377FCf305D3C269C328797Cc5E'
// ABI (simplified for example)
const redeemAbi = [
{
name: 'redeem',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'voucherToken', type: 'address' },
{ name: 'account', type: 'address' },
{ name: 'amount', type: 'uint256' },
{ name: 'data', type: 'bytes' }
],
outputs: []
}
]
export function AdminRedeem() {
const { address } = useAccount()
const [voucherAddress, setVoucherAddress] = useState('0x4223f47b1EB3bDB97d0117Ae50e2cC65309c22AE')
const [userAddress, setUserAddress] = useState('')
const [amount, setAmount] = useState('')
const [status, setStatus] = useState('')
// Redeem vouchers as admin
const { data: redeemHash, writeContract: redeem } = useWriteContract()
// Wait for redeem transaction
const { isLoading: isRedeeming, isSuccess: isRedeemed } = useWaitForTransactionReceipt({
hash: redeemHash,
})
const handleAdminRedeem = async () => {
if (!voucherAddress || !userAddress || !amount) {
setStatus('Please fill in all fields')
return
}
try {
setStatus('Processing admin redemption...')
const amountInWei = parseEther(amount)
await redeem({
address: REDEEM_ADDRESS,
abi: redeemAbi,
functionName: 'redeem',
args: [voucherAddress, userAddress, amountInWei, '0x'],
})
setStatus('Waiting for transaction confirmation...')
if (isRedeemed) {
setStatus('Admin redemption successful!')
setAmount('')
}
} catch (error) {
console.error('Error during admin redemption:', error)
setStatus('Error: ' + (error.message || 'Failed to process admin redemption'))
}
}
return (
<div className="p-4 border rounded-lg">
<h2 className="text-xl font-bold mb-4">Admin Redemption</h2>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">Voucher Token Address</label>
<input
type="text"
value={voucherAddress}
onChange={(e) => setVoucherAddress(e.target.value)}
className="w-full p-2 border rounded"
disabled={isRedeeming}
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">User Address</label>
<input
type="text"
value={userAddress}
onChange={(e) => setUserAddress(e.target.value)}
placeholder="0x..."
className="w-full p-2 border rounded"
disabled={isRedeeming}
/>
</div>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">Amount</label>
<input
type="text"
value={amount}
onChange={(e) => setAmount(e.target.value)}
placeholder="Enter amount"
className="w-full p-2 border rounded"
disabled={isRedeeming}
/>
</div>
<button
onClick={handleAdminRedeem}
disabled={!voucherAddress || !userAddress || !amount || isRedeeming}
className="w-full bg-blue-600 text-white py-2 px-4 rounded disabled:opacity-50"
>
{isRedeeming ? 'Processing...' : 'Process Redemption'}
</button>
{status && (
<div className="mt-4 p-3 bg-gray-100 rounded text-sm">
{status}
</div>
)}
</div>
)
}
import { useAccount, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
import { useState } from 'react'
// Contract address
const REDEEM_ADDRESS = '0x6cD3B9C6a28851377FCf305D3C269C328797Cc5E'
// ABI (simplified for example)
const redeemAbi = [
{
name: 'setSupportedVoucher',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'voucherToken', type: 'address' },
{ name: 'supported', type: 'bool' }
],
outputs: []
},
{
name: 'setRedemptionToken',
type: 'function',
stateMutability: 'nonpayable',
inputs: [
{ name: 'voucherToken', type: 'address' },
{ name: 'redemptionToken', type: 'address' }
],
outputs: []
}
]
export function SetupVoucher() {
const { address } = useAccount()
const [voucherAddress, setVoucherAddress] = useState('')
const [redemptionTokenAddress, setRedemptionTokenAddress] = useState('')
const [status, setStatus] = useState('')
const [step, setStep] = useState(1)
// Set supported voucher
const { data: supportHash, writeContract: setSupportedVoucher } = useWriteContract()
// Wait for support transaction
const { isLoading: isSettingSupport, isSuccess: isSupportSet } = useWaitForTransactionReceipt({
hash: supportHash,
})
// Set redemption token
const { data: tokenHash, writeContract: setRedemptionToken } = useWriteContract()
// Wait for token transaction
const { isLoading: isSettingToken, isSuccess: isTokenSet } = useWaitForTransactionReceipt({
hash: tokenHash,
})
const handleSetSupport = async () => {
if (!voucherAddress) return
try {
setStatus('Setting voucher as supported...')
await setSupportedVoucher({
address: REDEEM_ADDRESS,
abi: redeemAbi,
functionName: 'setSupportedVoucher',
args: [voucherAddress, true],
})
setStatus('Waiting for transaction confirmation...')
if (isSupportSet) {
setStatus('Voucher set as supported successfully!')
setStep(2)
}
} catch (error) {
console.error('Error setting voucher support:', error)
setStatus('Error: ' + (error.message || 'Failed to set voucher support'))
}
}
const handleSetRedemptionToken = async () => {
if (!voucherAddress || !redemptionTokenAddress) return
try {
setStatus('Setting redemption token...')
await setRedemptionToken({
address: REDEEM_ADDRESS,
abi: redeemAbi,
functionName: 'setRedemptionToken',
args: [voucherAddress, redemptionTokenAddress],
})
setStatus('Waiting for transaction confirmation...')
if (isTokenSet) {
setStatus('Redemption token set successfully!')
setStep(3)
}
} catch (error) {
console.error('Error setting redemption token:', error)
setStatus('Error: ' + (error.message || 'Failed to set redemption token'))
}
}
return (
<div className="p-4 border rounded-lg">
<h2 className="text-xl font-bold mb-4">Setup New Voucher</h2>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">Voucher Token Address</label>
<input
type="text"
value={voucherAddress}
onChange={(e) => setVoucherAddress(e.target.value)}
placeholder="0x..."
className="w-full p-2 border rounded"
disabled={step > 1 || isSettingSupport}
/>
</div>
{step === 1 && (
<button
onClick={handleSetSupport}
disabled={!voucherAddress || isSettingSupport}
className="w-full bg-blue-600 text-white py-2 px-4 rounded disabled:opacity-50"
>
{isSettingSupport ? 'Setting...' : 'Set as Supported'}
</button>
)}
{step >= 2 && (
<>
<div className="mb-4">
<label className="block text-sm font-medium mb-1">Redemption Token Address</label>
<input
type="text"
value={redemptionTokenAddress}
onChange={(e) => setRedemptionTokenAddress(e.target.value)}
placeholder="0x..."
className="w-full p-2 border rounded"
disabled={step > 2 || isSettingToken}
/>
</div>
{step === 2 && (
<button
onClick={handleSetRedemptionToken}
disabled={!redemptionTokenAddress || isSettingToken}
className="w-full bg-blue-600 text-white py-2 px-4 rounded disabled:opacity-50"
>
{isSettingToken ? 'Setting...' : 'Set Redemption Token'}
</button>
)}
</>
)}
{step === 3 && (
<div className="p-3 bg-green-100 text-green-700 rounded">
Voucher setup completed successfully!
</div>
)}
{status && (
<div className="mt-4 p-3 bg-gray-100 rounded text-sm">
{status}
</div>
)}
</div>
)
}
The contract uses OpenZeppelin's Ownable pattern for access control. Ensure that the owner address is secure and consider implementing a multi-signature wallet or DAO for critical operations.
The contract must have sufficient redemption tokens to fulfill redemption requests. Ensure that the contract is properly funded with redemption tokens before allowing redemptions.
The emergency withdrawal function allows the owner to withdraw any tokens from the contract. This is a powerful capability that should be used with caution and only in emergency situations.