VoucherRedeemContract

Handles the redemption process for Tap Ants Vouchers.

Overview

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.

Contract Address:0x6cD3B9C6a28851377FCf305D3C269C328797Cc5E
Network:Sepolia Testnet

Key Features

Contract API

constructor

Initializes the redemption contract with an initial owner.

Parameters:

  • initialOwner - Initial owner address

setSupportedVoucher

Sets whether a voucher token is supported for redemption.

Parameters:

  • voucherToken - Address of the voucher token
  • supported - Boolean indicating if the voucher is supported

Access Control:

Only callable by the contract owner

setRedemptionToken

Sets the redemption token for a voucher token.

Parameters:

  • voucherToken - Address of the voucher token
  • redemptionToken - Address of the token to be issued on redemption

Access Control:

Only callable by the contract owner

redeem

Admin function to redeem/burn voucher tokens and issue new tokens.

Parameters:

  • voucherToken - Address of voucher token to redeem
  • account - Account to redeem from
  • amount - Amount to redeem
  • data - Additional data for redemption

Access Control:

Only callable by the contract owner

Requirements:

  • The voucher token must be supported
  • The redemption token must be set
  • The contract must have enough redemption tokens

selfRedeem

Self-service redemption function for users.

Parameters:

  • voucherToken - Address of voucher token to redeem
  • amount - Amount to redeem
  • data - Additional data for redemption

Requirements:

  • The voucher token must be supported
  • The redemption token must be set
  • The contract must have enough redemption tokens

emergencyWithdraw

Emergency withdrawal of redemption tokens in case of issues.

Parameters:

  • token - Token to withdraw
  • to - Address to send tokens to
  • amount - Amount to withdraw

Access Control:

Only callable by the contract owner

Code Examples

Self-Redeeming Vouchers

React + wagmi/viem
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>
  )
}

Admin Redemption (Owner Only)

React + wagmi/viem
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>
  )
}

Setting Up a New Voucher (Owner Only)

React + wagmi/viem
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>
  )
}

Security Considerations

Access Control

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.

Redemption Token Supply

The contract must have sufficient redemption tokens to fulfill redemption requests. Ensure that the contract is properly funded with redemption tokens before allowing redemptions.

Emergency Withdrawal

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.

Previous: TapAntsVoucherNext: RedemptionToken