Skip to content
Draft
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
196 changes: 195 additions & 1 deletion demo/nextjs-ssr-app/components/Main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ import {
useWeb3AuthDisconnect,
useWeb3AuthUser,
} from "@web3auth/modal/react";
import { useAccount, useBalance, useChainId, useSignMessage, useSignTypedData, useSwitchChain } from "wagmi";
import { useState } from "react";
import { useAccount, useBalance, useChainId, useSendTransaction, useSignMessage, useSignTypedData, useSwitchChain } from "wagmi";

const Main = () => {
const { provider, isConnected } = useWeb3Auth();
Expand All @@ -25,15 +26,131 @@ const Main = () => {
const { userInfo, isMFAEnabled } = useWeb3AuthUser();
const { data: balance } = useBalance({ address });
const { signTypedData, data: signedTypedDataData } = useSignTypedData();
const { sendTransaction, data: txHash, isPending: isSendingTx, error: sendTxError } = useSendTransaction();
const { enableMFA, loading: isEnableMFALoading, error: enableMFAError } = useEnableMFA();
const { manageMFA, loading: isManageMFALoading, error: manageMFAError } = useManageMFA();
const { showCheckout, loading: isCheckoutLoading, error: checkoutError } = useCheckout();
const { showWalletConnectScanner, loading: isWalletConnectScannerLoading, error: walletConnectScannerError } = useWalletConnectScanner();
const { showWalletUI, loading: isWalletUILoading, error: walletUIError } = useWalletUI();
const { token, loading: isUserTokenLoading, error: userTokenError, getIdentityToken } = useIdentityToken();

// ─── EIP-7702 / EIP-5792 State ──────────────────────────────────────
const [eipResult, setEipResult] = useState<string>("");
const [lastBatchId, setLastBatchId] = useState<string | null>(null);
const [eipLoading, setEipLoading] = useState<string | null>(null);

console.log("isConnected", isConnected, balance);

// ─── EIP-7702 Handlers ──────────────────────────────────────────────

const getAccountUpgradeStatus = async () => {
if (!provider || !address) return;
setEipLoading("upgradeStatus");
try {
const currentChainId = "0x" + chainId.toString(16);
const result = await provider.request({
method: "wallet_getAccountUpgradeStatus",
params: [{ account: address, chainId: currentChainId }],
});
setEipResult(JSON.stringify(result, null, 2));
} catch (error: any) {
setEipResult(JSON.stringify({ error: error?.message || error }, null, 2));
} finally {
setEipLoading(null);
}
};

const upgradeAccount = async () => {
if (!provider || !address) return;
setEipLoading("upgrade");
try {
const currentChainId = "0x" + chainId.toString(16);
const result = await provider.request({
method: "wallet_upgradeAccount",
params: [{ account: address, chainId: currentChainId }],
});
setEipResult(JSON.stringify(result, null, 2));
} catch (error: any) {
setEipResult(JSON.stringify({ error: error?.message || error }, null, 2));
} finally {
setEipLoading(null);
}
};

// ─── EIP-5792 Handlers ──────────────────────────────────────────────

const getCapabilities = async () => {
if (!provider || !address) return;
setEipLoading("capabilities");
try {
const currentChainId = "0x" + chainId.toString(16);
const result = await provider.request({
method: "wallet_getCapabilities",
params: [address, [currentChainId]],
});
setEipResult(JSON.stringify(result, null, 2));
} catch (error: any) {
setEipResult(JSON.stringify({ error: error?.message || error }, null, 2));
} finally {
setEipLoading(null);
}
};

const sendBatchCalls = async () => {
if (!provider || !address) return;
setEipLoading("sendBatch");
try {
const currentChainId = "0x" + chainId.toString(16);
// Two simple self-transfers as a batch
const batchId = await provider.request({
method: "wallet_sendCalls",
params: [
{
version: "2.0",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EIP-5792 version string doesn't match specification

Medium Severity

The wallet_sendCalls request uses version: "2.0" but the EIP-5792 specification requires "2.0.0" (three-part semver). This mismatch may cause compliant wallet providers to reject the request or fall back to unexpected behavior. Since this is a demo app showcasing EIP-5792 support, the version string needs to match the spec exactly.

Fix in Cursor Fix in Web

chainId: currentChainId,
from: address,
calls: [
{
to: address,
value: "0x0",
data: "0x",
},
{
to: address,
value: "0x0",
data: "0x",
},
],
},
],
});
if (batchId && typeof batchId === "string") {
setLastBatchId(batchId);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wrong EIP-5792 version string and response handling

Medium Severity

The wallet_sendCalls request uses version: "2.0" but the EIP-5792 spec defines "2.0.0" as the official version string. More critically, the v2 response is a structured object ({ id: "...", capabilities: {...} }) rather than a plain string, but the code checks typeof batchId === "string" — this condition will always be false for a v2 response, so lastBatchId is never set and the "Get Calls Status" feature becomes permanently non-functional.

Fix in Cursor Fix in Web

setEipResult(JSON.stringify({ batchId }, null, 2));
} catch (error: any) {
setEipResult(JSON.stringify({ error: error?.message || error }, null, 2));
} finally {
setEipLoading(null);
}
};

const getCallsStatus = async () => {
if (!provider || !lastBatchId) return;
setEipLoading("callsStatus");
try {
const result = await provider.request({
method: "wallet_getCallsStatus",
params: [lastBatchId],
});
setEipResult(JSON.stringify(result, null, 2));
} catch (error: any) {
setEipResult(JSON.stringify({ error: error?.message || error }, null, 2));
} finally {
setEipLoading(null);
}
};

const loggedInView = (
<>
<div className="container">
Expand Down Expand Up @@ -177,8 +294,85 @@ const Main = () => {
Sign Typed Data
</button>
{signedTypedDataData && <textarea disabled rows={5} value={signedTypedDataData} style={{ width: "100%" }} />}

{/* Send Zero Transaction */}
<button
onClick={() => sendTransaction({ to: address!, value: BigInt(0) })}
className="card"
disabled={isSendingTx || !address}
>
{isSendingTx ? "Sending..." : "Send Zero Transaction"}
</button>
{txHash && <textarea disabled rows={2} value={`Tx Hash: ${txHash}`} style={{ width: "100%" }} />}
{sendTxError && <p style={{ color: "red" }}>Error: {sendTxError.message}</p>}
</div>

{/* ── EIP-7702 Section ─────────────────────────────────────── */}
<div style={{ marginTop: "24px", marginBottom: "16px", borderTop: "2px solid #8b5cf6", paddingTop: "16px" }}>
<p style={{ color: "#8b5cf6", fontWeight: "bold" }}>EIP-7702 (Account Upgrade)</p>
<button
onClick={getAccountUpgradeStatus}
className="card"
style={{ borderColor: "#8b5cf6", color: "#8b5cf6" }}
disabled={eipLoading === "upgradeStatus"}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shared loading state allows concurrent conflicting EIP operations

Low Severity

The eipLoading state is a single string shared across all five EIP operation buttons, but each button only disables itself when its own key matches (e.g., disabled={eipLoading === "upgradeStatus"}). This means while one operation is in-flight, other EIP buttons remain clickable. Clicking a second button overwrites eipLoading, and when the first operation's finally block runs setEipLoading(null), it prematurely clears the loading state for the still-in-progress second operation. The eipResult state is also clobbered. Each button's disabled check likely needs to be eipLoading !== null instead of checking a specific key.

Additional Locations (2)

Fix in Cursor Fix in Web

>
{eipLoading === "upgradeStatus" ? "Checking..." : "Get Upgrade Status"}
</button>
<button
onClick={upgradeAccount}
className="card"
style={{ borderColor: "#8b5cf6", color: "#8b5cf6" }}
disabled={eipLoading === "upgrade"}
>
{eipLoading === "upgrade" ? "Upgrading..." : "Upgrade Account (EIP-7702)"}
</button>
</div>

{/* ── EIP-5792 Section ─────────────────────────────────────── */}
<div style={{ marginTop: "24px", marginBottom: "16px", borderTop: "2px solid #10b981", paddingTop: "16px" }}>
<p style={{ color: "#10b981", fontWeight: "bold" }}>EIP-5792 (Batch Calls)</p>
<button
onClick={getCapabilities}
className="card"
style={{ borderColor: "#10b981", color: "#10b981" }}
disabled={eipLoading === "capabilities"}
>
{eipLoading === "capabilities" ? "Fetching..." : "Get Capabilities"}
</button>
<button
onClick={sendBatchCalls}
className="card"
style={{ borderColor: "#10b981", color: "#10b981" }}
disabled={eipLoading === "sendBatch"}
>
{eipLoading === "sendBatch" ? "Sending..." : "Send Batch Calls (wallet_sendCalls)"}
</button>
<button
onClick={getCallsStatus}
className="card"
style={{
borderColor: lastBatchId ? "#10b981" : "#ccc",
color: lastBatchId ? "#10b981" : "#ccc",
}}
disabled={!lastBatchId || eipLoading === "callsStatus"}
>
{eipLoading === "callsStatus" ? "Fetching..." : "Get Calls Status"}
</button>
{lastBatchId && (
<p style={{ fontSize: "0.85rem", color: "#666", marginTop: "0.5rem" }}>
Last Batch ID: <code>{lastBatchId}</code>
</p>
)}
</div>

{/* EIP Result Console */}
{eipResult && (
<div style={{ marginTop: "8px", marginBottom: "16px" }}>
<p style={{ fontWeight: "bold" }}>EIP-7702 / 5792 Result:</p>
<textarea disabled rows={8} value={eipResult} style={{ width: "100%", fontFamily: "monospace", fontSize: "0.9rem" }} />
</div>
)}

{/* Switch Chain */}
<div style={{ marginTop: "16px", marginBottom: "16px" }}>
<p>Switch Chain</p>
Expand Down
10 changes: 10 additions & 0 deletions demo/nextjs-ssr-app/components/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@ const web3authConfig: Web3AuthContextConfig = {
web3AuthNetwork: "sapphire_devnet",
clientId: clientId,
ssr: true,
defaultChainId: "0xaa36a7",
walletServicesConfig: {
confirmationStrategy: "modal",
loginMode: "plugin",
// walletUrls: {
// production: {
// url: "http://localhost:4050",
// },
// },
},
},
};

Expand Down
2 changes: 1 addition & 1 deletion demo/nextjs-ssr-app/next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
44 changes: 22 additions & 22 deletions demo/nextjs-ssr-app/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading