{"content":{"title":"Nextjs+wagmi+rainbowkit构建的Dapp开发模板+使用案例","body":"# 技术栈\r\n\r\n本次使用技术栈有：Nextjs+wagmi+rainbowkit+ethers，相对于之前我弄过的[web3-react](https:\/\/learnblockchain.cn\/article\/4478)模板的优点：配置文件少很多，开箱即用，各种现成的react hooks使用起来较方便，钱包连接的一些交互状态已经被rainbowkit做过了，视觉体验和ui看起来很舒服。\r\n\r\n钱包UI:\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/08\/aar0aOYj630c8463386c8.png)\r\n\r\n钱包小交互：\r\n\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/08\/hlZMNO9E630c848d3d931.png)\r\n\r\n# 项目目录介绍\r\n\r\n目录总览：\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/08\/QDKPVHf4630c804e16140.png)\r\n\r\n# 钱包配置\r\n首先看_app.tsx文件：\r\n\r\n```\r\nimport { RainbowKitProvider } from \"@rainbow-me\/rainbowkit\";\r\nimport { chains, wagmiClient } from \"config\/wagmi\";\r\nimport type { AppProps } from \"next\/app\";\r\nimport { useEffect, useState } from \"react\";\r\nimport \"styles\/globals.css\";\r\nimport \"styles\/rainbowkit.css\";\r\nimport { WagmiConfig } from \"wagmi\";\r\n\r\nfunction MyApp({ Component, pageProps }: AppProps) {\r\n    const [mounted, setMounted] = useState(false);\r\n    useEffect(() => setMounted(true), []);\r\n    if (!mounted) return null;\r\n\r\n    return (\r\n        <WagmiConfig client={wagmiClient}>\r\n            <RainbowKitProvider chains={chains}>\r\n                <Component {...pageProps} \/>\r\n            <\/RainbowKitProvider>\r\n        <\/WagmiConfig>\r\n    );\r\n}\r\n\r\nexport default MyApp;\r\n```\r\n两个配置：**WagmiConfig**和**RainbowKitProvider**主要是来自wagmi.ts:\r\n```\r\nimport { connectorsForWallets, wallet } from \"@rainbow-me\/rainbowkit\";\r\nimport { chain, configureChains, createClient } from \"wagmi\";\r\nimport { alchemyProvider } from \"wagmi\/providers\/alchemy\";\r\nimport { jsonRpcProvider } from \"wagmi\/providers\/jsonRpc\";\r\nimport { publicProvider } from \"wagmi\/providers\/public\";\r\n\r\nexport const { chains, provider, webSocketProvider } = configureChains(\r\n    [chain.mainnet, chain.goerli, chain.rinkeby, chain.kovan, chain.ropsten],\r\n    [\r\n        alchemyProvider({ apiKey: process.env.NEXT_PUBLIC_ALCHEMY_ID }),\r\n        jsonRpcProvider({ rpc: (chain) => ({ http: chain.rpcUrls.default }) }),\r\n        publicProvider(),\r\n    ],\r\n);\r\n\r\nconst needsInjectedWalletFallback =\r\n    typeof window !== \"undefined\" &&\r\n    window.ethereum &&\r\n    !window.ethereum.isMetaMask &&\r\n    !window.ethereum.isCoinbaseWallet;\r\n\r\nconst connectors = connectorsForWallets([\r\n    {\r\n        groupName: \"Popular\",\r\n        wallets: [\r\n            wallet.metaMask({ chains, shimDisconnect: true }),\r\n            wallet.brave({ chains, shimDisconnect: true }),\r\n            wallet.rainbow({ chains }),\r\n            wallet.walletConnect({ chains }),\r\n            wallet.coinbase({ appName: \"Coinbase\", chains }),\r\n            ...(needsInjectedWalletFallback ? [wallet.injected({ chains, shimDisconnect: true })] : []),\r\n        ],\r\n    },\r\n    {\r\n        groupName: \"Other\",\r\n        wallets: [wallet.trust({ chains, shimDisconnect: true }), wallet.steak({ chains }), wallet.imToken({ chains })],\r\n    },\r\n]);\r\n\r\nexport const wagmiClient = createClient({\r\n    autoConnect: true,\r\n    connectors,\r\n    provider,\r\n});\r\n```\r\n这里主要是钱包配置，包括了支持的链和支持的钱包等，这一个文件基本上涵盖了所有钱包的配置，具体配置可以参考[rainbowkit的官网](https:\/\/www.rainbowkit.com\/docs\/introduction)\r\n\r\n# 合约的封装\r\nhooks\/useContract:\r\n\r\n```\r\nimport { Contract, ContractInterface } from \"@ethersproject\/contracts\";\r\nimport { AddressMap } from \"config\/constants\/addresses\";\r\nimport { ChainId, defaultChainId } from \"config\/constants\/chainId\";\r\nimport { Providers } from \"config\/providers\";\r\nimport { useMemo } from \"react\";\r\nimport { useNetwork, useProvider, useSigner } from \"wagmi\";\r\n\r\nexport const createStaticContract = <TContract extends Contract = Contract>(ABI: ContractInterface) => {\r\n    return (address: string, chainId: ChainId) => {\r\n        const provider = Providers.getStaticProvider(chainId);\r\n        return useMemo(() => new Contract(address, ABI, provider) as TContract, [address, provider]);\r\n    };\r\n};\r\n\r\nconst createDynamicContract = <TContract extends Contract = Contract>(ABI: ContractInterface) => {\r\n    return (addressMap: AddressMap, asSigner = false) => {\r\n        const provider = useProvider();\r\n        const { data: signer } = useSigner();\r\n        const { chain = { id: defaultChainId } } = useNetwork();\r\n\r\n        return useMemo(() => {\r\n            const address = addressMap[chain.id as keyof typeof addressMap];\r\n\r\n            if (!address) return null;\r\n\r\n            const providerOrSigner = asSigner && signer ? signer : provider;\r\n\r\n            return new Contract(address, ABI, providerOrSigner) as TContract;\r\n        }, [addressMap, chain.id, asSigner, signer, provider]);\r\n    };\r\n};\r\n\r\n\/\/ export const useStaticExampleContract = createStaticContract<type>(ABI);\r\n\r\n\/\/ export const useDynamicExampleContract = createDynamicContract<type>(ABI);\r\n\r\n```\r\n引入的配置直接看文件即可，这里分为两种\r\n1.createStaticContract可以用在无需连接钱包的地方，比如一些查询功能；\r\n2.createDynamicContract则是用在需要钱包连接的功能，一般我用在写合约方法里面；\r\n\r\n# 使用案例\r\n\r\n1.在useContract.ts中初始化：\r\n```\r\nimport { Contract, ContractInterface } from \"@ethersproject\/contracts\";\r\nimport ExampleABI from \"config\/abis\/example.json\";\r\nimport { Example } from \"config\/abis\/types\";\r\nimport { AddressMap } from \"config\/constants\/addresses\";\r\nimport { ChainId, defaultChainId } from \"config\/constants\/chainId\";\r\nimport { Providers } from \"config\/providers\";\r\nimport { useMemo } from \"react\";\r\nimport { useNetwork, useProvider, useSigner } from \"wagmi\";\r\nexport const createStaticContract = <TContract extends Contract = Contract>(ABI: ContractInterface) => {\r\n    return (address: string, chainId: ChainId) => {\r\n        const provider = Providers.getStaticProvider(chainId);\r\n        return useMemo(() => new Contract(address, ABI, provider) as TContract, [address, provider]);\r\n    };\r\n};\r\n\r\nconst createDynamicContract = <TContract extends Contract = Contract>(ABI: ContractInterface) => {\r\n    return (addressMap: AddressMap, asSigner = false) => {\r\n        const provider = useProvider();\r\n        const { data: signer } = useSigner();\r\n        const { chain = { id: defaultChainId } } = useNetwork();\r\n\r\n        return useMemo(() => {\r\n            const address = addressMap[chain.id as keyof typeof addressMap];\r\n\r\n            if (!address) return null;\r\n\r\n            const providerOrSigner = asSigner && signer ? signer : provider;\r\n\r\n            return new Contract(address, ABI, providerOrSigner) as TContract;\r\n        }, [addressMap, chain.id, asSigner, signer, provider]);\r\n    };\r\n};\r\n\r\n\/\/ export const useStaticExampleContract = createStaticContract<type>(ABI);\r\n\r\nexport const useStaticExampleContract = createStaticContract<Example>(ExampleABI);\r\n\r\n\/\/ export const useDynamicExampleContract = createDynamicContract<type>(ABI);\r\n\r\nexport const useDynamicExampleContract = createDynamicContract<Example>(ExampleABI);\r\n\r\n```\r\n2.在组件中使用：\r\n\r\n```\r\nimport { BasciConnect } from \"components\/ConnectWallet\";\r\nimport { EXAMPLE_ADDRESSES } from \"config\/constants\/addresses\";\r\nimport { ChainId } from \"config\/constants\/chainId\";\r\nimport { useDynamicExampleContract, useStaticExampleContract } from \"hooks\/useContract\";\r\nimport { useEffect, useState } from \"react\";\r\nimport { useAccount } from \"wagmi\";\r\n\r\nconst Example = () => {\r\n    const StaticExampleInstance = useStaticExampleContract(EXAMPLE_ADDRESSES[ChainId.RINKEBY], ChainId.RINKEBY);\r\n    const DynamicExampleInstance = useDynamicExampleContract(EXAMPLE_ADDRESSES, true);\r\n    const [count, setCount] = useState(\"\");\r\n    const { isConnected } = useAccount();\r\n\r\n    useEffect(() => {\r\n        init();\r\n    }, []);\r\n    const init = async () => {\r\n        const count = await StaticExampleInstance.getCount();\r\n        setCount(count.toString());\r\n    };\r\n    return (\r\n        <div>\r\n            <div>\r\n                <BasciConnect><\/BasciConnect>\r\n\r\n                <div\r\n                    style={{\r\n                        width: \"100%\",\r\n                        textAlign: \"center\",\r\n                        fontSize: \"24px\",\r\n                    }}\r\n                >\r\n                    count (\r\n                    <span\r\n                        style={{\r\n                            color: \"gray\",\r\n                        }}\r\n                    >\r\n                        read Contract\r\n                    <\/span>\r\n                    ) :{count ? count : 0}\r\n                <\/div>\r\n                <>\r\n                    {isConnected ? (\r\n                        <div>\r\n                            <div\r\n                                style={{\r\n                                    width: \"200px\",\r\n                                    height: \"50px\",\r\n                                    backgroundColor: \"#0076F7\",\r\n                                    color: \"white\",\r\n                                    borderRadius: \"20px\",\r\n                                    textAlign: \"center\",\r\n                                    lineHeight: \"50px\",\r\n                                    fontSize: \"14px\",\r\n                                    margin: \"0 auto\",\r\n                                    cursor: \"pointer\",\r\n                                }}\r\n                                onClick={async () => {\r\n                                    try {\r\n                                        console.log(DynamicExampleInstance);\r\n\r\n                                        const tx = await DynamicExampleInstance.setCount();\r\n                                        await tx.wait();\r\n                                        init();\r\n                                    } catch (error) {\r\n                                        console.log(error);\r\n                                    }\r\n                                }}\r\n                            >\r\n                                setCount(write Contract)\r\n                            <\/div>\r\n                        <\/div>\r\n                    ) : (\r\n                        <div\r\n                            style={{\r\n                                display: \"flex\",\r\n                                justifyContent: \"center\",\r\n                            }}\r\n                        >\r\n                            <BasciConnect><\/BasciConnect>\r\n                        <\/div>\r\n                    )}\r\n                <\/>\r\n            <\/div>\r\n        <\/div>\r\n    );\r\n};\r\n\r\nexport default Example;\r\n\r\n```\r\n\r\n合约已经部署过，想体验的可以用rinkeby调用一下案例网站：https:\/\/nextjs-wagmi-template.vercel.app\/\r\n\r\ngithub：https:\/\/github.com\/Verin1005\/Nextjs-wagmi-template\r\n后续会更新一下Vite+React版本\r\n\r\n如果好用，欢迎大家来我git点个star，谢谢。"},"author":{"user":"https:\/\/learnblockchain.cn\/people\/4922","address":null},"history":"QmU3GMUBUkHpLVBXsojZurW5b9ryLbJB12dCEcc5bX4PRF","timestamp":1661765189,"version":1}