Interacting With Contracts

Now, let's take a look at how we interact with our deployed contracts.

It is beyond the scope of this guide to go into this in detail. For a detailed explanation with examples, we recommend reading the Rainbowkit and wagmi documentation.

Our dApp uses functions provided by wagmi to intearact with the Dukong testnet, our connected wallet, and our contracts. The sample app provides several hooks, found in the /frontend/hooks directory. These hooks are used to both read and write transactions on our deployed contracts.

For example, here is the hook for the Whitelist:

import { useMemo } from 'react';
import { useAccount, useReadContract, useWriteContract, useWaitForTransactionReceipt } from 'wagmi';
import { whitelistAbi } from '../lib/abis/whitelist';
import { WHITELIST_ADDRESS } from '../lib/addresses';

export function useWhitelist() {
  const { address: connected } = useAccount();

  const { data: owner } = useReadContract({
    address: WHITELIST_ADDRESS,
    abi: whitelistAbi,
    functionName: 'owner',
  });

  const isOwner = useMemo(
    () => !!connected && !!owner && connected.toLowerCase() === (owner as string).toLowerCase(),
    [connected, owner]
  );

  const { writeContract, data: hash, isPending } = useWriteContract();
  const receipt = useWaitForTransactionReceipt({ hash });

  const approve = (project: `0x${string}`, investor: `0x${string}`) =>
    writeContract({ address: WHITELIST_ADDRESS, abi: whitelistAbi, functionName: 'approve', args: [project, investor] });

  const revoke = (project: `0x${string}`, investor: `0x${string}`) =>
    writeContract({ address: WHITELIST_ADDRESS, abi: whitelistAbi, functionName: 'revoke', args: [project, investor] });

  return { owner, isOwner, hash, isPending, receipt, approve, revoke };
}

You will notice the useAccount, useReadContract, useWriteContract, and useWaitForTransactionReceipt functions that allow us to get the connected account, send read and write transactions, and wait for our transaction receipts. We can then use this hook (and the others) in our dApps components to provide a meaningful user interface for our users to interact with out contracts.

As an example, below is the ContractInfo component that we use in our dApp to display information about the Whitelist contract:

import { useAccount } from 'wagmi';
import { WHITELIST_ADDRESS } from '../../lib/addresses';
import { useWhitelist } from '../../hooks/useWhitelist';

export default function ContractInfo() {
  const { address } = useAccount();
  const { owner, isOwner } = useWhitelist();

  return (
    <div>
      <p><strong>Whitelist:</strong> <code>{WHITELIST_ADDRESS}</code></p>
      <p><strong>Owner:</strong> <code>{owner ?? '—'}</code></p>
      <p><strong>You:</strong> <code>{address ?? 'Not connected'}</code></p>
      <p style={{ color: isOwner ? '#008000' : '#b00020' }}>{isOwner ? 'You are the owner' : 'You are not the owner'}</p>
    </div>
  );
}

You can see that we have imported the useWhitelist hook which we use to get the owner address, and a check isOwner to indicte if the connected account is the owner of the Whitelist contract.

We will leave you to read through the other hooks and components to see how we read from our contracts and send transactions. But, as quick example, here is the useDeployProject hook which is used to send a transaction executing the deployProject() function of our RealEstateFactory contract:

import { useCallback } from 'react';
import {
  useWriteContract,
  useWaitForTransactionReceipt,
} from 'wagmi';
import { decodeEventLog } from 'viem';
import { FACTORY_ADDRESS } from '../lib/addresses';
import { factoryAbi } from '../lib/abis/factory';

export function useDeployProject() {
  const { writeContract, data: hash, isPending, error: writeError } = useWriteContract();
  const { data: receipt, isLoading: confirming, isSuccess } =
    useWaitForTransactionReceipt({ hash });

  const deploy = useCallback((args: {
    name: string; symbol: string;
    propertyId: string; jurisdiction: string; metadataUri: string;
  }) => {
    writeContract({
      address: FACTORY_ADDRESS,
      abi: factoryAbi,
      functionName: 'deployProject',
      args: [
        args.name,
        args.symbol,
        args.propertyId,
        args.jurisdiction,
        args.metadataUri,
      ],
    });
  }, [writeContract]);

  // extract new project address if available
  let newProject: `0x${string}` | undefined;
  if (isSuccess && receipt?.logs?.length) {
    for (const log of receipt.logs) {
      try {
        const parsed = decodeEventLog({
          abi: factoryAbi,
          data: log.data,
          topics: log.topics,
        });
        if (parsed.eventName === 'ProjectCreated') {
          newProject = parsed.args.projectAddress as `0x${string}`;
          break;
        }
      } catch {}
    }
  }

  return {
    deploy,
    txHash: hash,
    newProject,
    isPending,
    confirming,
    isSuccess,
    writeError,
  };
}

This hook is used in the NewProjectForm component to provide the user interface for deploying RealEstateToken contracts.

Last updated