Skip to content
Closed
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
3 changes: 3 additions & 0 deletions packages/nextjs/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,6 @@ NEXT_PUBLIC_APP_OG_TITLE=
NEXT_PUBLIC_APP_OG_DESCRIPTION=
NEXT_PUBLIC_APP_OG_IMAGE=

NEXT_PUBLIC_PRIVY_APP_ID=
NEXT_PUBLIC_PRIVY_CLIENT_ID=

6 changes: 4 additions & 2 deletions packages/nextjs/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import Image from "next/image";
import Link from "next/link";
import { usePathname } from "next/navigation";
import BackButton from "./BackButton";
import { useWallets } from "@privy-io/react-auth";
import { hardhat } from "viem/chains";
import { useAccount } from "wagmi";
import { Bars3Icon, BugAntIcon } from "@heroicons/react/24/outline";
Expand Down Expand Up @@ -79,7 +80,8 @@ export const HeaderMenuLinks = () => {
export const Header = () => {
const { targetNetwork } = useTargetNetwork();
const isLocalNetwork = targetNetwork.id === hardhat.id;

const { wallets } = useWallets();
const isWalletConnected = wallets?.length > 0;
const burgerMenuRef = useRef<HTMLDetailsElement>(null);
useOutsideClick(burgerMenuRef, () => {
burgerMenuRef?.current?.removeAttribute("open");
Expand Down Expand Up @@ -117,7 +119,7 @@ export const Header = () => {
</div>
<div className="navbar-end grow">
<RainbowKitCustomConnectButton />
{isLocalNetwork && <FaucetButton />}
{isLocalNetwork && isWalletConnected && <FaucetButton />}
{/* Removed Add Mini App button; auto-prompt handled in provider */}
<details className="dropdown dropdown-end" ref={burgerMenuRef}>
<summary className="btn btn-ghost lg:hidden hover:bg-transparent">
Expand Down
39 changes: 19 additions & 20 deletions packages/nextjs/components/ScaffoldEthAppWithProviders.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
"use client";

import { useEffect, useState } from "react";
import { MiniappProvider } from "./MiniappProvider";
import { RainbowKitProvider, darkTheme, lightTheme } from "@rainbow-me/rainbowkit";
import { PrivyProvider } from "@privy-io/react-auth";
import { WagmiProvider } from "@privy-io/wagmi";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { AppProgressBar as ProgressBar } from "next-nprogress-bar";
import { useTheme } from "next-themes";
import { Toaster } from "react-hot-toast";
import { WagmiProvider } from "wagmi";
// import { Footer } from "~~/components/Footer";
import { Header } from "~~/components/Header";
import { BlockieAvatar } from "~~/components/scaffold-eth";
import { useInitializeNativeCurrencyPrice } from "~~/hooks/scaffold-eth";
import scaffoldConfig from "~~/scaffold.config";
import { wagmiConfig } from "~~/services/web3/wagmiConfig";

const ScaffoldEthApp = ({ children }: { children: React.ReactNode }) => {
Expand All @@ -38,27 +36,28 @@ export const queryClient = new QueryClient({
});

export const ScaffoldEthAppWithProviders = ({ children }: { children: React.ReactNode }) => {
const { resolvedTheme } = useTheme();
const isDarkMode = resolvedTheme === "dark";
const [mounted, setMounted] = useState(false);

useEffect(() => {
setMounted(true);
}, []);

return (
<WagmiProvider config={wagmiConfig}>
<PrivyProvider
appId={process.env.NEXT_PUBLIC_PRIVY_APP_ID as string}
clientId={process.env.NEXT_PUBLIC_PRIVY_CLIENT_ID as string}
config={{
embeddedWallets: {
ethereum: {
createOnLogin: "users-without-wallets",
},
},
loginMethods: scaffoldConfig.loginMethods,
supportedChains: scaffoldConfig.targetNetworks,
}}
>
<QueryClientProvider client={queryClient}>
<RainbowKitProvider
avatar={BlockieAvatar}
theme={mounted ? (isDarkMode ? darkTheme() : lightTheme()) : lightTheme()}
>
<WagmiProvider config={wagmiConfig}>
<MiniappProvider>
<ProgressBar height="3px" color="#2299dd" />
<ScaffoldEthApp>{children}</ScaffoldEthApp>
</MiniappProvider>
</RainbowKitProvider>
</WagmiProvider>
</QueryClientProvider>
</WagmiProvider>
</PrivyProvider>
);
};
4 changes: 2 additions & 2 deletions packages/nextjs/components/scaffold-eth/FaucetButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const localWalletClient = createWalletClient({
* FaucetButton button which lets you grab eth.
*/
export const FaucetButton = () => {
const { address, chain: ConnectedChain } = useAccount();
const { address, chainId } = useAccount();

const { data: balance } = useWatchBalance({ address });

Expand All @@ -46,7 +46,7 @@ export const FaucetButton = () => {
};

// Render only on local chain
if (ConnectedChain?.id !== hardhat.id) {
if (chainId !== hardhat.id) {
return null;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useRef, useState } from "react";
import { NetworkOptions } from "./NetworkOptions";
import { getAddress } from "viem";
import { Address } from "viem";
import { usePrivy } from "@privy-io/react-auth";
import { Address, getAddress } from "viem";
import { useAccount, useDisconnect } from "wagmi";
import {
ArrowLeftOnRectangleIcon,
Expand Down Expand Up @@ -35,6 +35,7 @@ export const AddressInfoDropdown = ({
blockExplorerAddressLink,
}: AddressInfoDropdownProps) => {
const { disconnect } = useDisconnect();
const { logout } = usePrivy();
const { connector } = useAccount();
const checkSumAddress = getAddress(address);

Expand All @@ -50,6 +51,11 @@ export const AddressInfoDropdown = ({

useOutsideClick(dropdownRef, closeDropdown);

const handleDisconnect = () => {
disconnect();
logout();
};

return (
<>
<details ref={dropdownRef} className="dropdown dropdown-end leading-3">
Expand Down Expand Up @@ -124,7 +130,7 @@ export const AddressInfoDropdown = ({
<button
className="menu-item text-error h-8 btn-sm rounded-xl! flex gap-3 py-3"
type="button"
onClick={() => disconnect()}
onClick={handleDisconnect}
>
<ArrowLeftOnRectangleIcon className="h-6 w-4 ml-2 sm:ml-0" /> <span>Disconnect</span>
</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useWallets } from "@privy-io/react-auth";
import { useTheme } from "next-themes";
import { useAccount, useSwitchChain } from "wagmi";
import { ArrowsRightLeftIcon } from "@heroicons/react/24/solid";
Expand All @@ -12,10 +13,23 @@ type NetworkOptionsProps = {

export const NetworkOptions = ({ hidden = false }: NetworkOptionsProps) => {
const { switchChain } = useSwitchChain();
const { wallets } = useWallets();
const { chain } = useAccount();
const { resolvedTheme } = useTheme();
const isDarkMode = resolvedTheme === "dark";

const wallet = wallets?.[0];

const handleSwitchChain = (chainId: number) => {
if (wallet) {
if (wallet.walletClientType === "privy") {
wallet.switchChain(chainId);
} else {
switchChain?.({ chainId });
}
}
};

return (
<>
{allowedNetworks
Expand All @@ -26,7 +40,7 @@ export const NetworkOptions = ({ hidden = false }: NetworkOptionsProps) => {
className="menu-item btn-sm rounded-xl! flex gap-3 py-3 whitespace-nowrap"
type="button"
onClick={() => {
switchChain?.({ chainId: allowedNetwork.id });
handleSwitchChain(allowedNetwork.id);
}}
>
<ArrowsRightLeftIcon className="h-6 w-4 ml-2 sm:ml-0" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,69 +1,133 @@
"use client";

// @refresh reset
import { useEffect } from "react";
import { Balance } from "../Balance";
import { AddressInfoDropdown } from "./AddressInfoDropdown";
import { AddressQRCodeModal } from "./AddressQRCodeModal";
import { RevealBurnerPKModal } from "./RevealBurnerPKModal";
import { WrongNetworkDropdown } from "./WrongNetworkDropdown";
import { ConnectButton } from "@rainbow-me/rainbowkit";
import { usePrivy, useWallets } from "@privy-io/react-auth";
import { usePrivyWagmi } from "@privy-io/wagmi";
import { Address } from "viem";
import { normalize } from "viem/ens";
import { useAccount, useEnsAvatar, useEnsName } from "wagmi";
import { useNetworkColor } from "~~/hooks/scaffold-eth";
import { useTargetNetwork } from "~~/hooks/scaffold-eth/useTargetNetwork";
import { getBlockExplorerAddressLink } from "~~/utils/scaffold-eth";

/**
* Custom Wagmi Connect Button (watch balance + custom design)
* Custom Privy Connect Button (watch balance + custom design)
*/
export const RainbowKitCustomConnectButton = () => {
const networkColor = useNetworkColor();
const { targetNetwork } = useTargetNetwork();
const { authenticated, ready: privyReady, login, connectWallet } = usePrivy();
const { wallets } = useWallets();
const { setActiveWallet } = usePrivyWagmi();
const { address, chain, chainId, isConnected, isConnecting } = useAccount();

useEffect(() => {
if (isConnecting || address) {
return;
}

if (privyReady && authenticated && wallets.length > 0 && !address) {
const embeddedWallet = wallets.find(w => w.walletClientType === "privy");
if (embeddedWallet) {
setActiveWallet(embeddedWallet);
}
}
}, [privyReady, authenticated, wallets, address, isConnecting, setActiveWallet]);

const { data: ensName } = useEnsName({
address: address as Address,
chainId: 1,
query: {
enabled: Boolean(address),
gcTime: 30_000,
},
});

const { data: ensAvatar } = useEnsAvatar({
name: ensName ? normalize(ensName) : undefined,
chainId: 1,
query: {
enabled: Boolean(ensName),
gcTime: 30_000,
},
});

const mounted = privyReady;
const hasWallet = wallets.length > 0 || isConnected;
const connected = mounted && authenticated && address;
const isStillConnecting = authenticated && hasWallet && !address && !isConnecting;

const blockExplorerAddressLink = address ? getBlockExplorerAddressLink(targetNetwork, address) : undefined;

const displayName = ensName || (address ? `${address.slice(0, 6)}...${address.slice(-4)}` : "");

const handleConnect = () => {
if (authenticated) {
if (!hasWallet) {
connectWallet();
}
return;
}
login();
};

return (
<ConnectButton.Custom>
{({ account, chain, openConnectModal, mounted }) => {
const connected = mounted && account && chain;
const blockExplorerAddressLink = account
? getBlockExplorerAddressLink(targetNetwork, account.address)
: undefined;
<>
{(() => {
if (!mounted) {
return null;
}

return (
<>
{(() => {
if (!connected) {
return (
<button className="btn btn-primary btn-sm" onClick={openConnectModal} type="button">
Connect Wallet
</button>
);
}
// Show connecting state if authenticated with wallets but wagmi hasn't synced yet
if (isStillConnecting) {
return (
<button className="btn btn-primary btn-sm" disabled type="button">
Connecting...
</button>
);
}

if (chain.unsupported || chain.id !== targetNetwork.id) {
return <WrongNetworkDropdown />;
}
// Show connect button if not connected
if (!connected) {
return (
<button className="btn btn-primary btn-sm" onClick={handleConnect} type="button">
Connect Wallet
</button>
);
}

return (
<>
<div className="flex flex-col items-center mr-1">
<Balance address={account.address as Address} className="min-h-0 h-auto" />
<span className="text-xs" style={{ color: networkColor }}>
{chain.name}
</span>
</div>
<AddressInfoDropdown
address={account.address as Address}
displayName={account.displayName}
ensAvatar={account.ensAvatar}
blockExplorerAddressLink={blockExplorerAddressLink}
/>
<AddressQRCodeModal address={account.address as Address} modalId="qrcode-modal" />
<RevealBurnerPKModal />
</>
);
})()}
if (chainId && chainId !== targetNetwork.id) {
return <WrongNetworkDropdown />;
}

// Show connected state
return (
<>
<div className="flex flex-col items-center mr-1">
<Balance address={address as Address} className="min-h-0 h-auto" />
{chain && (
<span className="text-xs" style={{ color: networkColor }}>
{chain.name}
</span>
)}
</div>
<AddressInfoDropdown
address={address as Address}
displayName={displayName}
ensAvatar={(ensAvatar ?? undefined) as string | undefined}
blockExplorerAddressLink={blockExplorerAddressLink}
/>
<AddressQRCodeModal address={address as Address} modalId="qrcode-modal" />
<RevealBurnerPKModal />
</>
);
}}
</ConnectButton.Custom>
})()}
</>
);
};
Loading