Owner withdraw
We have one important action left to implement for the owner: withdrawing PYUSD from the contract. This is a simple feature that will allow the owner to view the PYUSD balance on the contract and withdraw it to their wallet.
Monitoring the balance
In order to display a constantly up to date balance to the owner, we'll need to regularly invalidate the query of the accrued PYUSD. To do this, we'll use the useBlockNumber
hook to watch for new blocks and invalidate the query when a new block is mined.
const accruedPyusd = useTokenBalance(paymentToken, helloPyusdAddress);
const queryClient = useQueryClient();
const { data: blockNumber } = useBlockNumber({ watch: true });
useEffect(() => {
queryClient.invalidateQueries({ queryKey: accruedPyusd.queryKey });
}, [blockNumber, queryClient]);
Conditionally displaying the owner info:
We query the owner of the contract and compare it to the connected wallet address. If the connected wallet is not the owner, we return null
so nothing is rendered:
const owner = useReadContract({
address: helloPyusdAddress,
abi: HelloPyusdAbi,
functionName: "owner",
});
if (!account.isConnected || account.address !== owner.data) {
return null;
}
Withdrawing PYUSD
Similar to the minting cases in the last section, we simulate the withdraw, and only let the user actually withdraw if the simulation is successful:
const simulateWithdraw = useSimulateContract({
address: helloPyusdAddress,
abi: HelloPyusdAbi,
functionName: "withdrawToken",
args: [paymentToken, account.address || zeroAddress],
query: {
enabled: account.isConnected,
},
});
const writeWithdraw = useWriteContract();
useChangeObserver(writeWithdraw.status, "success", () => {
accruedPyusd.refetch();
});
/// ...
<button
className='w-full mx-auto block button'
disabled={!simulateWithdraw.isSuccess || accruedPyusd.data === 0n}
onClick={() => writeWithdraw.writeContract(simulateWithdraw.data!.request)}
>
Withdraw
</button>;
We could move some of these actions into our domain specific hooks, but for now we'll leave them in the component.
Final OwnerActions component
import { useQueryClient } from "@tanstack/react-query";
import { useEffect } from "react";
import { formatUnits, zeroAddress } from "viem";
import {
useAccount,
useBlockNumber,
useReadContract,
useSimulateContract,
useWriteContract,
} from "wagmi";
import HelloPyusdAbi from "../abi/HelloPyusd.abi";
import { useChangeObserver } from "../hooks/changeObserver";
import { useToken, useTokenBalance } from "../hooks/erc20";
import { useHelloPyusdAddress } from "../hooks/helloPyusd";
import { usePaymentTokenAddress } from "../hooks/paymentToken";
export default function OwnerActions() {
const account = useAccount();
const paymentToken = usePaymentTokenAddress();
const token = useToken(paymentToken);
const helloPyusdAddress = useHelloPyusdAddress();
const accruedPyusd = useTokenBalance(paymentToken, helloPyusdAddress);
const queryClient = useQueryClient();
const { data: blockNumber } = useBlockNumber({ watch: true });
useEffect(() => {
queryClient.invalidateQueries({ queryKey: accruedPyusd.queryKey });
}, [blockNumber, queryClient]);
const owner = useReadContract({
address: helloPyusdAddress,
abi: HelloPyusdAbi,
functionName: "owner",
});
const simulateWithdraw = useSimulateContract({
address: helloPyusdAddress,
abi: HelloPyusdAbi,
functionName: "withdrawToken",
args: [paymentToken, account.address || zeroAddress],
query: {
enabled: account.isConnected,
},
});
const writeWithdraw = useWriteContract();
useChangeObserver(writeWithdraw.status, "success", () => {
accruedPyusd.refetch();
});
if (!account.isConnected || account.address !== owner.data) {
return null;
}
if (!token.data || !accruedPyusd.isSuccess) {
return (
<div className='text-sm opacity-50'>
{token.error?.message || "Loading..."}
</div>
);
}
return (
<section className='overflow-clip text-sm border-2 border-zinc-500 border-opacity-50 p-3 space-y-2'>
<div className='flex justify-between'>
<span>{token.data.symbol} Available</span>
<span>
{accruedPyusd.isSuccess
? formatUnits(accruedPyusd.data, token.data.decimals)
: "Loading..."}
</span>
</div>
<button
className='w-full mx-auto block button'
disabled={!simulateWithdraw.isSuccess || accruedPyusd.data === 0n}
onClick={() =>
writeWithdraw.writeContract(simulateWithdraw.data!.request)
}
>
Withdraw
</button>
</section>
);
}