Web Application Integration

Learn how to integrate the Tap Ants Voucher System into your web application.

Overview

Integrating the Tap Ants Voucher System into your web application allows you to offer voucher-based rewards, loyalty programs, or other incentives to your users. This guide will walk you through the process of connecting your web application to the Tap Ants Voucher System using wagmi and viem.

Prerequisites

  • Web Development Environment: A modern web development setup with Node.js and npm/yarn.
  • Web3 Knowledge: Basic understanding of Ethereum and web3 concepts.
  • Wallet Integration: A way for users to connect their Ethereum wallets (e.g., MetaMask).

Integration Steps

1. Install Dependencies

Install wagmi and related packages

Terminal
npm install wagmi viem @tanstack/react-query

2. Set Up wagmi Client

Configure wagmi

JavaScript
// config.ts
import { createConfig, http } from 'wagmi'
import { sepolia } from 'wagmi/chains'

// Contract addresses
export const VOUCHER_ADDRESS = '0x4223f47b1EB3bDB97d0117Ae50e2cC65309c22AE'
export const REDEEM_ADDRESS = '0x6cD3B9C6a28851377FCf305D3C269C328797Cc5E'
export const TOKEN_ADDRESS = '0x8f71a7503284c69eb200605b5ab11fabc555c865'

// Create config
export const config = createConfig({
  chains: [sepolia],
  transports: {
    [sepolia.id]: http(),
  },
})

3. Set Up Contract ABIs

Create ABI files

JavaScript
// abis.ts
export const voucherAbi = [
  {
    name: 'balanceOf',
    type: 'function',
    stateMutability: 'view',
    inputs: [{ name: 'owner', type: 'address' }],
    outputs: [{ type: 'uint256' }]
  },
  {
    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' }]
  }
];

export const redeemAbi = [
  {
    name: 'redeem',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'voucherToken', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'data', type: 'bytes' }
    ],
    outputs: []
  },
  {
    name: 'selfRedeem',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'voucherToken', type: 'address' },
      { name: 'amount', type: 'uint256' },
      { name: 'data', type: 'bytes' }
    ],
    outputs: []
  },
  {
    name: 'getSupportedVoucher',
    type: 'function',
    stateMutability: 'view',
    inputs: [{ name: '_voucherAddress', type: 'address' }],
    outputs: [
      { name: '', type: 'address' },
      { name: '', type: 'uint256' },
      { name: '', type: 'bool' }
    ]
  }
];

export const tokenAbi = [
  {
    name: 'balanceOf',
    type: 'function',
    stateMutability: 'view',
    inputs: [{ name: 'owner', type: 'address' }],
    outputs: [{ type: 'uint256' }]
  },
  {
    name: 'transfer',
    type: 'function',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'to', type: 'address' },
      { name: 'amount', type: 'uint256' }
    ],
    outputs: [{ type: 'bool' }]
  }
];

4. Implement Voucher Balance Check

Check voucher balance

React Component
import { useAccount, useReadContract } from 'wagmi'
import { formatUnits } from 'viem'
import { VOUCHER_ADDRESS, voucherAbi } from './config'

export function VoucherBalance() {
  const { address } = useAccount()
  
  const { data: balance, isLoading, isError } = useReadContract({
    address: VOUCHER_ADDRESS,
    abi: voucherAbi,
    functionName: 'balanceOf',
    args: [address],
    enabled: !!address,
  })
  
  if (isLoading) return <div>Loading balance...</div>
  if (isError) return <div>Error loading balance</div>
  
  return (
    <div className="p-4 border rounded-lg">
      <h2 className="text-xl font-bold mb-2">Your Voucher Balance</h2>
      <p className="text-2xl">{balance ? formatUnits(balance, 18) : '0'} TAV</p>
    </div>
  )
}

5. Implement Voucher Redemption

Redeem vouchers

React Component
import { useState } from 'react'
import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi'
import { parseUnits, formatUnits } from 'viem'
import { VOUCHER_ADDRESS, REDEEM_ADDRESS, voucherAbi, redeemAbi } from './config'

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
    
    setStatus('Starting redemption process...')
    
    try {
      const amountInWei = parseUnits(amount, 18)
      
      // 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],
        })
        
        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'],
      })
      
      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'))
    }
  }
  
  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>
  )
}

6. Create a Complete React Component

Voucher Dashboard Component

React Component
import { useState } from 'react'
import { useAccount } from 'wagmi'
import { VoucherBalance } from './VoucherBalance'
import { RedeemVouchers } from './RedeemVouchers'
import { WagmiConfig } from 'wagmi'
import { config } from './config'

export function VoucherDashboard() {
  const { isConnected } = useAccount()
  
  return (
    <WagmiConfig config={config}>
      <div className="max-w-md mx-auto">
        <h1 className="text-2xl font-bold mb-6">Tap Ants Voucher Dashboard</h1>
        
        {!isConnected ? (
          <div className="p-4 border rounded-lg text-center">
            <p className="mb-4">Please connect your wallet to continue</p>
            <ConnectButton />
          </div>
        ) : (
          <div className="space-y-6">
            <VoucherBalance />
            <RedeemVouchers />
          </div>
        )}
      </div>
    </WagmiConfig>
  )
}

// Simple connect button component
function ConnectButton() {
  const { connect } = useConnect({
    connector: new InjectedConnector(),
  })
  
  return (
    <button
      onClick={() => connect()}
      className="bg-blue-600 text-white py-2 px-4 rounded"
    >
      Connect Wallet
    </button>
  )
}

Best Practices

  • Error Handling: Implement comprehensive error handling to provide clear feedback to users when transactions fail.
  • Loading States: Show loading indicators during blockchain transactions to improve user experience.
  • Transaction Confirmation: Wait for transaction confirmations before updating the UI to reflect changes.
  • Gas Estimation: Estimate gas costs for transactions and inform users before they confirm.
  • Network Detection: Detect the connected network and warn users if they're not on the correct network (Sepolia testnet).

Advanced Integration

Admin Dashboard

For administrators, you can create a dashboard to manage vouchers and redemption rules:

  • Mint new vouchers to users
  • Add or remove supported vouchers
  • Update conversion rates
  • View redemption history

User Dashboard

For users, you can create a dashboard to manage their vouchers:

  • View voucher balances across different seasons
  • Redeem vouchers for tokens
  • View redemption history
  • Transfer redemption tokens to other users