Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make contract and hook types work without a generated output #255

Merged
merged 9 commits into from
Mar 24, 2023
1 change: 1 addition & 0 deletions packages/hardhat/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ coverage
coverage.json
typechain
typechain-types
generated

#Hardhat files
cache
Expand Down
4 changes: 2 additions & 2 deletions packages/hardhat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"account": "hardhat run scripts/listAccount.ts",
"chain": "hardhat node --network hardhat --no-deploy",
"compile": "hardhat compile",
"deploy": "hardhat deploy --export-all ../nextjs/generated/hardhat_contracts.json \"$@\" && yarn generateTsAbis",
"deploy": "hardhat deploy --export-all ./generated/hardhat_contracts.json \"$@\" && yarn generateTsAbis",
"fork": "MAINNET_FORKING_ENABLED=true hardhat node --network hardhat --no-deploy",
"generate": "hardhat run scripts/generateAccount.ts",
"generateTsAbis": "hardhat run scripts/generateTsAbis.ts",
Expand Down Expand Up @@ -50,4 +50,4 @@
"envfile": "^6.18.0",
"qrcode": "^1.5.1"
}
}
}
9 changes: 4 additions & 5 deletions packages/hardhat/scripts/generateTsAbis.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
import * as fs from "fs";
//@ts-expect-error This script runs after `hardhat deploy --export` therefore its deterministic that it will present
import allGeneratedContracts from "../../nextjs/generated/hardhat_contracts.json";
import allGeneratedContracts from "../generated/hardhat_contracts.json";
import prettier from "prettier";

function main() {
const GENERATED_PATH = "../nextjs/generated/hardhat_contracts";
const TARGET_DIR = "../nextjs/generated/";

const fileContent = Object.entries(allGeneratedContracts).reduce((content, [chainId, chainConfig]) => {
return `${content}${parseInt(chainId).toFixed(0)}:${JSON.stringify(chainConfig, null, 2)},`;
}, "");

fs.mkdirSync(TARGET_DIR);
fs.writeFileSync(
`${GENERATED_PATH}.ts`,
`${TARGET_DIR}hardhat_contracts.ts`,
prettier.format(`export default {${fileContent}} as const;`, { parser: "typescript" }),
);
// remove json file due to ambiguity
fs.unlinkSync(`${GENERATED_PATH}.json`);
}

try {
Expand Down
3 changes: 2 additions & 1 deletion packages/nextjs/components/scaffold-eth/Faucet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { hardhat, localhost } from "wagmi/chains";
import { BanknotesIcon } from "@heroicons/react/24/outline";
import { Address, AddressInput, Balance, EtherInput, getParsedEthersError } from "~~/components/scaffold-eth";
import { useTransactor } from "~~/hooks/scaffold-eth";
import scaffoldConfig from "~~/scaffold.config";
import { getLocalProvider, notification } from "~~/utils/scaffold-eth";

// Account index to use from generated hardhat accounts.
Expand All @@ -27,7 +28,7 @@ export const Faucet = () => {
useEffect(() => {
const getFaucetAddress = async () => {
try {
if (provider) {
if (provider && scaffoldConfig.contracts) {
const accounts = await provider.listAccounts();
setFaucetAddress(accounts[FAUCET_ACCOUNT_INDEX]);
}
Expand Down
43 changes: 29 additions & 14 deletions packages/nextjs/hooks/scaffold-eth/contract.types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import contracts from "../../generated/hardhat_contracts";
import { Abi, AbiParametersToPrimitiveTypes, ExtractAbiEvent, ExtractAbiEventNames, ExtractAbiFunction } from "abitype";
import type { ExtractAbiFunctionNames } from "abitype";
import { UseContractReadConfig, UseContractWriteConfig } from "wagmi";
import scaffoldConfig from "~~/scaffold.config";
import scaffoldConfig, { ScaffoldConfig } from "~~/scaffold.config";

export type Chain = keyof typeof contracts;
export type GenericContractsDeclaration = Exclude<ScaffoldConfig["contracts"], null>;

type SelectedChainId = (typeof scaffoldConfig)["targetNetwork"]["id"];
const contracts = scaffoldConfig.contracts;

type Contracts = (typeof contracts)[SelectedChainId][0]["contracts"];
type IsContractsFileMissing<TYes, TNo> = typeof contracts extends null ? TYes : TNo;
type ContractsDeclaration = IsContractsFileMissing<GenericContractsDeclaration, typeof contracts>;

export type Chain = keyof ContractsDeclaration;

type SelectedChainId = IsContractsFileMissing<number, (typeof scaffoldConfig)["targetNetwork"]["id"]>;

type Contracts = ContractsDeclaration[SelectedChainId][0]["contracts"];

export type ContractName = keyof Contracts;

Expand All @@ -32,9 +38,10 @@ export type AbiFunctionOutputs<TAbi extends Abi, TFunctionName extends string> =
TFunctionName
>["outputs"];

export type AbiFunctionReturnType<TAbi extends Abi, TFunctionName extends string> = AbiParametersToPrimitiveTypes<
AbiFunctionOutputs<TAbi, TFunctionName>
>[0];
export type AbiFunctionReturnType<TAbi extends Abi, TFunctionName extends string> = IsContractsFileMissing<
any,
AbiParametersToPrimitiveTypes<AbiFunctionOutputs<TAbi, TFunctionName>>[0]
>;

export type AbiEventInputs<TAbi extends Abi, TEventName extends ExtractAbiEventNames<TAbi>> = ExtractAbiEvent<
TAbi,
Expand Down Expand Up @@ -111,16 +118,24 @@ export type UseScaffoldReadConfig<
TFunctionName extends ExtractAbiFunctionNames<ContractAbi<TContractName>, ReadAbiStateMutability>,
> = {
contractName: TContractName;
functionName: TFunctionName;
} & UseScaffoldArgsParam<TContractName, ReadAbiStateMutability, TFunctionName> &
RestConfigParam<ReadAbiStateMutability>;
} & IsContractsFileMissing<
Partial<UseContractReadConfig>,
{
functionName: TFunctionName;
} & UseScaffoldArgsParam<TContractName, ReadAbiStateMutability, TFunctionName> &
RestConfigParam<ReadAbiStateMutability>
>;

export type UseScaffoldWriteConfig<
TContractName extends ContractName,
TFunctionName extends ExtractAbiFunctionNames<ContractAbi<TContractName>, WriteAbiStateMutability>,
> = {
contractName: TContractName;
functionName: TFunctionName;
value?: string;
} & UseScaffoldArgsParam<TContractName, WriteAbiStateMutability, TFunctionName> &
RestConfigParam<WriteAbiStateMutability>;
} & IsContractsFileMissing<
Partial<UseContractWriteConfig>,
{
functionName: TFunctionName;
} & UseScaffoldArgsParam<TContractName, WriteAbiStateMutability, TFunctionName> &
RestConfigParam<WriteAbiStateMutability>
>;
5 changes: 5 additions & 0 deletions packages/nextjs/hooks/scaffold-eth/useAutoConnect.ts
sverps marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useEffect } from "react";
import { useEffectOnce, useLocalStorage } from "usehooks-ts";
import { Connector, useAccount, useConnect } from "wagmi";
import { hardhat } from "wagmi/chains";
import scaffoldConfig from "~~/scaffold.config";
import { burnerWalletId, defaultBurnerChainId } from "~~/services/web3/wagmi-burner/BurnerConnector";
import { getTargetNetwork } from "~~/utils/scaffold-eth";

Expand Down Expand Up @@ -77,6 +78,10 @@ export const useAutoConnect = (config: TAutoConnect): void => {
}, [accountState.isConnected, accountState.connector?.name]);

useEffectOnce(() => {
if (!scaffoldConfig.contracts) {
return;
}

const initialConnector = getInitialConnector(config, walletId, connectState.connectors);

if (initialConnector?.connector) {
Expand Down
9 changes: 4 additions & 5 deletions packages/nextjs/hooks/scaffold-eth/useDeployedContractInfo.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { useEffect, useState } from "react";
import { Contract, ContractCodeStatus, ContractName } from "./contract.types";
import { Contract, ContractCodeStatus, ContractName, GenericContractsDeclaration } from "./contract.types";
import { useIsMounted } from "usehooks-ts";
import { useProvider } from "wagmi";
import contracts from "~~/generated/hardhat_contracts";
import scaffoldConfig from "~~/scaffold.config";

/**
Expand All @@ -11,9 +10,9 @@ import scaffoldConfig from "~~/scaffold.config";
*/
export const useDeployedContractInfo = <TContractName extends ContractName>(contractName: TContractName) => {
const isMounted = useIsMounted();
const deployedContract = contracts[scaffoldConfig.targetNetwork.id]?.[0]?.contracts?.[
contractName as ContractName
] as Contract<TContractName>;
const deployedContract = (scaffoldConfig.contracts as unknown as GenericContractsDeclaration)?.[
scaffoldConfig.targetNetwork.id
]?.[0]?.contracts?.[contractName as ContractName] as Contract<TContractName>;
const [status, setStatus] = useState<ContractCodeStatus>(ContractCodeStatus.LOADING);
const provider = useProvider({ chainId: scaffoldConfig.targetNetwork.id });

Expand Down
19 changes: 18 additions & 1 deletion packages/nextjs/scaffold.config.ts
sverps marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { Abi } from "abitype";
import * as chains from "wagmi/chains";
import contracts from "~~/generated/hardhat_contracts";
sverps marked this conversation as resolved.
Show resolved Hide resolved

type ScaffoldConfig = {
export type ScaffoldConfig = {
targetNetwork: chains.Chain;
pollingInterval: number;
alchemyApiKey: string;
contracts: null | {
[key: number]: readonly {
name: string;
chainId: string;
contracts: {
[key: string]: {
address: string;
abi: Abi;
};
};
}[];
};
};

const scaffoldConfig = {
Expand All @@ -19,6 +33,9 @@ const scaffoldConfig = {
// It's recommended to store it in an env variable:
// .env.local for local testing, and in the Vercel/system env config for live apps.
alchemyApiKey: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY || "oKxs-03sij-U_N0iOlrSsZFr29-IqbuF",

// This is the contract data that was generated by "yarn deploy", or null if no local contracts are used
contracts,
sverps marked this conversation as resolved.
Show resolved Hide resolved
} satisfies ScaffoldConfig;

export default scaffoldConfig;
9 changes: 6 additions & 3 deletions packages/nextjs/utils/scaffold-eth/contractNames.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import contracts from "~~/generated/hardhat_contracts";
import { ContractName } from "~~/hooks/scaffold-eth/contract.types";
import { ContractName, GenericContractsDeclaration } from "~~/hooks/scaffold-eth/contract.types";
import scaffoldConfig from "~~/scaffold.config";

export function getContractNames() {
const contractsData = contracts[scaffoldConfig.targetNetwork.id]?.[0]?.contracts;
if (!scaffoldConfig.contracts) {
return [];
}
const contractsData = (scaffoldConfig.contracts as GenericContractsDeclaration)[scaffoldConfig.targetNetwork.id]?.[0]
?.contracts;
return contractsData ? (Object.keys(contractsData) as ContractName[]) : [];
}