Skip to content
This repository has been archived by the owner on Jun 6, 2024. It is now read-only.

allow full local testing with anvil integration #27

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/app/.env
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
CHAIN_ID=5
NEXT_PUBLIC_CHAIN_SLUGS=
NEXT_PUBLIC_CHAIN_STATUS=
NEXT_PUBLIC_ALCHEMY_API_KEY=
8 changes: 8 additions & 0 deletions packages/app/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
module.exports = {
preset: "ts-jest",
testEnvironment: "node",
transform: {
"^.+\\.ts?$": "ts-jest",
},
transformIgnorePatterns: ["<rootDir>/node_modules/"],
};
6 changes: 5 additions & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,21 +11,25 @@
"start": "next start",
"lint": "eslint .",
"codegen": "graphql-codegen",
"prettier": "prettier --write src"
"prettier": "prettier --write src",
"test": "jest"
},
"dependencies": {
"@ethersproject/address": "^5.5.0",
"@ethersproject/contracts": "^5.5.0",
"@ethersproject/providers": "^5.5.0",
"@rainbow-me/rainbowkit": "^0.4.1",
"@types/jest": "^28.1.4",
"@web3-scaffold/contracts": "workspace:*",
"classnames": "^2.3.1",
"ethers": "^5.5.2",
"graphql": "^16.2.0",
"jest": "^28.1.2",
"next": "12.1.0",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-toastify": "^9.0.4",
"ts-jest": "^28.0.5",
"urql": "^2.0.6",
"wagmi": "^0.5.5",
"zustand": "^3.6.7"
Expand Down
15 changes: 11 additions & 4 deletions packages/app/src/EthereumProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,23 @@
import "@rainbow-me/rainbowkit/styles.css";

import { getDefaultWallets, RainbowKitProvider } from "@rainbow-me/rainbowkit";
import { chain, configureChains, createClient, WagmiConfig } from "wagmi";
import { configureChains, createClient, WagmiConfig } from "wagmi";
import { alchemyProvider } from "wagmi/providers/alchemy";
import { publicProvider } from "wagmi/providers/public";

export const targetChainId = parseInt(process.env.CHAIN_ID!) || 5;
import { getChains } from "./utils/getChains";

const targetChains = getChains();

// Use the first chain specified as target chain. Maybe in the future this can be configurable.
export const targetChainId = targetChains[0].id;

export const { chains, provider, webSocketProvider } = configureChains(
[chain.mainnet, chain.goerli],
targetChains,
[
alchemyProvider({ alchemyId: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY! }),
alchemyProvider({
alchemyId: process.env.NEXT_PUBLIC_ALCHEMY_API_KEY,
}),
publicProvider(),
]
);
Expand Down
7 changes: 4 additions & 3 deletions packages/app/src/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import ExampleNFTGoerli from "@web3-scaffold/contracts/deploys/goerli/ExampleNFT.json";
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm trying to figure out a good way of allowing the chain to be specified here. 🤔

import ExampleNFTFoundry from "@web3-scaffold/contracts/deploys/foundry/ExampleNFT.json";
// import ExampleNFTGoerli from "@web3-scaffold/contracts/deploys/goerli/ExampleNFT.json";
import { ExampleNFT__factory } from "@web3-scaffold/contracts/types";
import { useContractRead } from "wagmi";

Expand All @@ -14,7 +15,7 @@ import { provider, targetChainId } from "./EthereumProviders";
// ) as ExampleNFT;

export const exampleNFTContract = ExampleNFT__factory.connect(
ExampleNFTGoerli.deployedTo,
ExampleNFTFoundry.deployedTo,
provider({ chainId: targetChainId })
);

Expand All @@ -26,6 +27,6 @@ export const useExampleNFTContractRead = (
) =>
useContractRead({
...readConfig,
addressOrName: ExampleNFTGoerli.deployedTo,
addressOrName: ExampleNFTFoundry.deployedTo,
contractInterface: ExampleNFT__factory.abi,
});
3 changes: 2 additions & 1 deletion packages/app/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@ import {

import { EthereumProviders } from "../EthereumProviders";

// @TODO Set the Graph Node hostname via environment variable?
export const graphClient = createGraphClient({
url: "https://api.thegraph.com/subgraphs/name/holic/example-nft",
url: "http://127.0.0.1:8000/subgraphs/name/holic/example-nft",
});

const MyApp = ({ Component, pageProps }: AppProps) => {
Expand Down
3 changes: 2 additions & 1 deletion packages/app/src/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useExampleNFTContractRead } from "../contracts";
import { Inventory } from "../Inventory";
import { MintButton } from "../MintButton";
import { useIsMounted } from "../useIsMounted";
import { getChainStatus } from "../utils/getChainStatus";

const HomePage: NextPage = () => {
const totalSupply = useExampleNFTContractRead({
Expand All @@ -18,7 +19,7 @@ const HomePage: NextPage = () => {
return (
<div className="min-h-screen flex flex-col">
<div className="self-end p-2">
<ConnectButton />
<ConnectButton chainStatus={getChainStatus()} />
</div>
<div className="flex-grow flex flex-col gap-4 items-center justify-center p-8 pb-[50vh]">
<h1 className="text-4xl">Example NFT</h1>
Expand Down
24 changes: 24 additions & 0 deletions packages/app/src/utils/getChainStatus.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getChainStatus } from "./getChainStatus";

describe("getChainStatus", () => {
const env = process.env;

it("should get default chain status when env is empty", () => {
process.env = { ...env, NEXT_PUBLIC_CHAIN_STATUS: "" };
expect(getChainStatus()).toStrictEqual("icon");
});

it("should get chain status env is set", () => {
process.env = { ...env, NEXT_PUBLIC_CHAIN_STATUS: "none" };
expect(getChainStatus()).toStrictEqual("none");
});

it("should ignore invalid chain status when set", () => {
process.env = { ...env, NEXT_PUBLIC_CHAIN_STATUS: "fakestatus" };
expect(getChainStatus()).toStrictEqual("icon");
});

afterEach(() => {
process.env = env;
});
});
14 changes: 14 additions & 0 deletions packages/app/src/utils/getChainStatus.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// Note: If RainbowKit ever adds more chain status options, add them here.
const chainStatuses = ["full", "icon", "name", "none"] as const;
type ChainStatus = typeof chainStatuses[number];

const isValidChainStatus = (value: string): value is ChainStatus => {
return chainStatuses.includes(value as ChainStatus);
};

export const getChainStatus = (): ChainStatus => {
return process.env.NEXT_PUBLIC_CHAIN_STATUS &&
isValidChainStatus(process.env.NEXT_PUBLIC_CHAIN_STATUS)
? process.env.NEXT_PUBLIC_CHAIN_STATUS
: "icon";
};
30 changes: 30 additions & 0 deletions packages/app/src/utils/getChains.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { getChainSlugs } from "./getChains";

describe("getChains", () => {
const env = process.env;

it("should get default chains when chain slugs is empty", () => {
process.env = { ...env, NEXT_PUBLIC_CHAIN_SLUGS: "" };
expect(getChainSlugs()).toStrictEqual(["goerli", "mainnet"]);
});

it("should get chains when chain slugs is set", () => {
process.env = {
...env,
NEXT_PUBLIC_CHAIN_SLUGS: "foundry, mainnet,optimism",
};
expect(getChainSlugs()).toStrictEqual(["foundry", "mainnet", "optimism"]);
});

it("should ignore invalid chain slugs when set", () => {
process.env = {
...env,
NEXT_PUBLIC_CHAIN_SLUGS: "foundry, mainnet,fakechain",
};
expect(getChainSlugs()).toStrictEqual(["foundry", "mainnet"]);
});

afterEach(() => {
process.env = env;
});
});
32 changes: 32 additions & 0 deletions packages/app/src/utils/getChains.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { chain } from "wagmi";

const isValidChain = (value: string): value is keyof typeof chain => {
return [
"mainnet",
"ropsten",
"rinkeby",
"goerli",
"kovan",
"optimism",
"optimismKovan",
"polygon",
"polygonMumbai",
"arbitrum",
"arbitrumRinkeby",
"localhost",
"hardhat",
"foundry",
].includes(value);
};

export const getChainSlugs = () => {
const targetChainSlugs = process.env.NEXT_PUBLIC_CHAIN_SLUGS
? process.env.NEXT_PUBLIC_CHAIN_SLUGS.split(",").map((slug) => slug.trim())
: ["goerli", "mainnet"];
return targetChainSlugs.filter(isValidChain);
};

export const getChains = () => {
const chainSlugs = getChainSlugs();
return chainSlugs.map((chainSlug) => chain[chainSlug]);
};
5 changes: 5 additions & 0 deletions packages/contracts/deploys/foundry/ExampleNFT.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"deployedTo": "0x5fbdb2315678afecb367f032d93f642f64180aa3",
"deployer": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
"transactionHash": "0x1e46f02b55c070063b76cf84bfbc81fea73fb6e6b50ef8d37d5de628d71e6890"
}
1 change: 1 addition & 0 deletions packages/subgraph/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"codegen": "graph codegen subgraph*.yaml",
"build": "pnpm codegen && graph build subgraph*.yaml",
"deploy:goerli": "graph deploy --node https://api.thegraph.com/deploy/ holic/example-nft subgraph-goerli.yaml",
"deploy:local": "graph deploy --ipfs http://127.0.0.1:5001 --node http://127.0.0.1:8020 holic/example-nft subgraph-foundry.yaml",
"prettier": "prettier --write src",
"lint": "eslint src"
},
Expand Down
24 changes: 24 additions & 0 deletions packages/subgraph/subgraph-foundry.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
specVersion: 0.0.4
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: ExampleNFT
network: foundry
source:
abi: ExampleNFT
address: "0x5fbdb2315678afecb367f032d93f642f64180aa3"
startBlock: 0
mapping:
kind: ethereum/events
apiVersion: 0.0.5
language: wasm/assemblyscript
entities:
- NFT
abis:
- name: ExampleNFT
file: ../contracts/out/ExampleNFT.sol/ExampleNFT.abi.json
eventHandlers:
- event: Transfer(indexed address,indexed address,indexed uint256)
handler: handleTransfer
file: ./src/mapping.ts
Loading