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