Skip to content

Commit

Permalink
Migration to wagmi v1 and viem (#352)
Browse files Browse the repository at this point in the history
Co-authored-by: Samuel <sverps@gmail.com>
  • Loading branch information
technophile-04 and sverps authored Jul 5, 2023
1 parent 9262e6d commit 7d3e47b
Show file tree
Hide file tree
Showing 58 changed files with 1,041 additions and 1,079 deletions.
11 changes: 5 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,8 +213,8 @@ const {
} = useScaffoldEventHistory({
contractName: "YourContract",
eventName: "GreetingChange",
// Specify the starting block number from which to read events.
fromBlock: 31231,
// Specify the starting block number from which to read events, this is a bigint.
fromBlock: 31231n,
blockData: true,
// Apply filters to the event based on parameter names and values { [parameterName]: value },
filters: { premium: true }
Expand Down Expand Up @@ -251,13 +251,12 @@ const { data: yourContract } = useScaffoldContract({
await yourContract?.greeting();

// Used to write to a contract and can be called in any function
import { Signer } from "ethers";
import { useSigner } from "wagmi";
import { useWalletClient } from "wagmi";

const { data: signer, isError, isLoading } = useSigner();
const { data: walletClient } = useWalletClient();
const { data: yourContract } = useScaffoldContract({
contractName: "YourContract",
signerOrProvider: signer as Signer,
walletClient,
});
const setGreeting = async () => {
// Call the method in any function
Expand Down
6 changes: 4 additions & 2 deletions packages/nextjs/components/blockexplorer/AddressLogsTab.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Address } from "viem";
import { useContractLogs } from "~~/hooks/scaffold-eth";
import { replacer } from "~~/utils/scaffold-eth/common";

export const AddressLogsTab = ({ address }: { address: string }) => {
export const AddressLogsTab = ({ address }: { address: Address }) => {
const contractLogs = useContractLogs(address);

return (
Expand All @@ -9,7 +11,7 @@ export const AddressLogsTab = ({ address }: { address: string }) => {
<pre className="px-5 whitespace-pre-wrap break-words">
{contractLogs.map((log, i) => (
<div key={i}>
<strong>Log:</strong> {JSON.stringify(log, null, 2)}
<strong>Log:</strong> {JSON.stringify(log, replacer, 2)}
</div>
))}
</pre>
Expand Down
14 changes: 10 additions & 4 deletions packages/nextjs/components/blockexplorer/AddressStorageTab.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { useEffect, useState } from "react";
import { localhost } from "wagmi/chains";
import { getLocalProvider } from "~~/utils/scaffold-eth";
import { createPublicClient, http, toHex } from "viem";
import { hardhat } from "wagmi/chains";

const provider = getLocalProvider(localhost);
const publicClient = createPublicClient({
chain: hardhat,
transport: http(),
});

export const AddressStorageTab = ({ address }: { address: string }) => {
const [storage, setStorage] = useState<string[]>([]);
Expand All @@ -14,7 +17,10 @@ export const AddressStorageTab = ({ address }: { address: string }) => {
let idx = 0;

while (true) {
const storageAtPosition = await provider?.getStorageAt(address, idx);
const storageAtPosition = await publicClient.getStorageAt({
address: address,
slot: toHex(idx),
});

if (storageAtPosition === "0x" + "0".repeat(64)) break;

Expand Down
15 changes: 8 additions & 7 deletions packages/nextjs/components/blockexplorer/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { useState } from "react";
import { useRouter } from "next/router";
import { ethers } from "ethers";
import { localhost } from "wagmi/chains";
import { getLocalProvider } from "~~/utils/scaffold-eth";
import { isAddress, isHex } from "viem";
import { usePublicClient } from "wagmi";
import { hardhat } from "wagmi/chains";

const provider = getLocalProvider(localhost);
export const SearchBar = () => {
const [searchInput, setSearchInput] = useState("");
const router = useRouter();

const client = usePublicClient({ chainId: hardhat.id });

const handleSearch = async (event: React.FormEvent) => {
event.preventDefault();
if (ethers.utils.isHexString(searchInput)) {
if (isHex(searchInput)) {
try {
const tx = await provider?.getTransaction(searchInput);
const tx = await client.getTransaction({ hash: searchInput });
if (tx) {
router.push(`/blockexplorer/transaction/${searchInput}`);
return;
Expand All @@ -23,7 +24,7 @@ export const SearchBar = () => {
}
}

if (ethers.utils.isAddress(searchInput)) {
if (isAddress(searchInput)) {
router.push(`/blockexplorer/address/${searchInput}`);
return;
}
Expand Down
14 changes: 7 additions & 7 deletions packages/nextjs/components/blockexplorer/TransactionsTable.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ethers } from "ethers";
import { formatEther } from "viem";
import { TransactionHash } from "~~/components/blockexplorer/TransactionHash";
import { Address } from "~~/components/scaffold-eth";
import { getTargetNetwork } from "~~/utils/scaffold-eth";
import { TransactionWithFunction, getTargetNetwork } from "~~/utils/scaffold-eth";
import { TransactionsTableProps } from "~~/utils/scaffold-eth/";

export const TransactionsTable = ({ blocks, transactionReceipts, isLoading }: TransactionsTableProps) => {
Expand Down Expand Up @@ -36,10 +36,10 @@ export const TransactionsTable = ({ blocks, transactionReceipts, isLoading }: Tr
) : (
<tbody>
{blocks.map(block =>
block.transactions.map(tx => {
(block.transactions as TransactionWithFunction[]).map(tx => {
const receipt = transactionReceipts[tx.hash];
const timeMined = new Date(block.timestamp * 1000).toLocaleString();
const functionCalled = tx.data.substring(0, 10);
const timeMined = new Date(Number(block.timestamp) * 1000).toLocaleString();
const functionCalled = tx.input.substring(0, 10);

return (
<tr key={tx.hash} className="hover text-sm">
Expand All @@ -52,7 +52,7 @@ export const TransactionsTable = ({ blocks, transactionReceipts, isLoading }: Tr
<span className="badge badge-primary font-bold text-xs">{functionCalled}</span>
)}
</td>
<td className="w-1/12">{block.number}</td>
<td className="w-1/12">{block.number?.toString()}</td>
<td className="w-2/12">{timeMined}</td>
<td className="w-2/12">
<Address address={tx.from} size="sm" />
Expand All @@ -68,7 +68,7 @@ export const TransactionsTable = ({ blocks, transactionReceipts, isLoading }: Tr
)}
</td>
<td className="text-right">
{ethers.utils.formatEther(tx.value)} {targetNetwork.nativeCurrency.symbol}
{formatEther(tx.value)} {targetNetwork.nativeCurrency.symbol}
</td>
</tr>
);
Expand Down
9 changes: 6 additions & 3 deletions packages/nextjs/components/example-ui/ContractData.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,11 @@ export const ContractData = () => {
useScaffoldEventSubscriber({
contractName: "YourContract",
eventName: "GreetingChange",
listener: (greetingSetter, newGreeting, premium, value) => {
console.log(greetingSetter, newGreeting, premium, value);
listener: logs => {
logs.map(log => {
const { greetingSetter, value, premium, newGreeting } = log.args;
console.log("📡 GreetingChange event", greetingSetter, value, premium, newGreeting);
});
},
});

Expand All @@ -45,7 +48,7 @@ export const ContractData = () => {
} = useScaffoldEventHistory({
contractName: "YourContract",
eventName: "GreetingChange",
fromBlock: Number(process.env.NEXT_PUBLIC_DEPLOY_BLOCK) || 0,
fromBlock: process.env.NEXT_PUBLIC_DEPLOY_BLOCK ? BigInt(process.env.NEXT_PUBLIC_DEPLOY_BLOCK) : 0n,
filters: { greetingSetter: address },
blockData: true,
});
Expand Down
9 changes: 4 additions & 5 deletions packages/nextjs/components/scaffold-eth/Address.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { useEffect, useState } from "react";
import Link from "next/link";
import { ethers } from "ethers";
import { isAddress } from "ethers/lib/utils";
import Blockies from "react-blockies";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { isAddress } from "viem";
import { useEnsAvatar, useEnsName } from "wagmi";
import { hardhat } from "wagmi/chains";
import { CheckCircleIcon, DocumentDuplicateIcon } from "@heroicons/react/24/outline";
Expand Down Expand Up @@ -36,8 +35,8 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }:

const { data: fetchedEns } = useEnsName({ address, enabled: isAddress(address ?? ""), chainId: 1 });
const { data: fetchedEnsAvatar } = useEnsAvatar({
address,
enabled: isAddress(address ?? ""),
name: fetchedEns,
enabled: Boolean(fetchedEns),
chainId: 1,
cacheTime: 30_000,
});
Expand All @@ -63,7 +62,7 @@ export const Address = ({ address, disableAddressLink, format, size = "base" }:
);
}

if (!ethers.utils.isAddress(address)) {
if (!isAddress(address)) {
return <span className="text-error">Wrong address</span>;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Dispatch, SetStateAction } from "react";
import { utils } from "ethers";
import { AbiParameter } from "abitype";
import {
AddressInput,
Bytes32Input,
Expand All @@ -11,9 +11,9 @@ import {

type ContractInputProps = {
setForm: Dispatch<SetStateAction<Record<string, any>>>;
form: Record<string, any>;
form: Record<string, any> | undefined;
stateObjectKey: string;
paramType: utils.ParamType;
paramType: AbiParameter;
};

/**
Expand All @@ -22,7 +22,7 @@ type ContractInputProps = {
export const ContractInput = ({ setForm, form, stateObjectKey, paramType }: ContractInputProps) => {
const inputProps = {
name: stateObjectKey,
value: form[stateObjectKey],
value: form?.[stateObjectKey],
placeholder: paramType.name ? `${paramType.type} ${paramType.name}` : paramType.type,
onChange: (value: any) => {
setForm(form => ({ ...form, [stateObjectKey]: value }));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { ReadOnlyFunctionForm } from "./ReadOnlyFunctionForm";
import { Abi, AbiFunction } from "abitype";
import { Contract, ContractName } from "~~/utils/scaffold-eth/contract";

export const ContractReadMethods = ({ deployedContractData }: { deployedContractData: Contract<ContractName> }) => {
if (!deployedContractData) {
return null;
}

const functionsToDisplay = (
((deployedContractData.abi || []) as Abi).filter(part => part.type === "function") as AbiFunction[]
).filter(fn => {
const isQueryableWithParams =
(fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length > 0;
return isQueryableWithParams;
});

if (!functionsToDisplay.length) {
return <>No read methods</>;
}

return (
<>
{functionsToDisplay.map(fn => (
<ReadOnlyFunctionForm contractAddress={deployedContractData.address} abiFunction={fn} key={fn.name} />
))}
</>
);
};
52 changes: 15 additions & 37 deletions packages/nextjs/components/scaffold-eth/Contract/ContractUI.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
import { useMemo, useState } from "react";
import { Abi } from "abitype";
import { useContract, useProvider } from "wagmi";
import { useReducer } from "react";
import { ContractReadMethods } from "./ContractReadMethods";
import { ContractVariables } from "./ContractVariables";
import { ContractWriteMethods } from "./ContractWriteMethods";
import { Spinner } from "~~/components/Spinner";
import {
Address,
Balance,
getAllContractFunctions,
getContractReadOnlyMethodsWithParams,
getContractVariablesAndNoParamsReadMethods,
getContractWriteMethods,
} from "~~/components/scaffold-eth";
import { Address, Balance } from "~~/components/scaffold-eth";
import { useDeployedContractInfo, useNetworkColor } from "~~/hooks/scaffold-eth";
import { getTargetNetwork } from "~~/utils/scaffold-eth";
import { ContractName } from "~~/utils/scaffold-eth/contract";
Expand All @@ -23,34 +17,12 @@ type ContractUIProps = {
* UI component to interface with deployed contracts.
**/
export const ContractUI = ({ contractName, className = "" }: ContractUIProps) => {
const provider = useProvider();
const [refreshDisplayVariables, setRefreshDisplayVariables] = useState(false);
const [refreshDisplayVariables, triggerRefreshDisplayVariables] = useReducer(value => !value, false);
const configuredNetwork = getTargetNetwork();

const { data: deployedContractData, isLoading: deployedContractLoading } = useDeployedContractInfo(contractName);
const networkColor = useNetworkColor();

const contract = useContract({
address: deployedContractData?.address,
abi: deployedContractData?.abi as Abi,
signerOrProvider: provider,
});

const displayedContractFunctions = useMemo(() => getAllContractFunctions(contract), [contract]);

const contractVariablesDisplay = useMemo(() => {
return getContractVariablesAndNoParamsReadMethods(contract, displayedContractFunctions, refreshDisplayVariables);
}, [contract, displayedContractFunctions, refreshDisplayVariables]);

const contractMethodsDisplay = useMemo(
() => getContractReadOnlyMethodsWithParams(contract, displayedContractFunctions),
[contract, displayedContractFunctions],
);
const contractWriteMethods = useMemo(
() => getContractWriteMethods(contract, displayedContractFunctions, setRefreshDisplayVariables),
[contract, displayedContractFunctions],
);

if (deployedContractLoading) {
return (
<div className="mt-14">
Expand Down Expand Up @@ -90,7 +62,10 @@ export const ContractUI = ({ contractName, className = "" }: ContractUIProps) =>
)}
</div>
<div className="bg-base-300 rounded-3xl px-6 lg:px-8 py-4 shadow-lg shadow-base-300">
{contractVariablesDisplay.methods.length > 0 ? contractVariablesDisplay.methods : "No contract variables"}
<ContractVariables
refreshDisplayVariables={refreshDisplayVariables}
deployedContractData={deployedContractData}
/>
</div>
</div>
<div className="col-span-1 lg:col-span-2 flex flex-col gap-6">
Expand All @@ -102,7 +77,7 @@ export const ContractUI = ({ contractName, className = "" }: ContractUIProps) =>
</div>
</div>
<div className="p-5 divide-y divide-base-300">
{contractMethodsDisplay.methods.length > 0 ? contractMethodsDisplay.methods : "No read methods"}
<ContractReadMethods deployedContractData={deployedContractData} />
</div>
</div>
</div>
Expand All @@ -114,7 +89,10 @@ export const ContractUI = ({ contractName, className = "" }: ContractUIProps) =>
</div>
</div>
<div className="p-5 divide-y divide-base-300">
{contractWriteMethods.methods.length > 0 ? contractWriteMethods.methods : "No write methods"}
<ContractWriteMethods
deployedContractData={deployedContractData}
onChange={triggerRefreshDisplayVariables}
/>
</div>
</div>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { DisplayVariable } from "./DisplayVariable";
import { Abi, AbiFunction } from "abitype";
import { Contract, ContractName } from "~~/utils/scaffold-eth/contract";

export const ContractVariables = ({
refreshDisplayVariables,
deployedContractData,
}: {
refreshDisplayVariables: boolean;
deployedContractData: Contract<ContractName>;
}) => {
if (!deployedContractData) {
return null;
}

const functionsToDisplay = (
(deployedContractData.abi as Abi).filter(part => part.type === "function") as AbiFunction[]
).filter(fn => {
const isQueryableWithNoParams =
(fn.stateMutability === "view" || fn.stateMutability === "pure") && fn.inputs.length === 0;
return isQueryableWithNoParams;
});

if (!functionsToDisplay.length) {
return <>No contract variables</>;
}

return (
<>
{functionsToDisplay.map(fn => (
<DisplayVariable
abiFunction={fn}
contractAddress={deployedContractData.address}
key={fn.name}
refreshDisplayVariables={refreshDisplayVariables}
/>
))}
</>
);
};
Loading

0 comments on commit 7d3e47b

Please sign in to comment.