Skip to content
Merged
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
7 changes: 7 additions & 0 deletions .changeset/nice-baths-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@everipedia/iq-login": patch
---

Add `useEnsureCorrectChain` hook to standardize wallet network enforcement in dApps.

This hook introduces a unified status state machine (`idle` → `wrong-network` → `switching` → `correct`) for managing connected wallet chain state. It includes utilities for programmatic network switching, dismissal handling, and an optional `onStatusChange` callback for reacting to status transitions. Documentation has been added to the README with usage examples and API reference.
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
95 changes: 95 additions & 0 deletions src/hooks/use-ensure-correct-chain.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
"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 = useCallback(async () => {
if (!isConnected) {
console.error("Cannot switch chain, wallet not connected.");
return;
}

if (chainId === requiredChainId) return;

if (!targetChain) {
console.error(
`Cannot switch chain, target chain with ID ${requiredChainId} is not configured.`,
);
return;
}

try {
transition("switching");
await switchChainAsync({ chainId: requiredChainId });
} catch (error) {
console.error("Failed to switch network:", error);
transition("wrong-network");
}
}, [
isConnected,
chainId,
requiredChainId,
targetChain,
switchChainAsync,
transition,
]);

const dismiss = useCallback(() => transition("idle"), [transition]);

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