diff --git a/.gitignore b/.gitignore index 6f84b1f..8c256fc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,5 @@ node_modules !.yarn/sdks !.yarn/versions .eslintcache +.vscode/** +.DS_Store diff --git a/README.md b/README.md index 812757d..8ec4343 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # se-2 -A new version of scaffold-eth with its core functionality. Using NextJS, RainbowKit & Wagmi. - +A new version of scaffold-eth with its core functionality. Using NextJS, RainbowKit, Wagmi and Typescript. ## Set up @@ -25,6 +24,7 @@ yarn chain ``` 3rd terminal, deploy test contract + ``` yarn deploy ``` diff --git a/package.json b/package.json index 75600b9..94ff1dc 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "hardhat:test": "yarn workspace @se-2/hardhat test", "start": "yarn workspace @se-2/frontend dev", "next:lint": "yarn workspace @se-2/frontend lint", - "next:format" : "yarn workspace @se-2/frontend format", + "next:format": "yarn workspace @se-2/frontend format", "postinstall": "husky install", "precommit": "lint-staged" }, diff --git a/packages/frontend/.eslintrc.json b/packages/frontend/.eslintrc.json index 6f2873c..360d509 100644 --- a/packages/frontend/.eslintrc.json +++ b/packages/frontend/.eslintrc.json @@ -1,6 +1,12 @@ { "extends": ["next/core-web-vitals", "plugin:prettier/recommended"], "rules": { - "no-unused-vars": "error" + "no-unused-vars": "error", + "prettier/prettier": [ + "warn", + { + "endOfLine": "auto" + } + ] } } diff --git a/packages/frontend/components/hooks/index.ts b/packages/frontend/components/hooks/index.ts new file mode 100644 index 0000000..3f2266c --- /dev/null +++ b/packages/frontend/components/hooks/index.ts @@ -0,0 +1,2 @@ +export * from "./useAutoConnect"; +export * from "./useBurnerWallet"; diff --git a/packages/frontend/components/hooks/useAutoConnect.ts b/packages/frontend/components/hooks/useAutoConnect.ts new file mode 100644 index 0000000..e7835ce --- /dev/null +++ b/packages/frontend/components/hooks/useAutoConnect.ts @@ -0,0 +1,79 @@ +import { useEffect } from "react"; +import { Connector, useAccount, useConnect } from "wagmi"; +import { useEffectOnce, useLocalStorage } from "usehooks-ts"; +import { burnerWalletId, defaultBurnerChainId } from "~~/web3/wagmi-burner"; + +export type TAutoConnect = { + /** + * Enable the burner wallet. If this is disabled, burner wallet is entierly disabled + */ + enableBurnerWallet: boolean; + /** + * Auto connect: + * 1. If the user was connected into a wallet before, on page reload reconnect automatically + * 2. If user is not connected to any wallet: On reload, connect to burner wallet + */ + autoConnect: boolean; +}; + +const walletIdStorageKey = "scaffoldEth2.wallet"; + +/** + * This function will get the initial connector (if any), the app will connect to + * @param config + * @param previousWalletId + * @param connectors + * @returns + */ +const getInitialConnector = ( + config: TAutoConnect, + previousWalletId: string, + connectors: Connector[], +): { connector: Connector | undefined; chainId?: number } | undefined => { + const allowBurner = config.enableBurnerWallet; + + if (!previousWalletId) { + // The user was not connected to a wallet + if (allowBurner && config.autoConnect) { + const connector = connectors.find(f => f.id === burnerWalletId); + return { connector, chainId: defaultBurnerChainId }; + } + } else { + // the user was connected to wallet + if (config.autoConnect) { + const connector = connectors.find(f => f.id === previousWalletId); + return { connector }; + } + } + + return undefined; +}; + +/** + * Automatically connect to a wallet/connector based on config and prior wallet + * @param config + */ +export const useAutoConnect = (config: TAutoConnect): void => { + const [walletId, setWalletId] = useLocalStorage(walletIdStorageKey, ""); + const connectState = useConnect(); + const accountState = useAccount(); + + useEffect(() => { + if (accountState.isConnected) { + // user is connected, set walletName + setWalletId(accountState.connector?.id ?? ""); + } else { + // user has disconnected, reset walletName + setWalletId(""); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [accountState.isConnected, accountState.connector?.name]); + + useEffectOnce(() => { + const initialConnector = getInitialConnector(config, walletId, connectState.connectors); + + if (initialConnector?.connector) { + connectState.connect({ connector: initialConnector.connector, chainId: initialConnector.chainId }); + } + }); +}; diff --git a/packages/frontend/components/hooks/useBurnerWallet.ts b/packages/frontend/components/hooks/useBurnerWallet.ts new file mode 100644 index 0000000..05a21ea --- /dev/null +++ b/packages/frontend/components/hooks/useBurnerWallet.ts @@ -0,0 +1,160 @@ +import { BytesLike, ethers, Signer, Wallet } from "ethers"; +import { useEffect, useCallback, useRef } from "react"; +import { useProvider } from "wagmi"; +import { useDebounce } from "use-debounce"; +import { useLocalStorage } from "usehooks-ts"; + +const burnerStorageKey = "scaffoldEth2.burnerWallet.sk"; + +/** + * Is the private key valid + * @internal + * @param pk + * @returns + */ +const isValidSk = (pk: BytesLike | undefined | null): boolean => { + return pk?.length === 64 || pk?.length === 66; +}; + +/** + * If no burner is found in localstorage, we will use a new default wallet + */ +const newDefaultWallet = ethers.Wallet.createRandom(); + +/** + * Save the current burner private key from storage + * Can be used outside of react. Used by the burnerConnector. + * @internal + * @returns + */ +export const saveBurnerSK = (wallet: Wallet): void => { + if (typeof window != "undefined" && window != null) { + window?.localStorage?.setItem(burnerStorageKey, wallet.privateKey); + } +}; + +/** + * Gets the current burner private key from storage + * Can be used outside of react. Used by the burnerConnector. + * @internal + * @returns + */ +export const loadBurnerSK = (): string => { + let currentSk = ""; + if (typeof window != "undefined" && window != null) { + currentSk = window?.localStorage?.getItem?.(burnerStorageKey)?.replaceAll('"', "") ?? ""; + } + + if (!!currentSk && isValidSk(currentSk)) { + return currentSk; + } else { + saveBurnerSK(newDefaultWallet); + return newDefaultWallet.privateKey; + } +}; + +/** + * #### Summary + * Return type of useBurnerSigner: + * + * ##### ✏️ Notes + * - provides signer + * - methods of interacting with burner signer + * - methods to save and loadd signer from local storage + * + * @category Hooks + */ +export type TBurnerSigner = { + signer: Signer | undefined; + account: string | undefined; + /** + * create a new burner signer + */ + generateNewBurner: () => void; + /** + * explictly save burner to storage + */ + saveBurner: () => void; +}; + +/** + * #### Summary + * A hook that creates a burner signer/address and provides ways of interacting with + * and updating the signer + * + * @category Hooks + * + * @param localProvider localhost provider + * @returns IBurnerSigner + */ +export const useBurnerWallet = (): TBurnerSigner => { + const [burnerSk, setBurnerSk] = useLocalStorage(burnerStorageKey, newDefaultWallet.privateKey); + + const provider = useProvider(); + const walletRef = useRef(); + const isCreatingNewBurnerRef = useRef(false); + + const [signer] = useDebounce(walletRef.current, 200, { + trailing: true, + equalityFn: (a, b) => a?.address === b?.address && a != null && b != null, + }); + const account = walletRef.current?.address; + + /** + * callback to save current wallet sk + */ + const saveBurner = useCallback(() => { + setBurnerSk(walletRef.current?.privateKey ?? ""); + }, [setBurnerSk]); + + /** + * create a new burnerkey + */ + const generateNewBurner = useCallback(() => { + if (provider && !isCreatingNewBurnerRef.current) { + console.log("🔑 Create new burner wallet..."); + isCreatingNewBurnerRef.current = true; + + const wallet = Wallet.createRandom().connect(provider); + setBurnerSk(() => { + console.log("🔥 ...Save new burner wallet"); + isCreatingNewBurnerRef.current = false; + return wallet.privateKey; + }); + return wallet; + } else { + console.log("⚠ Could not create burner wallet"); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [provider?.network?.chainId]); + + /** + * Load wallet with burnerSk + * connect and set wallet, once we have burnerSk and valid provider + */ + useEffect(() => { + if (burnerSk && provider.network.chainId) { + let wallet: Wallet | undefined = undefined; + if (isValidSk(burnerSk)) { + wallet = new ethers.Wallet(burnerSk, provider); + } else { + wallet = generateNewBurner?.(); + } + + if (wallet == null) { + throw "Error: Could not create burner wallet"; + } + walletRef.current = wallet; + saveBurner?.(); + } + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [burnerSk, provider?.network?.chainId]); + + return { + signer, + account, + generateNewBurner, + saveBurner, + }; +}; diff --git a/packages/frontend/components/useTempTestContract.tsx b/packages/frontend/components/useTempTestContract.tsx new file mode 100644 index 0000000..c150fc3 --- /dev/null +++ b/packages/frontend/components/useTempTestContract.tsx @@ -0,0 +1,40 @@ +/* eslint-disable no-unused-vars */ +import { useEffect } from "react"; +import { usePrepareContractWrite, useContractWrite, useContractRead, chain } from "wagmi"; +import { tempContract } from "~~/generated/tempContract"; + +// todo remove this, this is until we have contract element + +const testChainId = chain.hardhat.id; + +export const useTempTestContract = () => { + const cRead = useContractRead({ + addressOrName: tempContract.address, + contractInterface: tempContract.abi, + functionName: "purpose", + chainId: testChainId, + watch: true, + cacheOnBlock: false, + }); + + const cWrite = useContractWrite({ + mode: "recklesslyUnprepared", + addressOrName: tempContract.address, + contractInterface: tempContract.abi, + functionName: "setPurpose", + args: "new purpose", + chainId: testChainId, + }); + + useEffect(() => { + if (cRead.isSuccess) { + console.log("read contract: ", cRead.data); + } + }, [cRead.data, cRead.isSuccess]); + + const onClick = () => { + console.log("...attempting to write"); + cWrite.write?.(); + }; + return { onClick }; +}; diff --git a/packages/frontend/generated/tempContract.ts b/packages/frontend/generated/tempContract.ts new file mode 100644 index 0000000..9c7017d --- /dev/null +++ b/packages/frontend/generated/tempContract.ts @@ -0,0 +1,60 @@ +// todo remove this, this is until we have contract element + +/** + * This is just for testing. you have to deploy the contract yourself and change the address. + */ +export const tempContract = { + address: "0x5FbDB2315678afecb367f032d93F642f64180aa3", + abi: [ + { + inputs: [], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "sender", + type: "address", + }, + { + indexed: false, + internalType: "string", + name: "purpose", + type: "string", + }, + ], + name: "SetPurpose", + type: "event", + }, + { + inputs: [], + name: "purpose", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "newPurpose", + type: "string", + }, + ], + name: "setPurpose", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + ], +}; diff --git a/packages/frontend/next.config.js b/packages/frontend/next.config.js index 91ef62f..257dce6 100644 --- a/packages/frontend/next.config.js +++ b/packages/frontend/next.config.js @@ -1,3 +1,5 @@ +// @ts-check + /** @type {import('next').NextConfig} */ const nextConfig = { reactStrictMode: true, diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 98626d5..31edfa0 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -4,12 +4,15 @@ "version": "0.1.0", "scripts": { "dev": "next dev", + "start": "next dev", "build": "next build", - "start": "next start", + "serve": "next start", "lint": "next lint", "format": "prettier --write . '!(node_module|.next|contracts)/**/*'" }, "dependencies": { + "@ethersproject/networks": "^5.7.1", + "@ethersproject/web": "^5.7.1", "@heroicons/react": "^2.0.11", "@rainbow-me/rainbowkit": "^0.6.0", "@types/react-blockies": "^1.4.1", @@ -21,6 +24,8 @@ "react-blockies": "^1.4.1", "react-copy-to-clipboard": "^5.1.0", "react-dom": "^18.1.0", + "use-debounce": "^8.0.4", + "usehooks-ts": "^2.7.2", "wagmi": "^0.6.0" }, "devDependencies": { diff --git a/packages/frontend/pages/_app.tsx b/packages/frontend/pages/_app.tsx index 1195306..048549f 100644 --- a/packages/frontend/pages/_app.tsx +++ b/packages/frontend/pages/_app.tsx @@ -1,54 +1,17 @@ -import type { AppProps } from "next/app"; -import { RainbowKitProvider, connectorsForWallets, wallet } from "@rainbow-me/rainbowkit"; -import { chain, configureChains, createClient, WagmiConfig } from "wagmi"; -import { alchemyProvider } from "wagmi/providers/alchemy"; -import { publicProvider } from "wagmi/providers/public"; import "../styles/globals.css"; -import "@rainbow-me/rainbowkit/styles.css"; - -const { chains, provider, webSocketProvider } = configureChains( - [ - chain.mainnet, - chain.polygon, - chain.optimism, - chain.arbitrum, - ...(process.env.NEXT_PUBLIC_ENABLE_TESTNETS === "true" - ? [chain.goerli, chain.kovan, chain.rinkeby, chain.ropsten] - : []), - ], - [ - alchemyProvider({ - // This is Alchemy's default API key. - // You can get your own at https://dashboard.alchemyapi.io - apiKey: "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC", - }), - publicProvider(), - ], -); -const connectors = connectorsForWallets([ - { - groupName: "Supported Wallets", - wallets: [ - wallet.metaMask({ chains }), - wallet.walletConnect({ chains }), - wallet.coinbase({ appName: "scaffold-eth", chains }), - wallet.rainbow({ chains }), - ], - }, -]); +import type { AppProps } from "next/app"; +import { RainbowKitProvider } from "@rainbow-me/rainbowkit"; +import { WagmiConfig } from "wagmi"; -const wagmiClient = createClient({ - autoConnect: true, - connectors, - provider, - webSocketProvider, -}); +import "@rainbow-me/rainbowkit/styles.css"; +import { appChains } from "~~/web3/wagmiConnectors"; +import { wagmiClient } from "../web3/wagmiClient"; function ScaffoldEthApp({ Component, pageProps }: AppProps) { return ( - + diff --git a/packages/frontend/pages/index.tsx b/packages/frontend/pages/index.tsx index 576d121..e28fb3b 100644 --- a/packages/frontend/pages/index.tsx +++ b/packages/frontend/pages/index.tsx @@ -1,18 +1,39 @@ import { ConnectButton } from "@rainbow-me/rainbowkit"; import type { NextPage } from "next"; import Head from "next/head"; +import { TAutoConnect, useAutoConnect } from "~~/components/hooks/useAutoConnect"; +import { useTempTestContract } from "~~/components/useTempTestContract"; import Address from "../components/scaffold-eth/Address"; +// todo: move this later scaffold config. See TAutoConnect for comments on each prop +const tempAutoConnectConfig: TAutoConnect = { + enableBurnerWallet: true, + autoConnect: true, +}; + const Home: NextPage = () => { + const tempTest = useTempTestContract(); + useAutoConnect(tempAutoConnectConfig); + return (
- Scaffod-eth App + Scaffold-eth App
- +

Welcome to{" "} @@ -34,7 +55,9 @@ const Home: NextPage = () => {

- +

diff --git a/packages/frontend/tsconfig.json b/packages/frontend/tsconfig.json index 99710e8..7190373 100644 --- a/packages/frontend/tsconfig.json +++ b/packages/frontend/tsconfig.json @@ -13,7 +13,10 @@ "resolveJsonModule": true, "isolatedModules": true, "jsx": "preserve", - "incremental": true + "incremental": true, + "paths": { + "~~/*": ["./*"] + } }, "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], "exclude": ["node_modules"] diff --git a/packages/frontend/web3/index.ts b/packages/frontend/web3/index.ts new file mode 100644 index 0000000..ab7d690 --- /dev/null +++ b/packages/frontend/web3/index.ts @@ -0,0 +1,2 @@ +export * from "./wagmiClient"; +export * from "./wagmiConnectors"; diff --git a/packages/frontend/web3/wagmi-burner/BurnerConnector.ts b/packages/frontend/web3/wagmi-burner/BurnerConnector.ts new file mode 100644 index 0000000..dfb1449 --- /dev/null +++ b/packages/frontend/web3/wagmi-burner/BurnerConnector.ts @@ -0,0 +1,137 @@ +import { StaticJsonRpcProvider } from "@ethersproject/providers"; +import { Wallet } from "ethers"; +import { Connector, Chain, chain } from "wagmi"; +import { loadBurnerSK } from "~~/components/hooks/useBurnerWallet"; +import { BurnerConnectorError } from "."; +import { BurnerConnectorData, BurnerConnectorOptions, BurnerConnectorErrorList } from "."; + +export const burnerWalletId = "burner-wallet"; +export const burnerWalletName = "Burner Wallet"; +export const defaultBurnerChainId = chain.hardhat.id; + +/** + * This class is a wagmi connector for BurnerWallet. Its used by {@link burnerWalletConfig} + */ +export class BurnerConnector extends Connector { + readonly id = burnerWalletId; + readonly name = burnerWalletName; + readonly ready = true; + + private provider?: StaticJsonRpcProvider; + /** + * this is the store for getWallet() + */ + private burnerWallet: Wallet | undefined; + + constructor(config: { chains?: Chain[]; options: BurnerConnectorOptions }) { + super(config); + this.burnerWallet = undefined; + } + + async getProvider() { + if (!this.provider) { + const chain = this.getChainFromId(); + this.provider = new StaticJsonRpcProvider(chain.rpcUrls.default); + } + return this.provider; + } + + async connect(config?: { chainId?: number | undefined } | undefined): Promise> { + const chain = this.getChainFromId(config?.chainId); + + this.provider = new StaticJsonRpcProvider(chain.rpcUrls.default); + const account = await this.getAccount(); + const chainId = await this.getChainId(); + + if (this.provider == null || account == null || chainId == null) { + throw new BurnerConnectorError(BurnerConnectorErrorList.couldNotConnect); + } + + // todo unsported chains?? should i populate unsupported param + + if (!account) { + throw new BurnerConnectorError(BurnerConnectorErrorList.accountNotFound); + } + + const data: Required = { + account, + chain: { + id: chainId, + unsupported: false, + }, + provider: this.provider, + }; + + return data; + } + private getChainFromId(chainId?: number) { + const resolveChainId = chainId ?? this.options.defaultChainId; + const chain = this.chains.find(f => f.id === resolveChainId); + if (chain == null) { + throw new BurnerConnectorError(BurnerConnectorErrorList.chainNotSupported); + } + return chain; + } + + disconnect(): Promise { + console.log("disconnect from burnerwallet"); + return Promise.resolve(); + } + + async getAccount(): Promise { + const accounts = await this.provider?.listAccounts(); + if (accounts == null || accounts[0] == null) { + throw new BurnerConnectorError(BurnerConnectorErrorList.accountNotFound); + } + + const wallet = this.getWallet(); + const account = wallet.address; + return account; + } + + async getChainId(): Promise { + const network = await this.provider?.getNetwork(); + const chainId = network?.chainId ?? this.options.defaultChainId; + if (chainId == null) { + throw new BurnerConnectorError(BurnerConnectorErrorList.chainIdNotResolved); + } + + return Promise.resolve(chainId); + } + + async getSigner(): Promise { + const account = await this.getAccount(); + const signer = this.getWallet(); + + if (signer == null || (await signer.getAddress()) !== account) { + throw new BurnerConnectorError(BurnerConnectorErrorList.signerNotResolved); + } + + return Promise.resolve(signer); + } + async isAuthorized() { + try { + const account = await this.getAccount(); + return !!account; + } catch { + return false; + } + } + + private getWallet(): Wallet { + if (this.burnerWallet == null) { + this.burnerWallet = new Wallet(loadBurnerSK(), this.provider); + } + return this.burnerWallet; + } + + protected onAccountsChanged(): void { + this.burnerWallet = new Wallet(loadBurnerSK(), this.provider); + } + protected onChainChanged(): void { + this.burnerWallet = new Wallet(loadBurnerSK(), this.provider); + } + protected onDisconnect(error: Error): void { + if (error) console.warn(error); + } +} diff --git a/packages/frontend/web3/wagmi-burner/BurnerConnectorErrors.ts b/packages/frontend/web3/wagmi-burner/BurnerConnectorErrors.ts new file mode 100644 index 0000000..4baae15 --- /dev/null +++ b/packages/frontend/web3/wagmi-burner/BurnerConnectorErrors.ts @@ -0,0 +1,24 @@ +/** + * Error list used by {@link BurnerConnectorError} + */ +export const BurnerConnectorErrorList = { + accountNotFound: "Account not found", + couldNotConnect: "Could not connect to network", + unsupportedBurnerChain: "This network is not supported for burner connector", + chainIdNotResolved: "Cound not resolve chainId", + signerNotResolved: "Cound not resolve signer", + chainNotSupported: "Chain is not supported, check burner wallet config", +} as const; + +/** + * A union of all the BurnerConnectorErrorList + */ +export type BurnerConnectorErrorTypes = typeof BurnerConnectorErrorList[keyof typeof BurnerConnectorErrorList]; + +export class BurnerConnectorError extends Error { + constructor(errorType: BurnerConnectorErrorTypes, message?: string) { + const msg = `BurnerConnectorError ${errorType}: ${message ?? ""} `; + super(msg); + console.warn(msg); + } +} diff --git a/packages/frontend/web3/wagmi-burner/BurnerConnectorTypes.ts b/packages/frontend/web3/wagmi-burner/BurnerConnectorTypes.ts new file mode 100644 index 0000000..76d2d0f --- /dev/null +++ b/packages/frontend/web3/wagmi-burner/BurnerConnectorTypes.ts @@ -0,0 +1,8 @@ +import { ConnectorData } from "wagmi"; +import { StaticJsonRpcProvider } from "@ethersproject/providers"; + +export type BurnerConnectorOptions = { + defaultChainId: number; +}; + +export type BurnerConnectorData = ConnectorData & {}; diff --git a/packages/frontend/web3/wagmi-burner/burnerWalletConfig.ts b/packages/frontend/web3/wagmi-burner/burnerWalletConfig.ts new file mode 100644 index 0000000..171da61 --- /dev/null +++ b/packages/frontend/web3/wagmi-burner/burnerWalletConfig.ts @@ -0,0 +1,27 @@ +import { Chain, Wallet } from "@rainbow-me/rainbowkit"; +import { BurnerConnector, burnerWalletId, burnerWalletName, defaultBurnerChainId } from "~~/web3/wagmi-burner"; + +export interface BurnerWalletOptions { + chains: Chain[]; +} + +/** + * Wagmi config for burner wallet + * @param param0 + * @returns + */ +export const burnerWalletConfig = ({ chains }: BurnerWalletOptions): Wallet => ({ + id: burnerWalletId, + name: burnerWalletName, + iconUrl: "https://avatars.githubusercontent.com/u/56928858?s=200&v=4", + iconBackground: "#0c2f78", + //todo add conditions to hide burner wallet + hidden: () => false, + createConnector: () => { + const connector = new BurnerConnector({ chains, options: { defaultChainId: defaultBurnerChainId } }); + + return { + connector, + }; + }, +}); diff --git a/packages/frontend/web3/wagmi-burner/index.ts b/packages/frontend/web3/wagmi-burner/index.ts new file mode 100644 index 0000000..ce468ed --- /dev/null +++ b/packages/frontend/web3/wagmi-burner/index.ts @@ -0,0 +1,4 @@ +export * from "./BurnerConnector"; +export * from "./BurnerConnectorErrors"; +export * from "./BurnerConnectorTypes"; +export * from "./burnerWalletConfig"; diff --git a/packages/frontend/web3/wagmiClient.tsx b/packages/frontend/web3/wagmiClient.tsx new file mode 100644 index 0000000..55c269a --- /dev/null +++ b/packages/frontend/web3/wagmiClient.tsx @@ -0,0 +1,9 @@ +import { createClient } from "wagmi"; +import { appChains, wagmiConnectors } from "~~/web3/wagmiConnectors"; + +export const wagmiClient = createClient({ + autoConnect: false, + connectors: wagmiConnectors, + provider: appChains.provider, + webSocketProvider: appChains.webSocketProvider, +}); diff --git a/packages/frontend/web3/wagmiConnectors.tsx b/packages/frontend/web3/wagmiConnectors.tsx new file mode 100644 index 0000000..bc7ffb9 --- /dev/null +++ b/packages/frontend/web3/wagmiConnectors.tsx @@ -0,0 +1,66 @@ +import { connectorsForWallets, wallet } from "@rainbow-me/rainbowkit"; +import { configureChains, chain } from "wagmi"; +import { alchemyProvider } from "wagmi/providers/alchemy"; +import { publicProvider } from "wagmi/providers/public"; +import { burnerWalletConfig } from "~~/web3/wagmi-burner/burnerWalletConfig"; +import { BurnerConnector } from "~~/web3/wagmi-burner"; + +/** + * chains for the app + */ +export const appChains = configureChains( + [ + chain.mainnet, + chain.polygon, + chain.optimism, + chain.arbitrum, + chain.hardhat, + chain.localhost, + chain.polygon, + // todo replace with config instead of env + ...(process.env.NEXT_PUBLIC_ENABLE_TESTNETS === "true" + ? [chain.goerli, chain.kovan, chain.rinkeby, chain.ropsten, chain.polygonMumbai] + : []), + ], + [ + alchemyProvider({ + // This is Alchemy's default API key. + // You can get your own at https://dashboard.alchemyapi.io + apiKey: "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC", + }), + publicProvider(), + ], +); + +/** + * list of burner wallet compatable chains + */ +export const burnerChains = configureChains( + [chain.localhost, chain.hardhat], + [ + alchemyProvider({ + // This is Alchemy's default API key. + // You can get your own at https://dashboard.alchemyapi.io + apiKey: "_gg7wSSi0KMBsdKnGVfHDueq6xMB9EkC", + }), + publicProvider(), + ], +); + +/** + * wagmi connectors for the wagmi context + */ +export const wagmiConnectors = connectorsForWallets([ + { + groupName: "Supported Wallets", + wallets: [ + wallet.metaMask({ chains: appChains.chains, shimDisconnect: true }), + wallet.walletConnect({ chains: appChains.chains }), + wallet.ledger({ chains: appChains.chains }), + wallet.brave({ chains: appChains.chains }), + wallet.coinbase({ appName: "scaffold-eth", chains: appChains.chains }), + wallet.rainbow({ chains: appChains.chains }), + burnerWalletConfig({ chains: burnerChains.chains }), + ], + }, +]); diff --git a/yarn.lock b/yarn.lock index dbcdc27..6ae2632 100644 --- a/yarn.lock +++ b/yarn.lock @@ -382,7 +382,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/networks@npm:5.7.1, @ethersproject/networks@npm:^5.7.0": +"@ethersproject/networks@npm:5.7.1, @ethersproject/networks@npm:^5.7.0, @ethersproject/networks@npm:^5.7.1": version: 5.7.1 resolution: "@ethersproject/networks@npm:5.7.1" dependencies: @@ -559,7 +559,7 @@ __metadata: languageName: node linkType: hard -"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0": +"@ethersproject/web@npm:5.7.1, @ethersproject/web@npm:^5.7.0, @ethersproject/web@npm:^5.7.1": version: 5.7.1 resolution: "@ethersproject/web@npm:5.7.1" dependencies: @@ -1293,6 +1293,8 @@ __metadata: version: 0.0.0-use.local resolution: "@se-2/frontend@workspace:packages/frontend" dependencies: + "@ethersproject/networks": ^5.7.1 + "@ethersproject/web": ^5.7.1 "@heroicons/react": ^2.0.11 "@rainbow-me/rainbowkit": ^0.6.0 "@types/node": ^17.0.35 @@ -1315,6 +1317,8 @@ __metadata: react-dom: ^18.1.0 tailwindcss: ^3.1.8 typescript: ^4.7.2 + use-debounce: ^8.0.4 + usehooks-ts: ^2.7.2 wagmi: ^0.6.0 languageName: unknown linkType: soft @@ -10388,6 +10392,15 @@ __metadata: languageName: node linkType: hard +"use-debounce@npm:^8.0.4": + version: 8.0.4 + resolution: "use-debounce@npm:8.0.4" + peerDependencies: + react: ">=16.8.0" + checksum: 0f8b9e571f68a4694033ec00690bf73d781ac890140ae9ed4b330f71f7adb3f7d7df5cdaa32923c4d03fdc9de8a11be0f84301175ae4f66208af52ad03c55504 + languageName: node + linkType: hard + "use-sidecar@npm:^1.1.2": version: 1.1.2 resolution: "use-sidecar@npm:1.1.2" @@ -10413,6 +10426,16 @@ __metadata: languageName: node linkType: hard +"usehooks-ts@npm:^2.7.2": + version: 2.7.2 + resolution: "usehooks-ts@npm:2.7.2" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: c4edfc53aa60f6c107c8724dbd60c06b5888f1905091797c1639255924dd45ea337355c5d51e4826f7f07c0167742f9f2d3bf23e11fb6d9b15ad7abe01d1d51d + languageName: node + linkType: hard + "utf-8-validate@npm:^5.0.2": version: 5.0.9 resolution: "utf-8-validate@npm:5.0.9"