Skip to content
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
4 changes: 2 additions & 2 deletions packages/nextjs/components/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import Link from "next/link";
import { usePathname } from "next/navigation";
import { Bars3Icon, BugAntIcon } from "@heroicons/react/24/outline";
import { useOutsideClick } from "~~/hooks/scaffold-stark";
import { CustomConnectButton } from "~~/components/scaffold-stark/CustomConnectButton";
import { StarknetkitButton } from "~~/components/scaffold-stark/StarknetkitButton";
import { useTheme } from "next-themes";
import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork";
import { devnet } from "@starknet-react/chains";
Expand Down Expand Up @@ -172,7 +172,7 @@ export const Header = () => {
Wallet Not Deployed
</span>
) : null}
<CustomConnectButton />
<StarknetkitButton />
{/* <FaucetButton /> */}
<SwitchTheme
className={`pointer-events-auto ${
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import React from "react";
import { useTheme } from "next-themes";
import { BlockieAvatar } from "../BlockieAvatar";
import { burnerAccounts } from "@scaffold-stark/stark-burner";

interface BurnerWalletModalProps {
isOpen: boolean;
onClose: () => void;
onSelectBurner: (index: number) => void;
}

const BurnerWalletModal: React.FC<BurnerWalletModalProps> = ({
isOpen,
onClose,
onSelectBurner,
}) => {
const { resolvedTheme } = useTheme();
const isDarkMode = resolvedTheme === "dark";

if (!isOpen) return null;

return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
<div
className={`bg-base-100 rounded-lg w-96 p-6 shadow-xl ${isDarkMode ? "border border-[#385183]" : "border border-gray-300"}`}
>
<div className="flex items-center justify-between mb-6">
<h3 className="text-xl font-bold">Choose account</h3>
<button onClick={onClose} className="btn btn-ghost btn-sm btn-circle">
</button>
</div>

<div className="h-[300px] overflow-y-auto flex w-full flex-col gap-2">
{burnerAccounts.map((burnerAcc, ix) => (
<div key={burnerAcc.publicKey} className="w-full flex flex-col">
<button
className={`hover:bg-gradient-modal border rounded-md text-neutral py-[8px] pl-[10px] pr-16 flex items-center gap-4 ${
isDarkMode ? "border-[#385183]" : ""
}`}
onClick={() => onSelectBurner(ix)}
>
<BlockieAvatar address={burnerAcc.accountAddress} size={35} />
{`${burnerAcc.accountAddress.slice(
0,
6,
)}...${burnerAcc.accountAddress.slice(-4)}`}
</button>
</div>
))}
</div>
</div>
</div>
);
};

export default BurnerWalletModal;
179 changes: 179 additions & 0 deletions packages/nextjs/components/scaffold-stark/StarknetkitButton/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import { useCallback, useEffect, useState } from "react";
import { connect, disconnect, StarknetkitConnector } from "starknetkit";
import { BurnerConnector, burnerAccounts } from "@scaffold-stark/stark-burner";
import { useLocalStorage } from "usehooks-ts";
import {
useAccount,
useConnect,
useDisconnect,
useNetwork,
} from "@starknet-react/core";
import { AddressInfoDropdown } from "../CustomConnectButton/AddressInfoDropdown";
import { AddressQRCodeModal } from "../CustomConnectButton/AddressQRCodeModal";
import { WrongNetworkDropdown } from "../CustomConnectButton/WrongNetworkDropdown";
import { Balance } from "../Balance";
import { Address } from "@starknet-react/chains";
import { useAutoConnect, useNetworkColor } from "~~/hooks/scaffold-stark";
import { useTargetNetwork } from "~~/hooks/scaffold-stark/useTargetNetwork";
import { getBlockExplorerAddressLink } from "~~/utils/scaffold-stark";
import { LAST_CONNECTED_TIME_LOCALSTORAGE_KEY } from "~~/utils/Constants";
import { useMemo } from "react";
import BurnerWalletModal from "./BurnerWalletModal";

export const StarknetkitButton = () => {
useAutoConnect();
const networkColor = useNetworkColor();
const { connector } = useConnect();
const { targetNetwork } = useTargetNetwork();
const { account, status, address: accountAddress } = useAccount();
const [accountChainId, setAccountChainId] = useState<bigint>(0n);
const { chain } = useNetwork();
const [isBurnerModalOpen, setIsBurnerModalOpen] = useState(false);
const [_, setLastConnector] = useLocalStorage<{ id: string; ix?: number }>(
"lastUsedConnector",
{ id: "" },
{
initializeWithValue: false,
},
);
const [, setLastConnectionTime] = useLocalStorage<number>(
LAST_CONNECTED_TIME_LOCALSTORAGE_KEY,
0,
);

const blockExplorerAddressLink = useMemo(() => {
return (
accountAddress &&
getBlockExplorerAddressLink(targetNetwork, accountAddress)
);
}, [accountAddress, targetNetwork]);

// effect to get chain id and address from account
useEffect(() => {
if (account) {
const getChainId = async () => {
const chainId = await account.channel.getChainId();
setAccountChainId(BigInt(chainId as string));
};

getChainId();
}
}, [account, status]);

useEffect(() => {
const handleChainChange = (event: { chainId?: bigint }) => {
const { chainId } = event;
if (chainId && chainId !== accountChainId) {
setAccountChainId(chainId);
}
};
connector?.on("change", handleChainChange);
return () => {
connector?.off("change", handleChainChange);
};
}, [connector, accountChainId]);

const { connectors, connect: connectWithConnector } = useConnect();

const handleConnectionWithStarknetKit = useCallback(async () => {
const starknetkitConnectors = connectors.filter(
(connector) => connector.id !== "burner-wallet",
) as StarknetkitConnector[];

const { wallet } = await connect({
dappName: "Scaffold Stark",
connectors: starknetkitConnectors,
});

if (wallet) {
const selectedConnector = connectors.find((c) => c.id === wallet.id);

if (selectedConnector) {
connectWithConnector({ connector: selectedConnector });
setLastConnector({ id: selectedConnector.id });
setLastConnectionTime(Date.now());
}
}
}, [
connectors,
connectWithConnector,
setLastConnector,
setLastConnectionTime,
]);

const handleBurnerWallet = useCallback(() => {
setIsBurnerModalOpen(true);
}, []);

const handleConnectBurner = useCallback(
(ix: number) => {
const connector = connectors.find((it) => it.id === "burner-wallet");
if (connector && connector instanceof BurnerConnector) {
connector.burnerAccount = burnerAccounts[ix];
connectWithConnector({ connector });
setLastConnector({ id: connector.id, ix });
setLastConnectionTime(Date.now());
setIsBurnerModalOpen(false);
}
},
[connectors, connectWithConnector, setLastConnector, setLastConnectionTime],
);

if (status === "disconnected" || accountChainId === 0n) {
const isDevnet = targetNetwork.network === "devnet";

return (
<div className="flex space-x-2">
<button
onClick={handleConnectionWithStarknetKit}
className="rounded-[18px] btn-sm font-bold px-4 bg-btn-wallet py-3 cursor-pointer flex items-center justify-center"
>
<span>Connect Wallet</span>
</button>

{isDevnet && (
<button
onClick={handleBurnerWallet}
className="rounded-[18px] btn-sm font-bold px-4 bg-secondary py-3 cursor-pointer flex items-center justify-center"
>
<span>Burner Wallet</span>
</button>
)}

<BurnerWalletModal
isOpen={isBurnerModalOpen}
onClose={() => setIsBurnerModalOpen(false)}
onSelectBurner={handleConnectBurner}
/>
</div>
);
}

if (accountChainId !== targetNetwork.id) {
return <WrongNetworkDropdown />;
}

return (
<>
<div className="flex flex-col items-center max-sm:mt-2">
<Balance
address={accountAddress as Address}
className="min-h-0 h-auto"
/>
<span className="text-xs ml-1" style={{ color: networkColor }}>
{chain.name}
</span>
</div>
<AddressInfoDropdown
address={accountAddress as Address}
displayName={""}
ensAvatar={""}
blockExplorerAddressLink={blockExplorerAddressLink}
/>
<AddressQRCodeModal
address={accountAddress as Address}
modalId="qrcode-modal"
/>
</>
);
};
1 change: 1 addition & 0 deletions packages/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"react-dom": "19.0.0",
"react-hot-toast": "^2.4.1",
"starknet": "^7.1.0",
"starknetkit": "^2.10.4",
"type-fest": "^4.6.0",
"usehooks-ts": "^2.13.0",
"zustand": "^4.1.2"
Expand Down
2 changes: 1 addition & 1 deletion packages/nextjs/scaffold.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export type ScaffoldConfig = {
};

const scaffoldConfig = {
targetNetworks: [chains.devnet],
targetNetworks: [chains.sepolia],
// Only show the Burner Wallet when running on devnet
onlyLocalBurnerWallet: false,
rpcProviderUrl: {
Expand Down
50 changes: 40 additions & 10 deletions packages/nextjs/services/web3/connectors.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { argent, braavos, InjectedConnector } from "@starknet-react/core";
// import { argent, braavos, InjectedConnector } from '@starknet-react/core';
import { getTargetNetworks } from "~~/utils/scaffold-stark";
import { BurnerConnector } from "@scaffold-stark/stark-burner";
import scaffoldConfig from "~~/scaffold.config";
import { LAST_CONNECTED_TIME_LOCALSTORAGE_KEY } from "~~/utils/Constants";
import { KeplrConnector } from "./keplr";
import { supportedChains } from "~~/supportedChains";
import { Connector } from "@starknet-react/core";
import { InjectedConnector } from "starknetkit/injected";
import { WebWalletConnector } from "starknetkit/webwallet";

const targetNetworks = getTargetNetworks();

export const connectors = getConnectors();

// workaround helper function to properly disconnect with removing local storage (prevent autoconnect infinite loop)
function withDisconnectWrapper(connector: InjectedConnector) {
function withDisconnectWrapper(connector: Connector) {
const connectorDisconnect = connector.disconnect;
const _disconnect = (): Promise<void> => {
localStorage.removeItem("lastUsedConnector");
Expand All @@ -22,23 +25,50 @@ function withDisconnectWrapper(connector: InjectedConnector) {
return connector;
}

function getConnectors() {
function getConnectors(): Connector[] {
const { targetNetworks } = scaffoldConfig;

const connectors: InjectedConnector[] = [argent(), braavos()];
const connectors: Connector[] = [
new InjectedConnector({
options: { id: "argentX", name: "Argent X" },
}),
new InjectedConnector({
options: { id: "braavos", name: "Braavos" },
}),
];

const isDevnet = targetNetworks.some(
(network) => (network.network as string) === "devnet",
);
const isSepolia = targetNetworks.some(
(network) => (network.network as string) === "sepolia",
);

if (isSepolia) {
// Add standard connectors for mainnet/testnet
connectors.push(
new KeplrConnector(),
new WebWalletConnector({ url: "https://sepolia-web.argent.xyz" }),
);
}
if (!isDevnet && !isSepolia) {
// Add standard connectors for mainnet/testnet
connectors.push(
new KeplrConnector(),
new WebWalletConnector({ url: "https://web.argent.xyz" }),
);
}

if (!isDevnet) {
connectors.push(new KeplrConnector());
} else {
const burnerConnector = new BurnerConnector();
// Always add burner connector for devnet
const burnerConnector = new BurnerConnector();
if (isDevnet) {
burnerConnector.chain = supportedChains.devnet;
connectors.push(burnerConnector as unknown as InjectedConnector);
}
connectors.push(burnerConnector);

return connectors.sort(() => Math.random() - 0.5).map(withDisconnectWrapper);
return connectors
.map(withDisconnectWrapper)
.filter((connector) => connector !== null);
}

export const appChains = targetNetworks;
2 changes: 1 addition & 1 deletion packages/nextjs/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
Expand Down
Loading
Loading