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 all 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
16 changes: 13 additions & 3 deletions packages/app/.env
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,19 @@
#
# You are welcome to use other files, but remember to add them to the .gitignore file!

# Find your chainID here: https://chainlist.org/
# By default we use 5, or the Goerli network
CHAIN_ID=5
# Find your chain slugs here: https://github.com/tmm/wagmi/blob/main/packages/core/src/constants/chains.ts#L5-L20
# By default we use the Goerli network, with mainnet also enabled for ENS resolution
NEXT_PUBLIC_CHAIN_SLUGS=

# RainbowKit supports the following values for chain status: full, icon, name, and none
# Since we sometimes bring in mainnet just for ENS, you might want to make this 'none' for an L2 app
NEXT_PUBLIC_CHAIN_STATUS=

# Provide your ALCHEMY API Key here
NEXT_PUBLIC_ALCHEMY_API_KEY=

# Provide your subgraph URL for The Graph here, such as if you are using a local node
NEXT_PUBLIC_SUBGRAPH_URL=

# Provide your contract address here
NEXT_PUBLIC_CONTRACT_ADDRESS=
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 || "0") || 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
10 changes: 6 additions & 4 deletions packages/app/src/contracts.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
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 { ExampleNFT__factory } from "@web3-scaffold/contracts/types";
import { useContractRead } from "wagmi";

import { provider, targetChainId } from "./EthereumProviders";
import { getContractAddress } from "./utils/getContractAddress";

// I would have used `ExampleNFT__factory.connect` to create this, but we may
// not have a provider ready to go. Any interactions with this contract should
// use `exampleNFTContract.connect(providerOrSigner)` first.

// export const exampleNFTContract = new Contract(
// ExampleNFTGoerli.deployedTo,
// getContractAddress(),
// ExampleNFT__factory.abi
// ) as ExampleNFT;

const contractAddress = getContractAddress();

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

Expand All @@ -26,6 +28,6 @@ export const useExampleNFTContractRead = (
) =>
useContractRead({
...readConfig,
addressOrName: ExampleNFTGoerli.deployedTo,
addressOrName: contractAddress,
contractInterface: ExampleNFT__factory.abi,
});
5 changes: 4 additions & 1 deletion packages/app/src/pages/_app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import {
} from "urql";

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

const subgraphURL = getSubgraphURL();

export const graphClient = createGraphClient({
url: "https://api.thegraph.com/subgraphs/name/holic/example-nft",
url: subgraphURL,
});

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]);
};
27 changes: 27 additions & 0 deletions packages/app/src/utils/getContractAddress.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getContractAddress } from "./getContractAddress";

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

it("should get default contract address", () => {
process.env = { ...env, NEXT_PUBLIC_CONTRACT_ADDRESS: "" };
expect(getContractAddress()).toBe(
"0xe584409f2ba1ade9895485d90587fd46baa3c0d8"
);
});

it("should get contract address from env when set", () => {
process.env = {
...env,
NEXT_PUBLIC_CONTRACT_ADDRESS:
"0x5FbDB2315678afecb367f032d93F642f64180aa3",
};
expect(getContractAddress()).toBe(
"0x5FbDB2315678afecb367f032d93F642f64180aa3"
);
});

afterEach(() => {
process.env = env;
});
});
5 changes: 5 additions & 0 deletions packages/app/src/utils/getContractAddress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const getContractAddress = (): string => {
return process.env.NEXT_PUBLIC_CONTRACT_ADDRESS
? process.env.NEXT_PUBLIC_CONTRACT_ADDRESS
: "0xe584409f2ba1ade9895485d90587fd46baa3c0d8";
};
27 changes: 27 additions & 0 deletions packages/app/src/utils/getSubgraphURL.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getSubgraphURL } from "./getSubgraphURL";

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

it("should get default graph host", () => {
process.env = { ...env, NEXT_PUBLIC_SUBGRAPH_URL: "" };
expect(getSubgraphURL()).toBe(
"https://api.thegraph.com/subgraphs/name/holic/example-nft"
);
});

it("should get graph host from env when set", () => {
process.env = {
...env,
NEXT_PUBLIC_SUBGRAPH_URL:
"http://127.0.0.1:8000/subgraphs/name/holic/example-nft",
};
expect(getSubgraphURL()).toBe(
"http://127.0.0.1:8000/subgraphs/name/holic/example-nft"
);
});

afterEach(() => {
process.env = env;
});
});
5 changes: 5 additions & 0 deletions packages/app/src/utils/getSubgraphURL.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const getSubgraphURL = (): string => {
return process.env.NEXT_PUBLIC_SUBGRAPH_URL
? process.env.NEXT_PUBLIC_SUBGRAPH_URL
: "https://api.thegraph.com/subgraphs/name/holic/example-nft";
};
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