Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,82 @@ if (token && address) {
}
```

## 🔗 Chain Enforcement Hook

Use `useEnsureCorrectChain` to ensure the connected wallet is on the correct network. It exposes a single `status` flow instead of multiple booleans:

```
idle → wrong-network → switching → correct
```

| Status | Meaning |
|---|---|
| `"idle"` | Wallet not connected or state dismissed |
| `"wrong-network"` | Connected to an unsupported chain |
| `"switching"` | Chain switch in progress |
| `"correct"` | On the required chain |

### Basic Usage

```tsx
import { useEnsureCorrectChain } from '@everipedia/iq-login/client';

function MyComponent() {
const { status, switchToCorrectChain, targetChain, dismiss } = useEnsureCorrectChain({
requiredChainId: 252, // e.g. Fraxtal
});

if (status === "wrong-network") {
return (
<div>
<p>Please switch to {targetChain?.name}</p>
<button onClick={switchToCorrectChain}>Switch Network</button>
<button onClick={dismiss}>Dismiss</button>
</div>
);
}

if (status === "switching") {
return <p>Switching network...</p>;
}

return <p>Connected to the correct network!</p>;
}
```

### With Status Callback

Use `onStatusChange` to react to transitions — e.g. to open/close a modal:

```tsx
const { status, switchToCorrectChain } = useEnsureCorrectChain({
requiredChainId: 252,
onStatusChange: (status, chainName) => {
if (status === "wrong-network") openSwitchModal();
if (status === "correct") closeSwitchModal();
},
});
```

### API Reference

**Options:**

| Prop | Type | Description |
|---|---|---|
| `requiredChainId` | `number` | The chain ID your app requires |
| `onStatusChange` | `(status, chainName?) => void` | Optional callback on every status transition |

**Returns:**

| Field | Type | Description |
|---|---|---|
| `status` | `ChainStatus` | Current status (`"idle"`, `"wrong-network"`, `"switching"`, `"correct"`) |
| `switchToCorrectChain` | `() => Promise<void>` | Trigger a chain switch |
| `dismiss` | `() => void` | Dismiss the wrong-network state |
| `targetChain` | `Chain \| undefined` | Target chain object from wagmi config |
| `isConnected` | `boolean` | Whether the wallet is connected |

## 🎨 Styling

The package uses Tailwind CSS and Shadcn UI Theme. Visit https://ui.shadcn.com/themes for theme customization.
Expand Down
6 changes: 6 additions & 0 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ export { Login } from "./components/login-element";
// ===============
export { useAuth } from "./hooks/use-auth";
export { useWeb3Auth } from "./hooks/use-web-3-auth";
export {
useEnsureCorrectChain,
type ChainStatus,
type UseEnsureCorrectChainOptions,
type UseEnsureCorrectChainReturn,
} from "./hooks/use-ensure-correct-chain";

// ===============
// Config
Expand Down
92 changes: 92 additions & 0 deletions src/hooks/use-ensure-correct-chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"use client";

import { useCallback, useEffect, useState } from "react";
import { useAccount, useSwitchChain } from "wagmi";

export type ChainStatus = "idle" | "correct" | "wrong-network" | "switching";

export interface UseEnsureCorrectChainOptions {
/** The chain ID the app requires */
requiredChainId: number;
/** Called when status transitions (e.g. to open/close a modal) */
onStatusChange?: (status: ChainStatus, targetChainName?: string) => void;
}

export interface UseEnsureCorrectChainReturn {
/** Current chain-matching status */
status: ChainStatus;
/** Dismiss the wrong-network state (user chose to stay) */
dismiss: () => void;
/** Attempt to programmatically switch to the required chain */
switchToCorrectChain: () => Promise<void>;
/** The target chain object from the wagmi config, if found */
targetChain: ReturnType<typeof useSwitchChain>["chains"][number] | undefined;
/** Whether the wallet is connected */
isConnected: boolean;
}

export const useEnsureCorrectChain = ({
requiredChainId,
onStatusChange,
}: UseEnsureCorrectChainOptions): UseEnsureCorrectChainReturn => {
const { chainId, isConnected } = useAccount();
const { switchChainAsync, chains } = useSwitchChain();
const [status, setStatus] = useState<ChainStatus>("idle");

const targetChain = chains.find((c) => c.id === requiredChainId);
Comment thread
Adebesin-Cell marked this conversation as resolved.

const transition = useCallback(
(next: ChainStatus) => {
setStatus(next);
onStatusChange?.(next, targetChain?.name);
},
[onStatusChange, targetChain?.name],
);

useEffect(() => {
if (!isConnected || !chainId) {
transition("idle");
return;
}

transition(chainId === requiredChainId ? "correct" : "wrong-network");
}, [chainId, isConnected, requiredChainId, transition]);

const switchToCorrectChain = async () => {
if (!chainId) {
throw new Error(
"Wallet is not connected. Please connect your wallet first.",
);
}

if (chainId === requiredChainId) return;

if (!targetChain) {
throw new Error(
`Chain ID ${requiredChainId} is not configured in your wallet.`,
);
}

try {
transition("switching");
await switchChainAsync({ chainId: requiredChainId });
transition("correct");
} catch (error) {
console.error("Failed to switch network:", error);
transition("wrong-network");
throw new Error(
"Network switch failed. Please switch manually in your wallet.",
);
}
};
Comment thread
Adebesin-Cell marked this conversation as resolved.
Outdated

const dismiss = () => setStatus("idle");
Comment thread
Adebesin-Cell marked this conversation as resolved.
Outdated

return {
status,
dismiss,
switchToCorrectChain,
targetChain,
isConnected,
};
};
Loading