Welcome to
- Scaffold-Alchemy
+ ID-Ephyrian (w/ Alchemy)
{isConnected ? (
diff --git a/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx b/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx
index 34ffe9b..1a9088d 100644
--- a/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx
+++ b/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx
@@ -116,9 +116,9 @@ export const AddressInfoDropdown = ({
- {accountType === "EOA" && "Upgrade to SCA"}
- {accountType === "EOA_7702" && "Already a 7702 + EOA"}
- {accountType === "SCA_4337" && "Already a 4337 - SCA"}
+ {accountType === "EOA" && "Upgrade to a Smart Wallet"}
+ {accountType === "EOA_7702" && "This is a Smart Wallet"}
+ {accountType === "SCA_4337" && "This is a Smart Account"}
{accountType === "UNKNOWN" && "Detecting account type..."}
From e8554c828561acce67179287604d9b97f02f5558 Mon Sep 17 00:00:00 2001
From: Ephyrian <150686927+UMainLove@users.noreply.github.com>
Date: Tue, 1 Jul 2025 13:43:06 +0200
Subject: [PATCH 06/11] feature: Added account upgrade (eip7702) functionality
using external wallets
---
package.json | 12 +-
packages/hardhat/package.json | 6 +-
.../ConnectButton/AddressInfoDropdown.tsx | 165 +++++-
.../nextjs/hooks/scaffold-alchemy/index.ts | 1 +
.../hooks/scaffold-alchemy/useAccountType.ts | 40 +-
.../hooks/scaffold-alchemy/useClient.ts | 2 +-
.../hooks/scaffold-alchemy/useEOAUpgrade.ts | 475 ++++++++++++++++++
packages/nextjs/package.json | 4 +-
.../nextjs/utils/scaffold-alchemy/index.ts | 1 +
yarn.lock | 467 +++++++++++++----
10 files changed, 1049 insertions(+), 124 deletions(-)
create mode 100644 packages/nextjs/hooks/scaffold-alchemy/useEOAUpgrade.ts
diff --git a/package.json b/package.json
index cf88379..04d04f8 100644
--- a/package.json
+++ b/package.json
@@ -48,15 +48,17 @@
},
"packageManager": "yarn@3.2.3",
"dependencies": {
- "@aa-sdk/core": "^4.24.0",
- "@account-kit/infra": "^4.24.0",
- "@account-kit/plugingen": "4.24.0",
- "@account-kit/smart-contracts": "^4.24.0",
+ "@aa-sdk/core": "^4.31.0",
+ "@account-kit/infra": "^4.31.0",
+ "@account-kit/plugingen": "4.31.0",
+ "@account-kit/smart-contracts": "^4.31.0",
+ "@account-kit/wallet-client": "0.1.0-alpha.9",
"viem": "2.28.4"
},
"devDependencies": {
"husky": "^9.1.6",
- "lint-staged": "^15.2.10"
+ "lint-staged": "^15.2.10",
+ "typescript": "^5.8.3"
},
"engines": {
"node": ">=18.18.0"
diff --git a/packages/hardhat/package.json b/packages/hardhat/package.json
index 389fcbc..070b267 100644
--- a/packages/hardhat/package.json
+++ b/packages/hardhat/package.json
@@ -46,9 +46,9 @@
"typescript": "<5.6.0"
},
"dependencies": {
- "@aa-sdk/core": "^4.12.0",
- "@account-kit/infra": "^4.12.0",
- "@account-kit/smart-contracts": "^4.12.0",
+ "@aa-sdk/core": "^4.31.0",
+ "@account-kit/infra": "^4.31.0",
+ "@account-kit/smart-contracts": "^4.31.0",
"@inquirer/password": "^4.0.2",
"@openzeppelin/contracts": "^5.0.2",
"@scaffold-alchemy/shared": "workspace:*",
diff --git a/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx b/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx
index 1a9088d..02f5fab 100644
--- a/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx
+++ b/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx
@@ -1,6 +1,5 @@
/* eslint-disable prettier/prettier */
-/* eslint-disable @typescript-eslint/no-unused-vars */
-import { useRef, useState } from "react";
+import { useRef, useState } from "react";
import { NetworkOptions } from "./NetworkOptions";
import { useLogout } from "@account-kit/react";
import CopyToClipboard from "react-copy-to-clipboard";
@@ -15,10 +14,12 @@ import {
DocumentDuplicateIcon,
QrCodeIcon,
ShieldCheckIcon, //feature_1
+ ExclamationTriangleIcon, // feature_1_part_2
} from "@heroicons/react/24/outline";
import { BlockieAvatar, isENS } from "~~/components/scaffold-alchemy";
-import { useOutsideClick, useAccountType } from "~~/hooks/scaffold-alchemy"; // 'useAccountType' is feature_1
+import { useOutsideClick, useAccountType, useEOAUpgrade } from "~~/hooks/scaffold-alchemy"; // 'useAccountType' is feature_1 AND 'useEOAUpgrade' is feature_1_part_2
import { getTargetNetworks } from "~~/utils/scaffold-alchemy";
+import { EIP7702_CONFIG } from "~~/utils/scaffold-alchemy/eip7702.config"; // feature_1_part_2
const allowedNetworks = getTargetNetworks();
@@ -38,8 +39,18 @@ export const AddressInfoDropdown = ({
const checkSumAddress = getAddress(address);
const { logout } = useLogout();
const { accountType } = useAccountType(); //Feature_1
+ const {
+ upgradeWithPrivateKey,
+ status: upgradeStatus,
+ isEIP7702Supported,
+ isMetaMaskConnected,
+ reset: resetUpgrade
+ } = useEOAUpgrade(); // feature_1_part_2
const [addressCopied, setAddressCopied] = useState(false);
+ const [showPrivateKeyModal, setShowPrivateKeyModal] = useState(false); // feature_1_part_2
+ const [privateKey, setPrivateKey] = useState(""); // feature_1_part_2
+ const [showPrivateKey, setShowPrivateKey] = useState(false); // feature_1_part_2
const [selectingNetwork, setSelectingNetwork] = useState(false);
const dropdownRef = useRef
(null);
@@ -49,6 +60,49 @@ export const AddressInfoDropdown = ({
};
useOutsideClick(dropdownRef, closeDropdown);
+ // feature_1_part_2: EIP-7702 Upgrade Button block start -----------------
+
+ // Determine if upgrade button should be shown and enabled
+ const showUpgradeButton = accountType === "EOA" && isMetaMaskConnected;
+ const isUpgradeEnabled = showUpgradeButton && isEIP7702Supported;
+ const isUpgrading = upgradeStatus === "upgrading";
+
+ // Get the appropriate message for the account status
+ const getAccountStatusMessage = () => {
+ if (isUpgrading) return "Upgrading...";
+ if (accountType === "EOA" && isMetaMaskConnected) return EIP7702_CONFIG.messages.UPGRADE_BUTTON;
+ if (accountType === "EOA_7702") return EIP7702_CONFIG.messages.UPGRADED_EOA;
+ if (accountType === "SCA_4337") return EIP7702_CONFIG.messages.SMART_ACCOUNT;
+ if (accountType === "UNKNOWN") return EIP7702_CONFIG.messages.DETECTING;
+ return "This is an EOA";
+ };
+
+ const handleUpgradeClick = async () => {
+ if (isUpgradeEnabled && !isUpgrading) {
+ closeDropdown();
+
+ // Since MetaMask doesn't support EIP-7702 yet, show private key modal directly
+ setShowPrivateKeyModal(true);
+ }
+ };
+
+ const handlePrivateKeyUpgrade = async () => {
+ if (!privateKey) return;
+
+ try {
+ // Reset the error state before attempting private key upgrade
+ resetUpgrade();
+ await upgradeWithPrivateKey(privateKey);
+ setShowPrivateKeyModal(false);
+ setPrivateKey(""); // Clear private key from memory
+ } catch (error) {
+ console.error("Private key upgrade failed:", error);
+ // Keep the modal open on error
+ }
+ };
+
+ // feature_1_part_2: EIP-7702 Upgrade Button block end -------------------
+
return (
<>
@@ -113,15 +167,31 @@ export const AddressInfoDropdown = ({
-
-
-
- {accountType === "EOA" && "Upgrade to a Smart Wallet"}
- {accountType === "EOA_7702" && "This is a Smart Wallet"}
- {accountType === "SCA_4337" && "This is a Smart Account"}
- {accountType === "UNKNOWN" && "Detecting account type..."}
-
-
+ {showUpgradeButton ? (
+
+ ) : (
+
+
+
+ {getAccountStatusMessage()}
+
+
+ )}
{allowedNetworks.length > 1 ? (
@@ -147,6 +217,75 @@ export const AddressInfoDropdown = ({
+
+ {/* Private Key Modal */}
+ {showPrivateKeyModal && (
+
+
+
+
+ EIP-7702 Upgrade with Private Key
+
+
+
+
+
+
Security Warning
+
+ {"MetaMask doesn't support EIP-7702 yet. You can upgrade using your private key, but this is risky. Never share your private key with anyone!"}
+
+
+
+
+
+
+
+ setPrivateKey(e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
{
+ setShowPrivateKeyModal(false);
+ setPrivateKey("");
+ setShowPrivateKey(false);
+ }}>
+
+ )}
>
);
-};
+};
\ No newline at end of file
diff --git a/packages/nextjs/hooks/scaffold-alchemy/index.ts b/packages/nextjs/hooks/scaffold-alchemy/index.ts
index 615a4ce..c4a65d1 100644
--- a/packages/nextjs/hooks/scaffold-alchemy/index.ts
+++ b/packages/nextjs/hooks/scaffold-alchemy/index.ts
@@ -15,3 +15,4 @@ export * from "./useTransactor";
export * from "./useWatchBalance";
export * from "./useSelectedNetwork";
export * from "./useAccountType"; //feature_1
+export * from "./useEOAUpgrade"; //feature_1_part_2
diff --git a/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts b/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
index da72bc6..8ad250a 100644
--- a/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
+++ b/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
@@ -2,8 +2,9 @@
// This code is part of feature_1: "Account Type Detection"
import { useEffect, useState } from "react";
-import { useAccount } from "wagmi";
+import { useAccount, usePublicClient } from "wagmi"; //usePublicClient is part of feature_1_part_2
import { useClient } from "./useClient";
+import { useTargetNetwork } from "./useTargetNetwork"; //useTargetNetwork is part of feature_1_part_2
export type AccountType = "EOA" | "EOA_7702" | "SCA_4337" | "UNKNOWN";
@@ -12,6 +13,8 @@ export const useAccountType = () => {
const [isLoading, setIsLoading] = useState(true);
const { isConnected, connector } = useAccount();
const { client, address } = useClient();
+ const publicClient = usePublicClient(); // usePublicClient is part of feature_1_part_2
+ const { targetNetwork } = useTargetNetwork(); //useTargetNetwork is part of feature_1_part_2
useEffect(() => {
@@ -26,6 +29,21 @@ export const useAccountType = () => {
connectorType: connector?.type,
connectorName: connector?.name,
});
+
+ // Check if there's bytecode at the address (indicates smart account) - feature_1_part_2
+ let hasCode = false;
+ if (address && publicClient) {
+ try {
+ const code = await publicClient.getBytecode({
+ address: address as `0x${string}`,
+ blockTag: 'latest'
+ });
+ hasCode = code !== undefined && code !== "0x";
+ console.log("[useAccountType] Bytecode check:", { address, hasCode });
+ } catch (error) {
+ console.error("[useAccountType] Error checking bytecode:", error);
+ }
+ }
// If we have a client and address, check if it's from external wallet or not
if (client && address) {
@@ -35,9 +53,13 @@ export const useAccountType = () => {
connector?.type === "walletConnect" ||
connector?.name?.toLowerCase().includes("wallet") ||
connector?.name?.toLowerCase().includes("metamask");
-
- if (isExternalWallet) {
- // External wallet with SCA (future: detect 7702)
+
+ //added extended logic for feature_1_part_2
+ if (isExternalWallet && hasCode) {
+ // External wallet with code at address = likely 7702 upgraded
+ setAccountType("EOA_7702");
+ } else if (isExternalWallet) {
+ // External wallet without code = regular EOA
setAccountType("EOA");
} else {
// Has SCA but no external wallet = email/social login
@@ -45,7 +67,12 @@ export const useAccountType = () => {
}
} else if (isConnected && !client) {
// Connected with external wallet but no SCA client
- setAccountType("EOA");
+ if (hasCode) {
+ // Has code but no SCA client = 7702 upgraded EOA
+ setAccountType("EOA_7702");
+ } else {
+ setAccountType("EOA");
+ }
} else if (!isConnected && address) {
// Has address but not connected via wagmi = likely email/social
setAccountType("SCA_4337");
@@ -61,7 +88,8 @@ export const useAccountType = () => {
};
detectAccountType();
- }, [isConnected, connector, client, address]);
+ }, [isConnected, connector, client, address, publicClient, targetNetwork]);
+
return { accountType, isLoading };
};
diff --git a/packages/nextjs/hooks/scaffold-alchemy/useClient.ts b/packages/nextjs/hooks/scaffold-alchemy/useClient.ts
index 3f1dd4e..7497d96 100644
--- a/packages/nextjs/hooks/scaffold-alchemy/useClient.ts
+++ b/packages/nextjs/hooks/scaffold-alchemy/useClient.ts
@@ -15,7 +15,7 @@ export const useClient = (
network: RPC_CHAIN_NAMES[scaffoldConfig.targetNetworks[0].id] as Network,
});
const enhancedApiDecorator = alchemyEnhancedApiActions(alchemy);
- return { client: client?.extend(enhancedApiDecorator), origClient: client, address };
+ return { client: client?.extend(enhancedApiDecorator as any), origClient: client, address };
};
export type Client = ReturnType["client"];
diff --git a/packages/nextjs/hooks/scaffold-alchemy/useEOAUpgrade.ts b/packages/nextjs/hooks/scaffold-alchemy/useEOAUpgrade.ts
new file mode 100644
index 0000000..3908908
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-alchemy/useEOAUpgrade.ts
@@ -0,0 +1,475 @@
+/* eslint-disable prettier/prettier */
+
+//feature_1_part_2: EOA Upgrade to Smart Account with EIP-7702
+
+import { useState, useEffect } from "react";
+import { createModularAccountV2Client, ModularAccountV2Client } from "@account-kit/smart-contracts";
+import { WalletClientSigner } from "@aa-sdk/core";
+import { alchemy, sepolia, mainnet } from "@account-kit/infra";
+import { useAccount, useWalletClient } from "wagmi";
+import { ALCHEMY_CONFIG } from "@scaffold-alchemy/shared";
+import { notification } from "~~/utils/scaffold-alchemy";
+import { useTargetNetwork } from "./useTargetNetwork";
+import { EIP7702_CONFIG } from "~~/utils/scaffold-alchemy/eip7702.config";
+import { zeroAddress, createWalletClient, custom, type Hex } from "viem";
+import { privateKeyToAccount } from "viem/accounts";
+
+
+export type UpgradeStatus = "idle" | "upgrading" | "success" | "error";
+
+interface UpgradeError {
+ code: string;
+ message: string;
+}
+
+export const useEOAUpgrade = () => {
+ const [status, setStatus] = useState("idle");
+ const [error, setError] = useState(null);
+ const [client, setClient] = useState(null);
+ const { address, connector } = useAccount();
+ const { data: walletClient } = useWalletClient();
+ const { targetNetwork } = useTargetNetwork();
+
+ const isEIP7702Supported = () => {
+ return EIP7702_CONFIG.supportedChains.includes(targetNetwork.id as (1 | 11155111));
+ };
+
+ const isMetaMaskConnected = () => {
+ return connector?.name?.toLowerCase().includes("metamask") ||
+ connector?.id === "injected";
+ };
+
+ // Initialize the client when wallet is connected
+ useEffect(() => {
+ const initializeClient = async () => {
+ // Check conditions inline to avoid hook dependency issues
+ const isMetaMask = connector?.name?.toLowerCase().includes("metamask") || connector?.id === "injected";
+ const isSupported = EIP7702_CONFIG.supportedChains.includes(targetNetwork.id as (1 | 11155111));
+
+ if (!walletClient || !address || !isMetaMask || !isSupported) {
+ return;
+ }
+
+ try {
+ console.log("[EOA Upgrade] Initializing client...");
+
+ // Create a custom wallet client for MetaMask
+ const customWalletClient = createWalletClient({
+ account: address as Hex,
+ chain: targetNetwork.id === 1 ? mainnet : sepolia,
+ transport: custom(window.ethereum!),
+ });
+
+ // Create the base WalletClientSigner
+ const baseSigner = new WalletClientSigner(customWalletClient, "metamask");
+
+ // Create an enhanced signer with EIP-7702 support
+ const signer = {
+ ...baseSigner,
+ signerType: baseSigner.signerType,
+ inner: baseSigner.inner,
+ getAddress: baseSigner.getAddress,
+ signMessage: baseSigner.signMessage,
+ signTypedData: baseSigner.signTypedData,
+ signAuthorization: async (unsignedAuth: any): Promise => {
+ const contractAddress = unsignedAuth.contractAddress ?? unsignedAuth.address;
+
+ try {
+ console.log("[EOA Upgrade] Signing EIP-7702 authorization for MetaMask...");
+ console.log("[EOA Upgrade] Authorization request:", unsignedAuth);
+
+ // For MetaMask, we need to use the wallet_invokeMethod for EIP-7702
+ // or fallback to manual typed data signing
+ try {
+ // Try the new MetaMask EIP-7702 method first
+ const signedAuth = await window.ethereum!.request({
+ method: 'wallet_invokeMethod',
+ params: [
+ 'eth_signAuthorization',
+ [{
+ chainId: `0x${unsignedAuth.chainId.toString(16)}`,
+ nonce: `0x${unsignedAuth.nonce.toString(16)}`,
+ address: contractAddress,
+ }]
+ ]
+ });
+
+ console.log("[EOA Upgrade] EIP-7702 authorization signed:", signedAuth);
+ return {
+ chainId: unsignedAuth.chainId,
+ nonce: unsignedAuth.nonce,
+ address: contractAddress!,
+ ...signedAuth
+ };
+ } catch (methodError) {
+ console.log("[EOA Upgrade] wallet_invokeMethod not supported, falling back to typed data signing:", methodError);
+
+ // Try EIP-712 typed data signing as a fallback
+ // Even though this won't produce the correct EIP-7702 format,
+ // we let the user complete the signing process before showing the private key option
+ console.log("[EOA Upgrade] Attempting typed data signing (this will likely fail but allows user to complete the flow)");
+
+ try {
+ const domain = {
+ name: "EIP-7702",
+ version: "1",
+ chainId: unsignedAuth.chainId,
+ };
+
+ const types = {
+ Authorization: [
+ { name: "chainId", type: "uint256" },
+ { name: "nonce", type: "uint256" },
+ { name: "address", type: "address" },
+ ],
+ };
+
+ const message = {
+ chainId: unsignedAuth.chainId,
+ nonce: unsignedAuth.nonce,
+ address: contractAddress,
+ };
+
+ // Let the user complete the MetaMask signing flow
+ const signature = await window.ethereum!.request({
+ method: 'eth_signTypedData_v4',
+ params: [address, JSON.stringify({ domain, types, primaryType: 'Authorization', message })],
+ });
+
+ // Parse signature components
+ const sig = signature as string;
+ const r = `0x${sig.slice(2, 66)}`;
+ const s = `0x${sig.slice(66, 130)}`;
+ const v = parseInt(sig.slice(130, 132), 16);
+
+ console.log("[EOA Upgrade] Typed data signature created (but won't work for EIP-7702):", { r, s, v });
+
+ // Even though we got a signature, it's not in the correct EIP-7702 format
+ // The bundler will reject this, but at least the user completed the MetaMask flow
+ return {
+ chainId: unsignedAuth.chainId,
+ nonce: unsignedAuth.nonce,
+ address: contractAddress!,
+ r: r as Hex,
+ s: s as Hex,
+ v: BigInt(v),
+ yParity: v - 27,
+ };
+ } catch (signError: any) {
+ // User rejected or error in signing
+ if (signError?.code === 4001 || signError?.message?.includes("rejected")) {
+ throw new Error("User rejected the signature request");
+ }
+
+ // If typed data signing also fails, then show the private key option
+ throw new Error(
+ "MetaMask doesn't support EIP-7702 authorization signing yet. " +
+ "Please use the private key option to upgrade your account."
+ );
+ }
+ }
+ } catch (error) {
+ console.error("[EOA Upgrade] Failed to sign authorization:", error);
+ throw error;
+ }
+ },
+ };
+
+ // Use the correct chain from @account-kit/infra
+ const alchemyChain = targetNetwork.id === 1 ? mainnet : sepolia;
+
+ // Get the API key and policy ID from the shared config
+ const apiKey = ALCHEMY_CONFIG.ALCHEMY_API_KEY;
+ const policyId = ALCHEMY_CONFIG.ALCHEMY_GAS_POLICY_ID;
+
+ // Create the Modular Account V2 client with EIP-7702 support
+ const modularClient = await createModularAccountV2Client({
+ signer,
+ chain: alchemyChain,
+ transport: alchemy({
+ apiKey,
+ }),
+ policyId,
+ // This enables EIP-7702 mode
+ mode: "7702",
+ });
+
+ setClient(modularClient);
+ console.log("[EOA Upgrade] Client initialized successfully");
+ } catch (err) {
+ console.error("[EOA Upgrade] Failed to initialize client:", err);
+ }
+ };
+
+ initializeClient();
+ }, [walletClient, address, targetNetwork.id, connector]);
+
+ const upgradeToSmartAccount = async () => {
+ console.log("[EOA Upgrade] Starting upgrade process...");
+
+ try {
+ // Reset state
+ setStatus("upgrading");
+ setError(null);
+
+ // Validation checks
+ if (!address) {
+ throw { code: "NO_ADDRESS", message: EIP7702_CONFIG.errors.NO_ADDRESS };
+ }
+
+ if (!isMetaMaskConnected()) {
+ throw { code: "NOT_METAMASK", message: EIP7702_CONFIG.errors.NOT_METAMASK };
+ }
+
+ if (!isEIP7702Supported()) {
+ throw {
+ code: "UNSUPPORTED_NETWORK",
+ message: EIP7702_CONFIG.errors.UNSUPPORTED_NETWORK
+ };
+ }
+
+ if (!client) {
+ throw { code: "NO_CLIENT", message: "Client not initialized. Please try again." };
+ }
+
+ notification.info(EIP7702_CONFIG.messages.PREPARING);
+
+ // First, check if MetaMask supports EIP-7702
+ // Try to detect if MetaMask has the experimental feature enabled
+ try {
+ // Check if the wallet client has experimental methods
+ const provider = await walletClient?.request({ method: 'eth_accounts' });
+ console.log("[EOA Upgrade] Wallet provider check:", provider);
+ } catch (checkErr) {
+ console.warn("[EOA Upgrade] Provider check failed:", checkErr);
+ }
+
+ console.log("[EOA Upgrade] Sending user operation to trigger EIP-7702 delegation...");
+ notification.info("Please approve the smart account delegation in MetaMask...");
+
+ // Send a simple user operation
+ // The SDK should automatically request EIP-7702 authorization from MetaMask
+ const uoHash = await client.sendUserOperation({
+ uo: {
+ target: zeroAddress, // Send to self
+ data: "0x", // Empty data
+ value: 0n,
+ },
+ });
+
+ console.log("[EOA Upgrade] User operation sent, hash:", uoHash);
+ notification.info(EIP7702_CONFIG.messages.UPGRADING);
+
+ // Wait for the user operation to be mined
+ const txHash = await client.waitForUserOperationTransaction(uoHash);
+ console.log("[EOA Upgrade] Transaction hash:", txHash);
+
+ if (txHash) {
+ setStatus("success");
+ notification.success(EIP7702_CONFIG.messages.SUCCESS);
+ console.log("[EOA Upgrade] Upgrade successful!");
+
+ // Trigger a refresh of account type detection
+ setTimeout(() => {
+ window.location.reload();
+ }, 2000);
+ } else {
+ throw new Error("Transaction failed");
+ }
+
+ } catch (err: any) {
+ console.error("[EOA Upgrade] Upgrade error:", err);
+
+ // Handle specific error cases
+ if (err.code === "UNSUPPORTED_NETWORK" || err.code === "NOT_METAMASK" || err.code === "NO_ADDRESS" || err.code === "NO_CLIENT") {
+ setError(err);
+ notification.error(err.message);
+ } else if (err.message?.includes("rejected") || err.message?.includes("denied") || err.code === 4001) {
+ setError({
+ code: "USER_REJECTED",
+ message: EIP7702_CONFIG.errors.USER_REJECTED
+ });
+ notification.error(EIP7702_CONFIG.errors.USER_REJECTED);
+ } else if (err.message?.includes("AA23") || err.message?.includes("Invalid 7702 Auth signature")) {
+ // AA23 error or Invalid 7702 Auth signature means the signature format is wrong
+ setError({
+ code: "SIGNATURE_VALIDATION_FAILED",
+ message: "The signature format is not valid for EIP-7702. MetaMask cannot create the required signature format."
+ });
+ notification.error("Invalid EIP-7702 signature format. Please use the private key option to upgrade.");
+ } else if (err.message?.includes("MetaMask doesn't support EIP-7702") ||
+ err.message?.includes("Please use the private key option")) {
+ // MetaMask doesn't support EIP-7702 signing
+ setError({
+ code: "METAMASK_EIP7702_NOT_SUPPORTED",
+ message: "MetaMask doesn't support EIP-7702 authorization signing. This feature requires a wallet that can sign EIP-7702 authorizations with the specific format (0x05 magic byte + RLP encoding)."
+ });
+ notification.error("MetaMask doesn't support EIP-7702 yet. Please use the private key option.");
+ } else if (err.message?.includes("AccountTypeNotSupportedError") || err.message?.includes("json-rpc")) {
+ // Viem doesn't support JSON-RPC accounts for signAuthorization
+ setError({
+ code: "ACCOUNT_TYPE_NOT_SUPPORTED",
+ message: "EIP-7702 signing failed. MetaMask may not support this feature yet or needs to be updated."
+ });
+ notification.error("EIP-7702 authorization failed. Please ensure MetaMask supports EIP-7702 features.");
+ } else if (err.message?.includes("eth_signTypedData_v4")) {
+ // MetaMask doesn't support the signing method
+ setError({
+ code: "METAMASK_NOT_SUPPORTED",
+ message: "Your MetaMask version doesn't support EIP-7702. Please update to the latest version."
+ });
+ notification.error("MetaMask doesn't support EIP-7702 signing. Please update MetaMask.");
+ } else if (err.message?.includes("precheck failed") || err.message?.includes("initCode is empty")) {
+ setError({
+ code: "SDK_NOT_READY",
+ message: EIP7702_CONFIG.errors.SDK_NOT_READY
+ });
+ notification.error(EIP7702_CONFIG.errors.SDK_NOT_READY);
+ } else {
+ setError({
+ code: "UNKNOWN_ERROR",
+ message: err.message || EIP7702_CONFIG.errors.UNKNOWN_ERROR
+ });
+ notification.error(err.message || EIP7702_CONFIG.errors.UNKNOWN_ERROR);
+ }
+
+ setStatus("error");
+ }
+ };
+
+ const reset = () => {
+ setStatus("idle");
+ setError(null);
+ };
+
+ const upgradeWithPrivateKey = async (privateKey: string) => {
+ console.log("[EOA Upgrade] Starting private key upgrade process...");
+
+ try {
+ // Reset state
+ setStatus("upgrading");
+ setError(null);
+
+ // Validation checks
+ if (!privateKey || !privateKey.startsWith("0x") || privateKey.length !== 66) {
+ throw { code: "INVALID_PRIVATE_KEY", message: "Invalid private key format. Must be a 0x-prefixed 64 character hex string." };
+ }
+
+ if (!isEIP7702Supported()) {
+ throw {
+ code: "UNSUPPORTED_NETWORK",
+ message: EIP7702_CONFIG.errors.UNSUPPORTED_NETWORK
+ };
+ }
+
+ notification.info("Creating account from private key...");
+
+ // Create account from private key
+ const account = privateKeyToAccount(privateKey as Hex);
+ console.log("[EOA Upgrade] Account created from private key:", account.address);
+
+ // Create a simple signer that matches the SmartAccountSigner interface
+ const signer = {
+ signerType: "local",
+ inner: account,
+ getAddress: async () => account.address,
+ signMessage: async (message: any) => account.signMessage({ message }),
+ signTypedData: async (params: any) => account.signTypedData(params),
+ signAuthorization: async (unsignedAuth: any) => {
+ console.log("[EOA Upgrade] Signing authorization with private key account:", unsignedAuth);
+ return account.signAuthorization(unsignedAuth);
+ }
+ };
+
+ // Use the correct chain from @account-kit/infra
+ const alchemyChain = targetNetwork.id === 1 ? mainnet : sepolia;
+
+ // Get the API key and policy ID from the shared config
+ const apiKey = ALCHEMY_CONFIG.ALCHEMY_API_KEY;
+ const policyId = ALCHEMY_CONFIG.ALCHEMY_GAS_POLICY_ID;
+
+ console.log("[EOA Upgrade] Creating client with LocalAccountSigner...");
+
+ // Create the Modular Account V2 client with EIP-7702 support
+ const modularClient = await createModularAccountV2Client({
+ signer: signer as any, // Type cast needed due to version mismatch between packages
+ chain: alchemyChain,
+ transport: alchemy({
+ apiKey,
+ }),
+ policyId,
+ // This enables EIP-7702 mode
+ mode: "7702",
+ });
+
+ console.log("[EOA Upgrade] Client created successfully, sending user operation...");
+ notification.info("Sending EIP-7702 delegation transaction...");
+
+ // Send a simple user operation
+ // The SDK should automatically request EIP-7702 authorization
+ const uoHash = await modularClient.sendUserOperation({
+ uo: {
+ target: zeroAddress, // Send to zero address
+ data: "0x", // Empty data
+ value: 0n,
+ },
+ });
+
+ console.log("[EOA Upgrade] User operation sent, hash:", uoHash);
+ notification.info(EIP7702_CONFIG.messages.UPGRADING);
+
+ // Wait for the user operation to be mined
+ const txHash = await modularClient.waitForUserOperationTransaction(uoHash);
+ console.log("[EOA Upgrade] Transaction hash:", txHash);
+
+ if (txHash) {
+ setStatus("success");
+ notification.success(EIP7702_CONFIG.messages.SUCCESS);
+ console.log("[EOA Upgrade] Upgrade successful!");
+
+ // Clear the private key from memory
+ privateKey = "";
+
+ // Trigger a refresh of account type detection
+ setTimeout(() => {
+ window.location.reload();
+ }, 2000);
+ } else {
+ throw new Error("Transaction failed");
+ }
+
+ } catch (err: any) {
+ console.error("[EOA Upgrade] Private key upgrade error:", err);
+
+ // Handle specific error cases
+ if (err.code === "UNSUPPORTED_NETWORK" || err.code === "INVALID_PRIVATE_KEY") {
+ setError(err);
+ notification.error(err.message);
+ } else if (err.message?.includes("AA23")) {
+ setError({
+ code: "SIGNATURE_VALIDATION_FAILED",
+ message: "EIP-7702 signature validation failed. Please check your private key."
+ });
+ notification.error("EIP-7702 authorization failed. Please verify your private key is correct.");
+ } else {
+ setError({
+ code: "UNKNOWN_ERROR",
+ message: err.message || EIP7702_CONFIG.errors.UNKNOWN_ERROR
+ });
+ notification.error(err.message || EIP7702_CONFIG.errors.UNKNOWN_ERROR);
+ }
+
+ setStatus("error");
+ }
+ };
+
+ return {
+ upgradeToSmartAccount,
+ upgradeWithPrivateKey,
+ status,
+ error,
+ isEIP7702Supported: isEIP7702Supported(),
+ isMetaMaskConnected: isMetaMaskConnected(),
+ reset,
+ };
+};
\ No newline at end of file
diff --git a/packages/nextjs/package.json b/packages/nextjs/package.json
index 3eeccb1..82e04b6 100644
--- a/packages/nextjs/package.json
+++ b/packages/nextjs/package.json
@@ -14,8 +14,8 @@
"vercel:yolo": "vercel --build-env YARN_ENABLE_IMMUTABLE_INSTALLS=false --build-env ENABLE_EXPERIMENTAL_COREPACK=1 --build-env NEXT_PUBLIC_IGNORE_BUILD_ERROR=true --build-env VERCEL_TELEMETRY_DISABLED=1"
},
"dependencies": {
- "@account-kit/infra": "^4.24.0",
- "@account-kit/react": "^4.24.0",
+ "@account-kit/infra": "^4.31.0",
+ "@account-kit/react": "^4.31.0",
"@heroicons/react": "^2.1.5",
"@scaffold-alchemy/shared": "workspace:*",
"@tanstack/react-query": "^5.64.1",
diff --git a/packages/nextjs/utils/scaffold-alchemy/index.ts b/packages/nextjs/utils/scaffold-alchemy/index.ts
index 6d69193..ca7a151 100644
--- a/packages/nextjs/utils/scaffold-alchemy/index.ts
+++ b/packages/nextjs/utils/scaffold-alchemy/index.ts
@@ -4,3 +4,4 @@ export * from "./notification";
export * from "./block";
export * from "./decodeTxData";
export * from "./getParsedError";
+export * from "./eip7702.config"; // feature_1_part_2
diff --git a/yarn.lock b/yarn.lock
index 6b5dccd..446e924 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5,7 +5,7 @@ __metadata:
version: 6
cacheKey: 8
-"@aa-sdk/core@npm:^4.12.0, @aa-sdk/core@npm:^4.24.0, @aa-sdk/core@npm:^4.31.0":
+"@aa-sdk/core@npm:^4.31.0":
version: 4.31.0
resolution: "@aa-sdk/core@npm:4.31.0"
dependencies:
@@ -18,31 +18,44 @@ __metadata:
languageName: node
linkType: hard
-"@account-kit/core@npm:^4.31.0":
- version: 4.31.0
- resolution: "@account-kit/core@npm:4.31.0"
+"@aa-sdk/core@npm:^4.43.1, @aa-sdk/core@npm:^4.47.0":
+ version: 4.47.0
+ resolution: "@aa-sdk/core@npm:4.47.0"
dependencies:
- "@account-kit/infra": ^4.31.0
- "@account-kit/logging": ^4.31.0
- "@account-kit/react-native-signer": ^4.31.0
- "@account-kit/signer": ^4.31.0
- "@account-kit/smart-contracts": ^4.31.0
+ abitype: ^0.8.3
+ eventemitter3: ^5.0.1
+ zod: ^3.22.4
+ peerDependencies:
+ viem: ^2.29.2
+ checksum: 2a1bc3e1be6d301a2137da5a3434622be4207159783e57bd97f695b3729c8219629a42094c996de15b7c401a5d2ba2e645453eb177372908a2642680888fbb67
+ languageName: node
+ linkType: hard
+
+"@account-kit/core@npm:^4.47.0":
+ version: 4.47.0
+ resolution: "@account-kit/core@npm:4.47.0"
+ dependencies:
+ "@account-kit/infra": ^4.47.0
+ "@account-kit/logging": ^4.47.0
+ "@account-kit/react-native-signer": ^4.47.0
+ "@account-kit/signer": ^4.47.0
+ "@account-kit/smart-contracts": ^4.47.0
"@solana/web3.js": ^1.98.0
alchemy-sdk: ^3.0.0
js-cookie: ^3.0.5
zod: ^3.22.4
zustand: ^5.0.0-rc.2
peerDependencies:
- viem: ^2.22.6
+ viem: ^2.29.2
wagmi: ^2.12.7
dependenciesMeta:
alchemy-sdk:
optional: true
- checksum: 695dd29c29f7ca3afbb6b59fd3f7266376710de14b837a10ac096df579faea53df440d9cececd0596f8ccbaa5b913676e92319fad9bbee08788305f2b41ecc8b
+ checksum: 5d6674f28b008e3828aaee84d028b92306825f87b3fe7102b7c5810ec1ed7af95002e38770834376dd24e44ce07aea703f641efac9e115e3f17faa641fbd5cd9
languageName: node
linkType: hard
-"@account-kit/infra@npm:^4.12.0, @account-kit/infra@npm:^4.24.0, @account-kit/infra@npm:^4.31.0":
+"@account-kit/infra@npm:^4.31.0":
version: 4.31.0
resolution: "@account-kit/infra@npm:4.31.0"
dependencies:
@@ -60,6 +73,24 @@ __metadata:
languageName: node
linkType: hard
+"@account-kit/infra@npm:^4.43.1, @account-kit/infra@npm:^4.47.0":
+ version: 4.47.0
+ resolution: "@account-kit/infra@npm:4.47.0"
+ dependencies:
+ "@aa-sdk/core": ^4.47.0
+ "@account-kit/logging": ^4.47.0
+ alchemy-sdk: ^3.0.0
+ eventemitter3: ^5.0.1
+ zod: ^3.22.4
+ peerDependencies:
+ viem: ^2.29.2
+ dependenciesMeta:
+ alchemy-sdk:
+ optional: true
+ checksum: 0311e17b58baf401d675002aa119d9fe027b29a0d44a8c99f9eedbfeae041b563e367a39bb92c5ee53743aa5b38d00f7cc8a615ae28ca132953729bacc2b0999
+ languageName: node
+ linkType: hard
+
"@account-kit/logging@npm:^4.31.0":
version: 4.31.0
resolution: "@account-kit/logging@npm:4.31.0"
@@ -70,11 +101,21 @@ __metadata:
languageName: node
linkType: hard
-"@account-kit/plugingen@npm:4.24.0":
- version: 4.24.0
- resolution: "@account-kit/plugingen@npm:4.24.0"
+"@account-kit/logging@npm:^4.47.0":
+ version: 4.47.0
+ resolution: "@account-kit/logging@npm:4.47.0"
dependencies:
- "@aa-sdk/core": ^4.24.0
+ "@segment/analytics-next": 1.74.0
+ uuid: ^11.0.2
+ checksum: 94bd933803e5a87a8392b2a9ad978a4bee83aa7ef0f0045b92a61ca0ecf4326704eee2ba55b9adf651db78d8b013df9eb212b4cf3641e5862ce03b726c0d27fd
+ languageName: node
+ linkType: hard
+
+"@account-kit/plugingen@npm:4.31.0":
+ version: 4.31.0
+ resolution: "@account-kit/plugingen@npm:4.31.0"
+ dependencies:
+ "@aa-sdk/core": ^4.31.0
bundle-require: ^4.0.2
cac: ^6.7.14
change-case: ^5.4.3
@@ -92,34 +133,38 @@ __metadata:
viem: ^2.22.6
bin:
plugingen: dist/esm/cli.js
- checksum: c4e692bf047363cee1a25b94584ab6925d7926ebe9ed055e577b7196566056585fd4318917a1e7e6a5bc8ccd6d6a60039afa7d32d4d1be8437e28a1dd471850c
+ checksum: 33327aa277a5e5276d718939de4a0e95ed807576964d7164b81b9eda410d6d4a50d3b965e175b79abcd78e6b8a5d8dad97011cf5c8433de0fee9acb57259415e
languageName: node
linkType: hard
-"@account-kit/react-native-signer@npm:^4.31.0":
- version: 4.31.0
- resolution: "@account-kit/react-native-signer@npm:4.31.0"
+"@account-kit/react-native-signer@npm:^4.47.0":
+ version: 4.47.0
+ resolution: "@account-kit/react-native-signer@npm:4.47.0"
dependencies:
- "@aa-sdk/core": ^4.31.0
- "@account-kit/signer": ^4.31.0
- viem: ^2.21.40
+ "@aa-sdk/core": ^4.47.0
+ "@account-kit/signer": ^4.47.0
+ "@turnkey/react-native-passkey-stamper": ^1.0.14
+ uuid: ^11.1.0
+ viem: 2.29.2
peerDependencies:
- react: ">=18.0.0"
+ react: ">=18.2.0"
react-native: ">=0.76.0"
+ react-native-get-random-values: ^1.11.0
react-native-inappbrowser-reborn: ^3.7.0
react-native-mmkv: ^3.1.0
- checksum: 0fe27ca6bbced22298427b71591f98f7668924f6c63b444eafb8d26c72844cbcba7bd3dd3a2999876f567e796ad690daed5efe44623974b0e9e2637040404862
+ react-native-passkey: ^3.1.0
+ checksum: 1200e529273354495f11a478f607e294647ffdb6f877bccc5f09380090db450dae31feb58f73ce70f9f97794f1a3e5ca994e1cff1d58b2bc87b1727fc82943ad
languageName: node
linkType: hard
-"@account-kit/react@npm:^4.24.0":
- version: 4.31.0
- resolution: "@account-kit/react@npm:4.31.0"
+"@account-kit/react@npm:^4.31.0":
+ version: 4.47.0
+ resolution: "@account-kit/react@npm:4.47.0"
dependencies:
- "@account-kit/core": ^4.31.0
- "@account-kit/infra": ^4.31.0
- "@account-kit/logging": ^4.31.0
- "@account-kit/signer": ^4.31.0
+ "@account-kit/core": ^4.47.0
+ "@account-kit/infra": ^4.47.0
+ "@account-kit/logging": ^4.47.0
+ "@account-kit/signer": ^4.47.0
"@solana/web3.js": ^1.98.0
"@tanstack/react-form": ^0.33.0
"@tanstack/zod-form-adapter": ^0.33.0
@@ -132,27 +177,30 @@ __metadata:
zustand: ^5.0.0-rc.2
peerDependencies:
"@tanstack/react-query": ^5.28.9
- react: ^18.2.0
- react-dom: ^18.2.0
+ react: ">=18.2.0"
+ react-dom: ">=18.2.0"
tailwindcss: ^3.4.3
- viem: ^2.22.6
+ viem: ^2.29.2
wagmi: ^2.12.7
dependenciesMeta:
alchemy-sdk:
optional: true
- checksum: f92c3802ab39785ce58f242d23815ce378ec23715775d5e37be88722bdd11e58b0ece026f296f2faca9560625a143cab16fd3c880f1fc2fc070b7e7b0fe9c434
+ checksum: 2468c57f6189535a5961e0631d62b369397cfda643e08430b68d8abe4af2721d2ab3b260282c3cea2bf7d520540c92594c361a44442623a3b634739816d78dbd
languageName: node
linkType: hard
-"@account-kit/signer@npm:^4.31.0":
- version: 4.31.0
- resolution: "@account-kit/signer@npm:4.31.0"
+"@account-kit/signer@npm:^4.47.0":
+ version: 4.47.0
+ resolution: "@account-kit/signer@npm:4.47.0"
dependencies:
- "@aa-sdk/core": ^4.31.0
- "@account-kit/logging": ^4.31.0
+ "@aa-sdk/core": ^4.47.0
+ "@account-kit/logging": ^4.47.0
+ "@noble/curves": ^1.9.2
+ "@noble/hashes": 1.7.1
+ "@noble/secp256k1": ^2.3.0
"@solana/web3.js": ^1.98.0
- "@turnkey/http": ^2.6.2
- "@turnkey/iframe-stamper": ^1.0.0
+ "@turnkey/http": ^3.4.2
+ "@turnkey/iframe-stamper": ^2.5.0
"@turnkey/viem": ^0.4.8
"@turnkey/webauthn-stamper": ^0.4.3
bs58: ^6.0.0
@@ -160,12 +208,12 @@ __metadata:
zod: ^3.22.4
zustand: ^5.0.0-rc.2
peerDependencies:
- viem: ^2.22.6
- checksum: 05ac61a9969d469ab9f2ec2b846a0000b8b7336da7c2d56dedac14e20650337e733687c3d10041e9c3904ac75818e866917cd6bf4e75194f98edbfeef99dff5a
+ viem: 2.29.2
+ checksum: d7de96efc80d14af1018eda6d8415672f43376856c21672c001eaf9c6b7ffb42b8e0cca3628417995abdab944e56db51253f32d178d104a4f3996ab8a441e15a
languageName: node
linkType: hard
-"@account-kit/smart-contracts@npm:^4.12.0, @account-kit/smart-contracts@npm:^4.24.0, @account-kit/smart-contracts@npm:^4.31.0":
+"@account-kit/smart-contracts@npm:^4.31.0":
version: 4.31.0
resolution: "@account-kit/smart-contracts@npm:4.31.0"
dependencies:
@@ -177,6 +225,36 @@ __metadata:
languageName: node
linkType: hard
+"@account-kit/smart-contracts@npm:^4.43.1, @account-kit/smart-contracts@npm:^4.47.0":
+ version: 4.47.0
+ resolution: "@account-kit/smart-contracts@npm:4.47.0"
+ dependencies:
+ "@aa-sdk/core": ^4.47.0
+ "@account-kit/infra": ^4.47.0
+ webauthn-p256: ^0.0.10
+ peerDependencies:
+ viem: 2.29.2
+ checksum: 663acc10bb36f309b7eefe0f6f6071db24e31142f70b79bb057d28bb40b04c708894311d33c640ecae94eb5692c056d4ffd7190df93bfa81d3386e76e8734677
+ languageName: node
+ linkType: hard
+
+"@account-kit/wallet-client@npm:0.1.0-alpha.9":
+ version: 0.1.0-alpha.9
+ resolution: "@account-kit/wallet-client@npm:0.1.0-alpha.9"
+ dependencies:
+ "@aa-sdk/core": ^4.43.1
+ "@account-kit/infra": ^4.43.1
+ "@account-kit/smart-contracts": ^4.43.1
+ "@sinclair/typebox": ^0.34.33
+ deep-equal: ^2.2.3
+ ox: ^0.6.12
+ viem: 2.29.2
+ peerDependencies:
+ typescript: ^5.8.2
+ checksum: 4b633f35ad0915240cd6447515969b17d88357ad014b9fd28de00a20ab4dc0ae17a5c6b5c833fa676fbc2bf253e8312ef4d06737d000686853e2453ca946caba
+ languageName: node
+ linkType: hard
+
"@adraffy/ens-normalize@npm:1.10.0":
version: 1.10.0
resolution: "@adraffy/ens-normalize@npm:1.10.0"
@@ -3278,6 +3356,15 @@ __metadata:
languageName: node
linkType: hard
+"@noble/curves@npm:^1.4.0, @noble/curves@npm:^1.9.2":
+ version: 1.9.2
+ resolution: "@noble/curves@npm:1.9.2"
+ dependencies:
+ "@noble/hashes": 1.8.0
+ checksum: bac582aefe951032cb04ed7627f139c3351ddfefd2625a25fe7f7a8043e7d781be4fad320d4ae75e31fa5d7e05ba643f16139877375130fd3cff86d81512e0f2
+ languageName: node
+ linkType: hard
+
"@noble/hashes@npm:1.2.0, @noble/hashes@npm:~1.2.0":
version: 1.2.0
resolution: "@noble/hashes@npm:1.2.0"
@@ -3341,6 +3428,13 @@ __metadata:
languageName: node
linkType: hard
+"@noble/secp256k1@npm:^2.3.0":
+ version: 2.3.0
+ resolution: "@noble/secp256k1@npm:2.3.0"
+ checksum: 423c7242203fe843f630b48eb3188ca73fffdc81dc29ce8b2579db850c381fb32cda82a8e721623007762241547527c0d5d76d56aa6570bfe077e71dcfbbd583
+ languageName: node
+ linkType: hard
+
"@noble/secp256k1@npm:~1.7.0":
version: 1.7.2
resolution: "@noble/secp256k1@npm:1.7.2"
@@ -4446,9 +4540,9 @@ __metadata:
version: 0.0.0-use.local
resolution: "@se-2/hardhat@workspace:packages/hardhat"
dependencies:
- "@aa-sdk/core": ^4.12.0
- "@account-kit/infra": ^4.12.0
- "@account-kit/smart-contracts": ^4.12.0
+ "@aa-sdk/core": ^4.31.0
+ "@account-kit/infra": ^4.31.0
+ "@account-kit/smart-contracts": ^4.31.0
"@ethersproject/abi": ^5.7.0
"@ethersproject/providers": ^5.7.2
"@inquirer/password": ^4.0.2
@@ -4493,8 +4587,8 @@ __metadata:
version: 0.0.0-use.local
resolution: "@se-2/nextjs@workspace:packages/nextjs"
dependencies:
- "@account-kit/infra": ^4.24.0
- "@account-kit/react": ^4.24.0
+ "@account-kit/infra": ^4.31.0
+ "@account-kit/react": ^4.31.0
"@heroicons/react": ^2.1.5
"@scaffold-alchemy/shared": "workspace:*"
"@tanstack/react-query": ^5.64.1
@@ -4532,6 +4626,18 @@ __metadata:
languageName: unknown
linkType: soft
+"@segment/analytics-core@npm:1.8.0":
+ version: 1.8.0
+ resolution: "@segment/analytics-core@npm:1.8.0"
+ dependencies:
+ "@lukeed/uuid": ^2.0.0
+ "@segment/analytics-generic-utils": 1.2.0
+ dset: ^3.1.4
+ tslib: ^2.4.1
+ checksum: c8e2a98670658d6400d8e517e141aff6eff581a12a4015ce8ff906185d148cf9dad79cc5bfb5376195794cc0c964a9c9ca79d36f3d4b49affeb7344251dcde1f
+ languageName: node
+ linkType: hard
+
"@segment/analytics-core@npm:1.8.1":
version: 1.8.1
resolution: "@segment/analytics-core@npm:1.8.1"
@@ -4553,6 +4659,24 @@ __metadata:
languageName: node
linkType: hard
+"@segment/analytics-next@npm:1.74.0":
+ version: 1.74.0
+ resolution: "@segment/analytics-next@npm:1.74.0"
+ dependencies:
+ "@lukeed/uuid": ^2.0.0
+ "@segment/analytics-core": 1.8.0
+ "@segment/analytics-generic-utils": 1.2.0
+ "@segment/analytics.js-video-plugins": ^0.2.1
+ "@segment/facade": ^3.4.9
+ dset: ^3.1.4
+ js-cookie: 3.0.1
+ node-fetch: ^2.6.7
+ tslib: ^2.4.1
+ unfetch: ^4.1.0
+ checksum: 8677611dbcbb27004700e1bb4aee73b39d3be87b6719f20a28e36d1d76e6dd10c41ffd96fbda4e06da55a95c4453275710fc4dda6705ed97b540cfd3b9180c84
+ languageName: node
+ linkType: hard
+
"@segment/analytics-next@npm:^1.74.0":
version: 1.81.0
resolution: "@segment/analytics-next@npm:1.81.0"
@@ -4737,6 +4861,13 @@ __metadata:
languageName: node
linkType: hard
+"@sinclair/typebox@npm:^0.34.33":
+ version: 0.34.37
+ resolution: "@sinclair/typebox@npm:0.34.37"
+ checksum: dbfef02bb40b286a40a140a94a3ce5b1f176df7ca1109849748a02533d1163bf0b2663cc098578af8f4e183af0c98add180c3e5e0d17c7ebc2a5b2265b4a8ad8
+ languageName: node
+ linkType: hard
+
"@sinonjs/commons@npm:^3.0.0":
version: 3.0.1
resolution: "@sinonjs/commons@npm:3.0.1"
@@ -5195,14 +5326,14 @@ __metadata:
languageName: node
linkType: hard
-"@turnkey/api-key-stamper@npm:0.4.4":
- version: 0.4.4
- resolution: "@turnkey/api-key-stamper@npm:0.4.4"
+"@turnkey/api-key-stamper@npm:0.4.7":
+ version: 0.4.7
+ resolution: "@turnkey/api-key-stamper@npm:0.4.7"
dependencies:
"@noble/curves": ^1.3.0
- "@turnkey/encoding": 0.4.0
+ "@turnkey/encoding": 0.5.0
sha256-uint8array: ^0.10.7
- checksum: aef9dd0dfa6f09a4de49e461d7552a2c4547ea7c0ac32e0d6c965ac25f1377c8232d9449a0ae5eac51926365745588009a17073fa0ae6ada803e51f0e25a096f
+ checksum: e6f09113f9df6fcc1ebbd3620714f3a7f0e51c4bf55052d1d86be0a707f2312d6704d75b8e1e08671f6227075916f9d0364c01027ddc0b9bdce8b35c47f013ae
languageName: node
linkType: hard
@@ -5230,10 +5361,10 @@ __metadata:
languageName: node
linkType: hard
-"@turnkey/encoding@npm:0.4.0":
- version: 0.4.0
- resolution: "@turnkey/encoding@npm:0.4.0"
- checksum: c0b8e0f48d790e4715a1eb1b2e213b7f71a5d478cd93994d7310f3c95dd332d9de1e6b7f7330c7d8b4d78029f10feb110b050836a016bf383105cc212d8a2dbd
+"@turnkey/encoding@npm:0.5.0":
+ version: 0.5.0
+ resolution: "@turnkey/encoding@npm:0.5.0"
+ checksum: 4a66f1f18ae0349a8cae3ebf2e5d3f8fed9f68186ad1873cc815e8654f6ce70c8afe48b2aeba5a226a7e1db3a3bb135295c5f518f3627f681652aa763f8afbcd
languageName: node
linkType: hard
@@ -5249,15 +5380,15 @@ __metadata:
languageName: node
linkType: hard
-"@turnkey/http@npm:^2.6.2":
- version: 2.22.0
- resolution: "@turnkey/http@npm:2.22.0"
+"@turnkey/http@npm:3.5.0, @turnkey/http@npm:^3.4.2":
+ version: 3.5.0
+ resolution: "@turnkey/http@npm:3.5.0"
dependencies:
- "@turnkey/api-key-stamper": 0.4.4
- "@turnkey/encoding": 0.4.0
- "@turnkey/webauthn-stamper": 0.5.0
+ "@turnkey/api-key-stamper": 0.4.7
+ "@turnkey/encoding": 0.5.0
+ "@turnkey/webauthn-stamper": 0.5.1
cross-fetch: ^3.1.5
- checksum: 35eab37cdd334c81182cff775976b61f2177a1f2930b29fc0ea02427c60e826035b35c38024d7569f03588cd2b1451f9725e31b54aa796ec8bbebd25f69cb3b8
+ checksum: 2b980d53e8c7aa85017a46e06b17a557abf504c0fda5cb5d68370834e5fdd4aa30018a9e2cfb7da06e678f2ab915784c60a151c60ebf40f1259ac54f7fd551ad
languageName: node
linkType: hard
@@ -5268,10 +5399,23 @@ __metadata:
languageName: node
linkType: hard
-"@turnkey/iframe-stamper@npm:^1.0.0":
- version: 1.2.0
- resolution: "@turnkey/iframe-stamper@npm:1.2.0"
- checksum: 347c65fbd4b6945b18db8fe64c1543f7054281f117231c5423b82013f9eb7df43415210354ba9da2c69bacd8d525a27a1c21d744cf32010908506722cdbfabb0
+"@turnkey/iframe-stamper@npm:^2.5.0":
+ version: 2.5.0
+ resolution: "@turnkey/iframe-stamper@npm:2.5.0"
+ checksum: e69477c13062570a35a02f4c32889a803a626887ea944d7e439e3f4cafd1659d57fd340e3af0aed2cf96bc9ac37c29e9729fc13754cd913fd653a82d88eb2ef5
+ languageName: node
+ linkType: hard
+
+"@turnkey/react-native-passkey-stamper@npm:^1.0.14":
+ version: 1.0.17
+ resolution: "@turnkey/react-native-passkey-stamper@npm:1.0.17"
+ dependencies:
+ "@turnkey/encoding": 0.5.0
+ "@turnkey/http": 3.5.0
+ buffer: ^6.0.3
+ react-native-passkey: ^3.0.0
+ sha256-uint8array: ^0.10.7
+ checksum: c8c099fe832cae2b34c4823ab658fca8fba6b0768fe02c823961e80e65dccba16d07b248d17f8a1fab9db7c1d65ad5f6804c2f1aa7606c300d502a7134bee887
languageName: node
linkType: hard
@@ -5332,6 +5476,15 @@ __metadata:
languageName: node
linkType: hard
+"@turnkey/webauthn-stamper@npm:0.5.1":
+ version: 0.5.1
+ resolution: "@turnkey/webauthn-stamper@npm:0.5.1"
+ dependencies:
+ sha256-uint8array: ^0.10.7
+ checksum: 9e3dfe43ea49ff0ba1902cd70e540c2bd666c90c79d8dd0399f0a31640196f8d55fcd4a08e2530e251aab5bfd262529e34aac683f28d88eca46589c3bb06019d
+ languageName: node
+ linkType: hard
+
"@turnkey/webauthn-stamper@npm:^0.4.3":
version: 0.4.3
resolution: "@turnkey/webauthn-stamper@npm:0.4.3"
@@ -7404,7 +7557,7 @@ __metadata:
languageName: node
linkType: hard
-"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2":
+"array-buffer-byte-length@npm:^1.0.0, array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2":
version: 1.0.2
resolution: "array-buffer-byte-length@npm:1.0.2"
dependencies:
@@ -8175,7 +8328,7 @@ __metadata:
languageName: node
linkType: hard
-"call-bind@npm:^1.0.7, call-bind@npm:^1.0.8":
+"call-bind@npm:^1.0.2, call-bind@npm:^1.0.5, call-bind@npm:^1.0.7, call-bind@npm:^1.0.8":
version: 1.0.8
resolution: "call-bind@npm:1.0.8"
dependencies:
@@ -9198,6 +9351,32 @@ __metadata:
languageName: node
linkType: hard
+"deep-equal@npm:^2.2.3":
+ version: 2.2.3
+ resolution: "deep-equal@npm:2.2.3"
+ dependencies:
+ array-buffer-byte-length: ^1.0.0
+ call-bind: ^1.0.5
+ es-get-iterator: ^1.1.3
+ get-intrinsic: ^1.2.2
+ is-arguments: ^1.1.1
+ is-array-buffer: ^3.0.2
+ is-date-object: ^1.0.5
+ is-regex: ^1.1.4
+ is-shared-array-buffer: ^1.0.2
+ isarray: ^2.0.5
+ object-is: ^1.1.5
+ object-keys: ^1.1.1
+ object.assign: ^4.1.4
+ regexp.prototype.flags: ^1.5.1
+ side-channel: ^1.0.4
+ which-boxed-primitive: ^1.0.2
+ which-collection: ^1.0.1
+ which-typed-array: ^1.1.13
+ checksum: ee8852f23e4d20a5626c13b02f415ba443a1b30b4b3d39eaf366d59c4a85e6545d7ec917db44d476a85ae5a86064f7e5f7af7479f38f113995ba869f3a1ddc53
+ languageName: node
+ linkType: hard
+
"deep-extend@npm:~0.6.0":
version: 0.6.0
resolution: "deep-extend@npm:0.6.0"
@@ -9772,6 +9951,23 @@ __metadata:
languageName: node
linkType: hard
+"es-get-iterator@npm:^1.1.3":
+ version: 1.1.3
+ resolution: "es-get-iterator@npm:1.1.3"
+ dependencies:
+ call-bind: ^1.0.2
+ get-intrinsic: ^1.1.3
+ has-symbols: ^1.0.3
+ is-arguments: ^1.1.1
+ is-map: ^2.0.2
+ is-set: ^2.0.2
+ is-string: ^1.0.7
+ isarray: ^2.0.5
+ stop-iteration-iterator: ^1.0.0
+ checksum: 8fa118da42667a01a7c7529f8a8cca514feeff243feec1ce0bb73baaa3514560bd09d2b3438873cf8a5aaec5d52da248131de153b28e2638a061b6e4df13267d
+ languageName: node
+ linkType: hard
+
"es-iterator-helpers@npm:^1.2.1":
version: 1.2.1
resolution: "es-iterator-helpers@npm:1.2.1"
@@ -11513,7 +11709,7 @@ __metadata:
languageName: node
linkType: hard
-"get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0":
+"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.2.7, get-intrinsic@npm:^1.3.0":
version: 1.3.0
resolution: "get-intrinsic@npm:1.3.0"
dependencies:
@@ -12435,7 +12631,7 @@ __metadata:
languageName: node
linkType: hard
-"is-arguments@npm:^1.0.4":
+"is-arguments@npm:^1.0.4, is-arguments@npm:^1.1.1":
version: 1.2.0
resolution: "is-arguments@npm:1.2.0"
dependencies:
@@ -12445,7 +12641,7 @@ __metadata:
languageName: node
linkType: hard
-"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5":
+"is-array-buffer@npm:^3.0.2, is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5":
version: 3.0.5
resolution: "is-array-buffer@npm:3.0.5"
dependencies:
@@ -12654,7 +12850,7 @@ __metadata:
languageName: node
linkType: hard
-"is-map@npm:^2.0.3":
+"is-map@npm:^2.0.2, is-map@npm:^2.0.3":
version: 2.0.3
resolution: "is-map@npm:2.0.3"
checksum: e6ce5f6380f32b141b3153e6ba9074892bbbbd655e92e7ba5ff195239777e767a976dcd4e22f864accaf30e53ebf961ab1995424aef91af68788f0591b7396cc
@@ -12701,7 +12897,7 @@ __metadata:
languageName: node
linkType: hard
-"is-regex@npm:^1.2.1":
+"is-regex@npm:^1.1.4, is-regex@npm:^1.2.1":
version: 1.2.1
resolution: "is-regex@npm:1.2.1"
dependencies:
@@ -12713,14 +12909,14 @@ __metadata:
languageName: node
linkType: hard
-"is-set@npm:^2.0.3":
+"is-set@npm:^2.0.2, is-set@npm:^2.0.3":
version: 2.0.3
resolution: "is-set@npm:2.0.3"
checksum: 36e3f8c44bdbe9496c9689762cc4110f6a6a12b767c5d74c0398176aa2678d4467e3bf07595556f2dba897751bde1422480212b97d973c7b08a343100b0c0dfe
languageName: node
linkType: hard
-"is-shared-array-buffer@npm:^1.0.4":
+"is-shared-array-buffer@npm:^1.0.2, is-shared-array-buffer@npm:^1.0.4":
version: 1.0.4
resolution: "is-shared-array-buffer@npm:1.0.4"
dependencies:
@@ -15036,6 +15232,16 @@ __metadata:
languageName: node
linkType: hard
+"object-is@npm:^1.1.5":
+ version: 1.1.6
+ resolution: "object-is@npm:1.1.6"
+ dependencies:
+ call-bind: ^1.0.7
+ define-properties: ^1.2.1
+ checksum: 3ea22759967e6f2380a2cbbd0f737b42dc9ddb2dfefdb159a1b927fea57335e1b058b564bfa94417db8ad58cddab33621a035de6f5e5ad56d89f2dd03e66c6a1
+ languageName: node
+ linkType: hard
+
"object-keys@npm:^1.1.1":
version: 1.1.1
resolution: "object-keys@npm:1.1.1"
@@ -15359,6 +15565,26 @@ __metadata:
languageName: node
linkType: hard
+"ox@npm:^0.6.12":
+ version: 0.6.12
+ resolution: "ox@npm:0.6.12"
+ dependencies:
+ "@adraffy/ens-normalize": ^1.10.1
+ "@noble/curves": ^1.6.0
+ "@noble/hashes": ^1.5.0
+ "@scure/bip32": ^1.5.0
+ "@scure/bip39": ^1.4.0
+ abitype: ^1.0.6
+ eventemitter3: 5.0.1
+ peerDependencies:
+ typescript: ">=5.4.0"
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: 6709e7ef5bc9b74d4a2f84443292cd624891d6f556a53363578d785103610d5623c96fe10c355158ebd50a4d09adaad9b26017e6207574d04be33ca61242c2a4
+ languageName: node
+ linkType: hard
+
"p-finally@npm:^2.0.0":
version: 2.0.1
resolution: "p-finally@npm:2.0.1"
@@ -16289,6 +16515,16 @@ __metadata:
languageName: node
linkType: hard
+"react-native-passkey@npm:^3.0.0":
+ version: 3.1.0
+ resolution: "react-native-passkey@npm:3.1.0"
+ peerDependencies:
+ react: "*"
+ react-native: "*"
+ checksum: 24e39648eae2162bf88c258f421c74a52dea243e9943f5fb89a7a5adc1b201cad61b687d3d0c56c5363fedc8339f6d4e1e160c8076b8a221464514db6b7a2717
+ languageName: node
+ linkType: hard
+
"react-native-quick-base64@npm:2.1.2":
version: 2.1.2
resolution: "react-native-quick-base64@npm:2.1.2"
@@ -16606,7 +16842,7 @@ __metadata:
languageName: node
linkType: hard
-"regexp.prototype.flags@npm:^1.5.3":
+"regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.3":
version: 1.5.4
resolution: "regexp.prototype.flags@npm:1.5.4"
dependencies:
@@ -17022,12 +17258,14 @@ __metadata:
version: 0.0.0-use.local
resolution: "se-2@workspace:."
dependencies:
- "@aa-sdk/core": ^4.24.0
- "@account-kit/infra": ^4.24.0
- "@account-kit/plugingen": 4.24.0
- "@account-kit/smart-contracts": ^4.24.0
+ "@aa-sdk/core": ^4.31.0
+ "@account-kit/infra": ^4.31.0
+ "@account-kit/plugingen": 4.31.0
+ "@account-kit/smart-contracts": ^4.31.0
+ "@account-kit/wallet-client": 0.1.0-alpha.9
husky: ^9.1.6
lint-staged: ^15.2.10
+ typescript: ^5.8.3
viem: 2.28.4
languageName: unknown
linkType: soft
@@ -17322,7 +17560,7 @@ __metadata:
languageName: node
linkType: hard
-"side-channel@npm:^1.1.0":
+"side-channel@npm:^1.0.4, side-channel@npm:^1.1.0":
version: 1.1.0
resolution: "side-channel@npm:1.1.0"
dependencies:
@@ -17663,6 +17901,16 @@ __metadata:
languageName: node
linkType: hard
+"stop-iteration-iterator@npm:^1.0.0":
+ version: 1.1.0
+ resolution: "stop-iteration-iterator@npm:1.1.0"
+ dependencies:
+ es-errors: ^1.3.0
+ internal-slot: ^1.1.0
+ checksum: be944489d8829fb3bdec1a1cc4a2142c6b6eb317305eeace1ece978d286d6997778afa1ae8cb3bd70e2b274b9aa8c69f93febb1e15b94b1359b11058f9d3c3a1
+ languageName: node
+ linkType: hard
+
"stream-chain@npm:^2.2.5":
version: 2.2.5
resolution: "stream-chain@npm:2.2.5"
@@ -18702,7 +18950,7 @@ __metadata:
languageName: node
linkType: hard
-"typescript@npm:^5.0.0, typescript@npm:^5.1":
+"typescript@npm:^5.0.0, typescript@npm:^5.1, typescript@npm:^5.8.3":
version: 5.8.3
resolution: "typescript@npm:5.8.3"
bin:
@@ -18742,7 +18990,7 @@ __metadata:
languageName: node
linkType: hard
-"typescript@patch:typescript@^5.0.0#~builtin, typescript@patch:typescript@^5.1#~builtin":
+"typescript@patch:typescript@^5.0.0#~builtin, typescript@patch:typescript@^5.1#~builtin, typescript@patch:typescript@^5.8.3#~builtin":
version: 5.8.3
resolution: "typescript@patch:typescript@npm%3A5.8.3#~builtin::version=5.8.3&hash=a1c5e5"
bin:
@@ -19241,7 +19489,7 @@ __metadata:
languageName: node
linkType: hard
-"uuid@npm:^11.0.2":
+"uuid@npm:^11.0.2, uuid@npm:^11.1.0":
version: 11.1.0
resolution: "uuid@npm:11.1.0"
bin:
@@ -19384,7 +19632,7 @@ __metadata:
languageName: node
linkType: hard
-"viem@npm:2.28.4, viem@npm:>=2.23.11, viem@npm:^2.1.1, viem@npm:^2.21.40, viem@npm:^2.27.0":
+"viem@npm:2.28.4, viem@npm:>=2.23.11, viem@npm:^2.1.1, viem@npm:^2.27.0":
version: 2.28.4
resolution: "viem@npm:2.28.4"
dependencies:
@@ -19405,6 +19653,27 @@ __metadata:
languageName: node
linkType: hard
+"viem@npm:2.29.2":
+ version: 2.29.2
+ resolution: "viem@npm:2.29.2"
+ dependencies:
+ "@noble/curves": 1.8.2
+ "@noble/hashes": 1.7.2
+ "@scure/bip32": 1.6.2
+ "@scure/bip39": 1.5.4
+ abitype: 1.0.8
+ isows: 1.0.6
+ ox: 0.6.9
+ ws: 8.18.1
+ peerDependencies:
+ typescript: ">=5.0.4"
+ peerDependenciesMeta:
+ typescript:
+ optional: true
+ checksum: c9bb267dd369008b6e49bf5642258984a99495ac51499d3058954c0b109094c46f6b572aaaa2363208ba613849fac48495f47f5cea8b029bf1082276e27c0ff6
+ languageName: node
+ linkType: hard
+
"viem@npm:^1.1.4":
version: 1.21.4
resolution: "viem@npm:1.21.4"
@@ -19513,6 +19782,16 @@ __metadata:
languageName: node
linkType: hard
+"webauthn-p256@npm:^0.0.10":
+ version: 0.0.10
+ resolution: "webauthn-p256@npm:0.0.10"
+ dependencies:
+ "@noble/curves": ^1.4.0
+ "@noble/hashes": ^1.4.0
+ checksum: 0648a3d78451bfa7105b5151a34bd685ee60e193be9be1981fe73819ed5a92f410973bdeb72427ef03c8c2a848619f818cf3e66b94012d5127b462cb10c24f5d
+ languageName: node
+ linkType: hard
+
"webextension-polyfill@npm:>=0.10.0 <1.0":
version: 0.12.0
resolution: "webextension-polyfill@npm:0.12.0"
@@ -19565,7 +19844,7 @@ __metadata:
languageName: node
linkType: hard
-"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1":
+"which-boxed-primitive@npm:^1.0.2, which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1":
version: 1.1.1
resolution: "which-boxed-primitive@npm:1.1.1"
dependencies:
@@ -19599,7 +19878,7 @@ __metadata:
languageName: node
linkType: hard
-"which-collection@npm:^1.0.2":
+"which-collection@npm:^1.0.1, which-collection@npm:^1.0.2":
version: 1.0.2
resolution: "which-collection@npm:1.0.2"
dependencies:
@@ -19618,7 +19897,7 @@ __metadata:
languageName: node
linkType: hard
-"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2":
+"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2":
version: 1.1.19
resolution: "which-typed-array@npm:1.1.19"
dependencies:
@@ -20192,4 +20471,4 @@ __metadata:
optional: true
checksum: 75f45c00a799c28afdb56f35c9fdcb1c5861113604daaffa1ffe05beff4e506f1a8b332f585076610ef737e14960be64af653eaa0d3022fb2f1ecec6e2bee1dd
languageName: node
- linkType: hard
\ No newline at end of file
+ linkType: hard
From c3a3c5794eb0baf5e1e7df6375f2ad412375eaa5 Mon Sep 17 00:00:00 2001
From: Ephyrian <150686927+UMainLove@users.noreply.github.com>
Date: Thu, 3 Jul 2025 13:01:43 +0200
Subject: [PATCH 07/11] added custom errors and messages when an EOA is
upgraded to a Smart Wallet
---
.../utils/scaffold-alchemy/eip7702.config.ts | 42 +++++++++++++++++++
1 file changed, 42 insertions(+)
create mode 100644 packages/nextjs/utils/scaffold-alchemy/eip7702.config.ts
diff --git a/packages/nextjs/utils/scaffold-alchemy/eip7702.config.ts b/packages/nextjs/utils/scaffold-alchemy/eip7702.config.ts
new file mode 100644
index 0000000..ddd264f
--- /dev/null
+++ b/packages/nextjs/utils/scaffold-alchemy/eip7702.config.ts
@@ -0,0 +1,42 @@
+/* eslint-disable prettier/prettier */
+
+/**
+ * Configuration for EIP-7702 Smart Account Upgrades
+ */
+
+export const EIP7702_CONFIG = {
+ // Chains that support EIP-7702
+ supportedChains: [1, 11155111] as const, // Mainnet and Sepolia
+
+ // Error messages
+ errors: {
+ UNSUPPORTED_NETWORK: "EIP-7702 is not supported on the connected network.",
+ NOT_METAMASK: "Please connect with MetaMask to upgrade to a Smart Account.",
+ USER_REJECTED: "Upgrade canceled. Smart Account delegation was not authorized.",
+ NO_ADDRESS: "No wallet address found. Please connect your wallet.",
+ NO_WALLET_CLIENT: "Wallet client not available. Please refresh and try again.",
+ UNKNOWN_ERROR: "Failed to upgrade account. Please try again.",
+ SIGNATURE_VALIDATION_FAILED: "Failed to validate signature. Please ensure you have enough ETH for gas.",
+ EIP7702_NOT_READY: "EIP-7702 support is still experimental. Please try again later.",
+ SDK_NOT_READY: "EIP-7702 upgrade requires MetaMask with experimental features enabled. Please ensure you have the latest MetaMask version.",
+ },
+
+ // UI messages
+ messages: {
+ PREPARING: "Preparing smart account upgrade...",
+ UPGRADING: "Upgrading account... Please approve in MetaMask",
+ SUCCESS: "Account successfully upgraded to Smart Account!",
+ UPGRADE_BUTTON: "Upgrade to a Smart Wallet",
+ UPGRADED_EOA: "This is a Smart Wallet",
+ SMART_ACCOUNT: "This is a Smart Account",
+ DETECTING: "Detecting account type...",
+ TOOLTIP_UNSUPPORTED: "EIP-7702 is not supported on this network",
+ },
+
+ // Timing configurations
+ timing: {
+ reloadDelay: 2000, // ms to wait before reloading page after successful upgrade
+ },
+} as const;
+
+export type EIP7702Config = typeof EIP7702_CONFIG;
\ No newline at end of file
From fa750dcf1fafbab00395ae1cc9f39e7147fae7d0 Mon Sep 17 00:00:00 2001
From: Ephyrian <150686927+UMainLove@users.noreply.github.com>
Date: Thu, 3 Jul 2025 14:33:11 +0200
Subject: [PATCH 08/11] fix: public client retrieves the connected address with
correct method
---
packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts b/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
index 8ad250a..1af0158 100644
--- a/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
+++ b/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
@@ -34,7 +34,7 @@ export const useAccountType = () => {
let hasCode = false;
if (address && publicClient) {
try {
- const code = await publicClient.getBytecode({
+ const code = await publicClient.getCode({
address: address as `0x${string}`,
blockTag: 'latest'
});
From 458c37fa7b92fb7e2f133f281086007b220b1c2f Mon Sep 17 00:00:00 2001
From: Ephyrian <150686927+UMainLove@users.noreply.github.com>
Date: Tue, 8 Jul 2025 13:25:46 +0200
Subject: [PATCH 09/11] feature: added EOA interactions with in-app smart
contracts
---
.gitignore | 5 +-
.../nextjs/hooks/scaffold-alchemy/index.ts | 2 +
.../hooks/scaffold-alchemy/useAccountType.ts | 49 +++++-
.../hooks/scaffold-alchemy/useClient.ts | 37 +++++
.../scaffold-alchemy/useSmartWalletClient.ts | 141 ++++++++++++++++++
5 files changed, 231 insertions(+), 3 deletions(-)
create mode 100644 packages/nextjs/hooks/scaffold-alchemy/useSmartWalletClient.ts
diff --git a/.gitignore b/.gitignore
index 0d4e11d..3aa2d3e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,4 +22,7 @@ node_modules
# cli
dist
-.env
\ No newline at end of file
+.env
+
+# Claude
+CLAUDE.md
\ No newline at end of file
diff --git a/packages/nextjs/hooks/scaffold-alchemy/index.ts b/packages/nextjs/hooks/scaffold-alchemy/index.ts
index c4a65d1..f34f307 100644
--- a/packages/nextjs/hooks/scaffold-alchemy/index.ts
+++ b/packages/nextjs/hooks/scaffold-alchemy/index.ts
@@ -16,3 +16,5 @@ export * from "./useWatchBalance";
export * from "./useSelectedNetwork";
export * from "./useAccountType"; //feature_1
export * from "./useEOAUpgrade"; //feature_1_part_2
+export * from "./useSmartWalletClient"; //feature_1_part_3
+export * from "./useClient"; //feature_1_part_3
diff --git a/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts b/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
index 1af0158..fe920d0 100644
--- a/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
+++ b/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
@@ -12,7 +12,7 @@ export const useAccountType = () => {
const [accountType, setAccountType] = useState("UNKNOWN");
const [isLoading, setIsLoading] = useState(true);
const { isConnected, connector } = useAccount();
- const { client, address } = useClient();
+ const { client, address, isSmartWallet } = useClient(); // isSmartWallet is part of feature_1_part_3
const publicClient = usePublicClient(); // usePublicClient is part of feature_1_part_2
const { targetNetwork } = useTargetNetwork(); //useTargetNetwork is part of feature_1_part_2
@@ -26,10 +26,11 @@ export const useAccountType = () => {
isConnected,
hasClient: !!client,
hasAddress: !!address,
+ isSmartWallet, // From useClient (feature_1_part_3)
connectorType: connector?.type,
connectorName: connector?.name,
});
-
+/*
// Check if there's bytecode at the address (indicates smart account) - feature_1_part_2
let hasCode = false;
if (address && publicClient) {
@@ -89,6 +90,50 @@ export const useAccountType = () => {
detectAccountType();
}, [isConnected, connector, client, address, publicClient, targetNetwork]);
+*/
+ // If useClient already detected a Smart Wallet, use that
+ if (isSmartWallet) {
+ setAccountType("EOA_7702");
+ return;
+ }
+
+ // If we have a client and address, check if it's from external wallet or not
+ if (client && address) {
+ // Check if connected via external wallet
+ const isExternalWallet =
+ connector?.type === "injected" ||
+ connector?.type === "walletConnect" ||
+ connector?.name?.toLowerCase().includes("wallet") ||
+ connector?.name?.toLowerCase().includes("metamask");
+
+ if (!isExternalWallet) {
+ // Has SCA and no external wallet = email/social login
+ setAccountType("SCA_4337");
+ } else {
+ // This shouldn't happen if isSmartWallet detection works
+ // But keeping as fallback
+ setAccountType("EOA");
+ }
+ } else if (isConnected && !client) {
+ // Connected with external wallet but no client = regular EOA
+ setAccountType("EOA");
+ } else if (!isConnected && address) {
+ // Has address but not connected via wagmi = likely email/social
+ setAccountType("SCA_4337");
+ } else {
+ setAccountType("UNKNOWN");
+ }
+ } catch (error) {
+ console.error("Error detecting account type:", error);
+ setAccountType("UNKNOWN");
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ detectAccountType();
+ }, [isConnected, connector, client, address, isSmartWallet, publicClient, targetNetwork]);
+
return { accountType, isLoading };
diff --git a/packages/nextjs/hooks/scaffold-alchemy/useClient.ts b/packages/nextjs/hooks/scaffold-alchemy/useClient.ts
index 7497d96..088c587 100644
--- a/packages/nextjs/hooks/scaffold-alchemy/useClient.ts
+++ b/packages/nextjs/hooks/scaffold-alchemy/useClient.ts
@@ -1,14 +1,19 @@
+/* eslint-disable prettier/prettier */
+
import { alchemyEnhancedApiActions } from "@account-kit/infra";
import { UseSmartAccountClientProps, useSmartAccountClient } from "@account-kit/react";
import { Alchemy, Network } from "alchemy-sdk";
import scaffoldConfig from "~~/scaffold.config";
import { RPC_CHAIN_NAMES } from "~~/utils/scaffold-alchemy";
+import { useSmartWalletClient } from "./useSmartWalletClient"; // feature_1_part_3
export const useClient = (
config: UseSmartAccountClientProps = {
type: "MultiOwnerModularAccount",
},
) => {
+
+ /*
const { client, address } = useSmartAccountClient(config);
const alchemy = new Alchemy({
url: client?.transport.alchemyRpcUrl,
@@ -17,5 +22,37 @@ export const useClient = (
const enhancedApiDecorator = alchemyEnhancedApiActions(alchemy);
return { client: client?.extend(enhancedApiDecorator as any), origClient: client, address };
};
+*/
+ // Get Smart Account client (for social login users)
+ const { client: smartAccountClient, address: smartAccountAddress } = useSmartAccountClient(config);
+
+ // Get Smart Wallet client (for EIP-7702 upgraded EOAs)
+ const { client: smartWalletClient, address: smartWalletAddress, isSmartWallet } = useSmartWalletClient();
+
+ // Determine which client to use
+ // Priority: Smart Wallet client (if available) > Smart Account client
+ const client = smartWalletClient || smartAccountClient;
+ const address = smartWalletClient ? smartWalletAddress : smartAccountAddress;
+
+ // Set up Alchemy enhanced API
+ let enhancedClient = null;
+ if (client) {
+ const alchemy = new Alchemy({
+ url: client.transport.alchemyRpcUrl,
+ network: RPC_CHAIN_NAMES[scaffoldConfig.targetNetworks[0].id] as Network,
+ });
+ const enhancedApiDecorator = alchemyEnhancedApiActions(alchemy);
+ enhancedClient = client.extend(enhancedApiDecorator as any);
+ }
+
+ return {
+ client: enhancedClient,
+ origClient: client,
+ address,
+ // Export isSmartWallet so useAccountType can use it without circular dependency
+ isSmartWallet,
+ };
+};
+
export type Client = ReturnType["client"];
diff --git a/packages/nextjs/hooks/scaffold-alchemy/useSmartWalletClient.ts b/packages/nextjs/hooks/scaffold-alchemy/useSmartWalletClient.ts
new file mode 100644
index 0000000..d09ad4e
--- /dev/null
+++ b/packages/nextjs/hooks/scaffold-alchemy/useSmartWalletClient.ts
@@ -0,0 +1,141 @@
+/* eslint-disable prettier/prettier */
+
+// feature_1_part_3: Smart Wallet Client for EIP-7702 upgraded EOAs
+
+import { useState, useEffect } from "react";
+import { createAlchemySmartAccountClient } from "@account-kit/infra";
+import { createModularAccountV2 } from "@account-kit/smart-contracts";
+import { WalletClientSigner } from "@aa-sdk/core";
+import { alchemy, sepolia, mainnet } from "@account-kit/infra";
+import { useAccount, useWalletClient, usePublicClient } from "wagmi";
+import { ALCHEMY_CONFIG } from "@scaffold-alchemy/shared";
+import { useTargetNetwork } from "./useTargetNetwork";
+import { createWalletClient, custom, type Hex } from "viem";
+import { EIP7702_CONFIG } from "~~/utils/scaffold-alchemy/eip7702.config";
+
+export const useSmartWalletClient = () => {
+ const [client, setClient] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const [hasCode, setHasCode] = useState(false);
+
+ const { address, connector } = useAccount();
+ const { data: walletClient } = useWalletClient();
+ const publicClient = usePublicClient();
+ const { targetNetwork } = useTargetNetwork();
+
+ const isMetaMaskConnected = connector?.name?.toLowerCase().includes("metamask") || connector?.id === "injected";
+ const isEIP7702Supported = EIP7702_CONFIG.supportedChains.includes(targetNetwork.id as (1 | 11155111));
+
+ // Check for bytecode at address (indicates upgraded Smart Wallet)
+ useEffect(() => {
+ const checkBytecode = async () => {
+ if (!address || !publicClient) {
+ setHasCode(false);
+ return;
+ }
+
+ try {
+ const code = await publicClient.getCode({
+ address: address as `0x${string}`,
+ blockTag: 'latest'
+ });
+ const codeExists = code !== undefined && code !== "0x";
+ setHasCode(codeExists);
+ console.log("[Smart Wallet Client] Bytecode check:", { address, hasCode: codeExists });
+ } catch (error) {
+ console.error("[Smart Wallet Client] Error checking bytecode:", error);
+ setHasCode(false);
+ }
+ };
+
+ checkBytecode();
+ }, [address, publicClient]);
+
+ // Determine if this is a Smart Wallet (EOA with bytecode)
+ const isSmartWallet = hasCode && isMetaMaskConnected;
+
+ useEffect(() => {
+ const initializeSmartWalletClient = async () => {
+ // Reset state
+ setIsLoading(true);
+ setError(null);
+
+ // Check if we should create a Smart Wallet client
+ if (!isSmartWallet || !walletClient || !address || !isEIP7702Supported) {
+ console.log("[Smart Wallet Client] Conditions not met:", {
+ isSmartWallet,
+ hasWalletClient: !!walletClient,
+ hasAddress: !!address,
+ isEIP7702Supported,
+ hasCode,
+ });
+ setClient(null);
+ setIsLoading(false);
+ return;
+ }
+
+ try {
+ console.log("[Smart Wallet Client] Initializing client for upgraded Smart Wallet...");
+
+ // Create a custom wallet client for MetaMask
+ const customWalletClient = createWalletClient({
+ account: address as Hex,
+ chain: targetNetwork.id === 1 ? mainnet : sepolia,
+ transport: custom(window.ethereum!),
+ });
+
+ // Create the signer
+ const signer = new WalletClientSigner(customWalletClient, "metamask");
+
+ // Use the correct chain from @account-kit/infra
+ const alchemyChain = targetNetwork.id === 1 ? mainnet : sepolia;
+
+ // Get the API key and policy ID from the shared config
+ const apiKey = ALCHEMY_CONFIG.ALCHEMY_API_KEY;
+ const policyId = ALCHEMY_CONFIG.ALCHEMY_GAS_POLICY_ID;
+
+ // Create the transport
+ const transport = alchemy({
+ apiKey,
+ });
+
+ // Create the Modular Account V2 in 7702 mode
+ const account = await createModularAccountV2({
+ signer,
+ chain: alchemyChain,
+ transport,
+ mode: "7702", // Key parameter for EIP-7702
+ accountAddress: address as Hex, // Use existing address since it's already upgraded
+ });
+
+ // Create the Alchemy Smart Account Client
+ const smartWalletClient = createAlchemySmartAccountClient({
+ account,
+ chain: alchemyChain,
+ transport,
+ policyId,
+ });
+
+ setClient(smartWalletClient);
+ console.log("[Smart Wallet Client] Client initialized successfully for address:", address);
+ } catch (err: any) {
+ console.error("[Smart Wallet Client] Failed to initialize client:", err);
+ setError(err.message || "Failed to initialize Smart Wallet client");
+ setClient(null);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ initializeSmartWalletClient();
+ }, [walletClient, address, targetNetwork.id, isSmartWallet, isEIP7702Supported, hasCode]);
+
+ return {
+ client,
+ isLoading,
+ error,
+ isSmartWallet,
+ address,
+ };
+};
\ No newline at end of file
From 551dd5526e647d18c40fe0b06114ae5926bba93f Mon Sep 17 00:00:00 2001
From: Ephyrian <150686927+UMainLove@users.noreply.github.com>
Date: Tue, 8 Jul 2025 18:42:49 +0200
Subject: [PATCH 10/11] upgrade: replaced EOA direct calls with delegated
userOps (eip7702) when a Smart Wallet interacts with in-app smart contracts
---
.../contract/WriteOnlyFunctionForm.tsx | 105 ++++++++++---
.../hooks/scaffold-alchemy/useAccountType.ts | 61 --------
.../hooks/scaffold-alchemy/useClient.ts | 11 --
.../useScaffoldWriteContract.ts | 140 ++++++++++++++----
4 files changed, 195 insertions(+), 122 deletions(-)
diff --git a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx
index d5b1104..ce501f6 100644
--- a/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx
+++ b/packages/nextjs/app/debug/_components/contract/WriteOnlyFunctionForm.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable prettier/prettier */
+
"use client";
import { useEffect, useState } from "react";
@@ -34,43 +36,103 @@ export const WriteOnlyFunctionForm = ({
contractAddress,
inheritedFrom,
}: WriteOnlyFunctionFormProps) => {
- const { client } = useClient();
+ const { client, isSmartWallet } = useClient(); // isSmartWallet is part of feature_1_part_3
+
+ // Only use useSendUserOperation for Smart Accounts cases
const { sendUserOperationAsync, isSendingUserOperation } = useSendUserOperation({
- client: client,
+ client: !isSmartWallet ? client : undefined, // client is not needed for Smart Wallets, 'client: client' is used for Smart Accounts
waitForTxn: true,
});
const [hash, setHash] = useState();
const [form, setForm] = useState>(() => getInitialFormState(abiFunction));
const [txValue, setTxValue] = useState("");
+ const [isSmartWalletSending, setIsSmartWalletSending] = useState(false); // feature_1_part_3: Track Smart Wallet sending state
const { chain } = useChain();
const writeTxn = useTransactor();
const { targetNetwork } = useTargetNetwork();
const writeDisabled = !chain || chain?.id !== targetNetwork.id;
+ const isSending = isSendingUserOperation || isSmartWalletSending; // feature_1_part_3: Track sending state
const handleWrite = async () => {
- if (sendUserOperationAsync) {
- try {
- const makeWriteWithParams = async () => {
- if (!client) throw Error("You must first login before making an onchain action");
+
+// feature_1_part_3: Handle Smart Wallet sending block start -----
+ if (!client && !sendUserOperationAsync) {
+ console.error("No client available for transaction");
+ return;
+ }
+
+ try {
+ if (isSmartWallet) {
+ setIsSmartWalletSending(true);
+ }
+
+ const makeWriteWithParams = async (): Promise => {
+ const encodedData = encodeFunctionData({
+ functionName: abiFunction.name,
+ abi: abi,
+ args: getParsedContractFunctionArgs(form),
+ });
+ let txHash: Hex;
+
+ // For Smart Wallets (EIP-7702), use the client directly
+ if (isSmartWallet && client) {
+ console.log("[WriteOnlyForm] Using Smart Wallet client for UserOp");
+ console.log("[WriteOnlyForm] Target:", contractAddress);
+ console.log("[WriteOnlyForm] Function:", abiFunction.name);
+ console.log("[WriteOnlyForm] Value:", BigInt(txValue || "0"));
+
+ try {
+ const result = await client.sendUserOperation({
+ uo: {
+ target: contractAddress,
+ data: encodedData,
+ value: BigInt(txValue || "0"),
+ },
+ });
+
+ console.log("[WriteOnlyForm] UserOp sent, result:", result);
+
+ // Wait for the transaction if we have a hash
+ if ('hash' in result && result.hash) {
+ console.log("[WriteOnlyForm] Waiting for UserOp transaction:", result.hash);
+ const confirmedHash = await client.waitForUserOperationTransaction({
+ hash: result.hash,
+ });
+ txHash = confirmedHash as Hex;
+ console.log("[WriteOnlyForm] Transaction confirmed:", txHash);
+ } else {
+ throw new Error("Failed to get transaction hash from Smart Wallet operation");
+ }
+ } catch (error) {
+ console.error("[WriteOnlyForm] Smart Wallet operation failed:", error);
+ throw error;
+ }
+ } else if (sendUserOperationAsync) {
+ // Use the SDK's sendUserOperationAsync for regular smart accounts
const { hash } = await sendUserOperationAsync({
uo: {
target: contractAddress,
- data: encodeFunctionData({
- functionName: abiFunction.name,
- abi: abi,
- args: getParsedContractFunctionArgs(form),
- }),
- value: BigInt(txValue),
+ data: encodedData,
+ value: BigInt(txValue || "0"),
},
});
- setHash(hash);
- return hash;
- };
- await writeTxn(makeWriteWithParams);
- onChange();
- } catch (e: any) {
- console.error("⚡️ ~ file: WriteOnlyFunctionForm.tsx:handleWrite ~ error", e);
+ txHash = hash as Hex;
+ } else {
+ throw Error("No method available to send transaction");
+ }
+
+ setHash(txHash);
+ return txHash;
+ };
+
+ await writeTxn(makeWriteWithParams);
+ onChange();
+ } catch (e: any) {
+ console.error("⚡️ ~ file: WriteOnlyFunctionForm.tsx:handleWrite ~ error", e);
+ } finally {
+ if (isSmartWallet) {
+ setIsSmartWalletSending(false);
}
}
};
@@ -82,6 +144,7 @@ export const WriteOnlyFunctionForm = ({
useEffect(() => {
setDisplayedTxResult(txResult);
}, [txResult]);
+// feature_1_part_3: Handle Smart Wallet sending block end -----
// TODO use `useMemo` to optimize also update in ReadOnlyFunctionForm
const transformedFunction = transformAbiFunction(abiFunction);
@@ -141,10 +204,10 @@ export const WriteOnlyFunctionForm = ({
>
diff --git a/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts b/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
index fe920d0..162421f 100644
--- a/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
+++ b/packages/nextjs/hooks/scaffold-alchemy/useAccountType.ts
@@ -30,67 +30,6 @@ export const useAccountType = () => {
connectorType: connector?.type,
connectorName: connector?.name,
});
-/*
- // Check if there's bytecode at the address (indicates smart account) - feature_1_part_2
- let hasCode = false;
- if (address && publicClient) {
- try {
- const code = await publicClient.getCode({
- address: address as `0x${string}`,
- blockTag: 'latest'
- });
- hasCode = code !== undefined && code !== "0x";
- console.log("[useAccountType] Bytecode check:", { address, hasCode });
- } catch (error) {
- console.error("[useAccountType] Error checking bytecode:", error);
- }
- }
-
- // If we have a client and address, check if it's from external wallet or not
- if (client && address) {
- // Check if connected via external wallet
- const isExternalWallet =
- connector?.type === "injected" ||
- connector?.type === "walletConnect" ||
- connector?.name?.toLowerCase().includes("wallet") ||
- connector?.name?.toLowerCase().includes("metamask");
-
- //added extended logic for feature_1_part_2
- if (isExternalWallet && hasCode) {
- // External wallet with code at address = likely 7702 upgraded
- setAccountType("EOA_7702");
- } else if (isExternalWallet) {
- // External wallet without code = regular EOA
- setAccountType("EOA");
- } else {
- // Has SCA but no external wallet = email/social login
- setAccountType("SCA_4337");
- }
- } else if (isConnected && !client) {
- // Connected with external wallet but no SCA client
- if (hasCode) {
- // Has code but no SCA client = 7702 upgraded EOA
- setAccountType("EOA_7702");
- } else {
- setAccountType("EOA");
- }
- } else if (!isConnected && address) {
- // Has address but not connected via wagmi = likely email/social
- setAccountType("SCA_4337");
- } else {
- setAccountType("UNKNOWN");
- }
- } catch (error) {
- console.error("Error detecting account type:", error);
- setAccountType("UNKNOWN");
- } finally {
- setIsLoading(false);
- }
- };
-
- detectAccountType();
- }, [isConnected, connector, client, address, publicClient, targetNetwork]);
-*/
// If useClient already detected a Smart Wallet, use that
if (isSmartWallet) {
setAccountType("EOA_7702");
diff --git a/packages/nextjs/hooks/scaffold-alchemy/useClient.ts b/packages/nextjs/hooks/scaffold-alchemy/useClient.ts
index 088c587..fb9ec00 100644
--- a/packages/nextjs/hooks/scaffold-alchemy/useClient.ts
+++ b/packages/nextjs/hooks/scaffold-alchemy/useClient.ts
@@ -12,17 +12,6 @@ export const useClient = (
type: "MultiOwnerModularAccount",
},
) => {
-
- /*
- const { client, address } = useSmartAccountClient(config);
- const alchemy = new Alchemy({
- url: client?.transport.alchemyRpcUrl,
- network: RPC_CHAIN_NAMES[scaffoldConfig.targetNetworks[0].id] as Network,
- });
- const enhancedApiDecorator = alchemyEnhancedApiActions(alchemy);
- return { client: client?.extend(enhancedApiDecorator as any), origClient: client, address };
-};
-*/
// Get Smart Account client (for social login users)
const { client: smartAccountClient, address: smartAccountAddress } = useSmartAccountClient(config);
diff --git a/packages/nextjs/hooks/scaffold-alchemy/useScaffoldWriteContract.ts b/packages/nextjs/hooks/scaffold-alchemy/useScaffoldWriteContract.ts
index 7c8f81b..6605c41 100644
--- a/packages/nextjs/hooks/scaffold-alchemy/useScaffoldWriteContract.ts
+++ b/packages/nextjs/hooks/scaffold-alchemy/useScaffoldWriteContract.ts
@@ -1,8 +1,10 @@
+/* eslint-disable prettier/prettier */
+
import { useEffect, useState } from "react";
import { useClient } from "./useClient";
import { useSendUserOperation } from "@account-kit/react";
import { ExtractAbiFunctionNames } from "abitype";
-import { Abi, EncodeFunctionDataParameters, WriteContractReturnType, encodeFunctionData } from "viem";
+import { Abi, EncodeFunctionDataParameters, WriteContractReturnType, encodeFunctionData, Hex } from "viem"; // Hex is part of feature_1_part_3
import { UseWriteContractParameters, useWriteContract } from "wagmi";
import { useSelectedNetwork } from "~~/hooks/scaffold-alchemy";
import { useDeployedContractInfo, useTransactor } from "~~/hooks/scaffold-alchemy";
@@ -70,6 +72,7 @@ export function useScaffoldWriteContract
(
const writeTx = useTransactor();
const [isMining, setIsMining] = useState(false);
+ const [isSmartWalletMining, setIsSmartWalletMining] = useState(false); //feature_1_part_3
const wagmiContractWrite = useWriteContract(finalWriteContractParams);
@@ -80,10 +83,12 @@ export function useScaffoldWriteContract(
chainId: selectedNetwork.id as AllowedChainIds,
});
- const { client } = useClient();
+ const { client, isSmartWallet } = useClient(); // isSmartWallet is part of feature_1_part_3
+ // Only use useSendUserOperation for Smart Accounts cases
+ // For Smart Wallets, we'll use the client directly to avoid the EOA fallback
const { sendUserOperationAsync, sendUserOperation } = useSendUserOperation({
- client,
+ client: !isSmartWallet ? client : undefined, // feature_1_part_3: client is undefined for Smart Wallets otherwise it falls back to EOA
waitForTxn: true,
});
@@ -103,23 +108,70 @@ export function useScaffoldWriteContract(
return;
}
+// feature_1_part_3: useScaffoldWriteContract now supports Smart Wallets (EIP-7702) userOps block start ------
try {
setIsMining(true);
+ if (isSmartWallet) {
+ setIsSmartWalletMining(true);
+ }
+
const { blockConfirmations, onBlockConfirmation } = options || {};
- const makeWriteWithParams = async () => {
- const { hash } = await sendUserOperationAsync({
- uo: {
- target: deployedContractData.address,
- data: encodeFunctionData({
- abi: deployedContractData.abi,
- functionName: variables.functionName,
- args: variables.args || [],
- } as EncodeFunctionDataParameters),
- value: variables.value,
- },
- });
- return hash;
+
+ const makeWriteWithParams = async (): Promise => {
+ const encodedData = encodeFunctionData({
+ abi: deployedContractData.abi,
+ functionName: variables.functionName,
+ args: variables.args || [],
+ } as EncodeFunctionDataParameters);
+
+ // For Smart Wallets (EIP-7702), use the client directly
+ if (isSmartWallet && client) {
+ console.log("[ScaffoldWrite] Using Smart Wallet client for UserOp");
+ console.log("[ScaffoldWrite] Target:", deployedContractData.address);
+ console.log("[ScaffoldWrite] Function:", variables.functionName);
+ console.log("[ScaffoldWrite] Value:", variables.value || 0n);
+
+ try {
+ const result = await client.sendUserOperation({
+ uo: {
+ target: deployedContractData.address,
+ data: encodedData,
+ value: variables.value || 0n,
+ },
+ });
+
+ console.log("[ScaffoldWrite] UserOp sent, result:", result);
+
+ // Wait for the transaction if we have a hash
+ if ('hash' in result && result.hash) {
+ console.log("[ScaffoldWrite] Waiting for UserOp transaction:", result.hash);
+ const txHash = await client.waitForUserOperationTransaction({
+ hash: result.hash,
+ });
+ console.log("[ScaffoldWrite] Transaction confirmed:", txHash);
+ return txHash as Hex;
+ }
+
+ throw new Error("Failed to get transaction hash from Smart Wallet operation");
+ } catch (error) {
+ console.error("[ScaffoldWrite] Smart Wallet operation failed:", error);
+ throw error;
+ }
+ } else if (sendUserOperationAsync) {
+ // Use the SDK's sendUserOperationAsync for regular smart accounts
+ const { hash } = await sendUserOperationAsync({
+ uo: {
+ target: deployedContractData.address,
+ data: encodedData,
+ value: variables.value,
+ },
+ });
+ return hash as Hex;
+ } else {
+ throw new Error("No method available to send transaction");
+ }
};
+
const writeTxResult = await writeTx(makeWriteWithParams, { blockConfirmations, onBlockConfirmation });
return writeTxResult;
@@ -127,9 +179,11 @@ export function useScaffoldWriteContract(
throw e;
} finally {
setIsMining(false);
+ setIsSmartWalletMining(false);
}
};
-
+// feature_1_part_3: useScaffoldWriteContract now supports Smart Wallets (EIP-7702) userOps block end ------
+
const sendContractWriteTx = <
TContractName extends ContractName,
TFunctionName extends ExtractAbiFunctionNames, "nonpayable" | "payable">,
@@ -141,25 +195,53 @@ export function useScaffoldWriteContract(
return;
}
- sendUserOperation({
- uo: {
- target: deployedContractData.address,
- data: encodeFunctionData({
- abi: deployedContractData.abi,
- functionName: variables.functionName,
- args: variables.args || [],
- } as EncodeFunctionDataParameters),
- value: variables.value,
- },
- });
+// feature_1_part_3: useScaffoldWriteContract now supports Smart Wallets (EIP-7702) userOps block start ------
+ if (!client && !isSmartWallet) {
+ notification.error(`You must first login before making an onchain action`);
+ return;
+ }
+
+ const encodedData = encodeFunctionData({
+ abi: deployedContractData.abi,
+ functionName: variables.functionName,
+ args: variables.args || [],
+ } as EncodeFunctionDataParameters);
+
+ // For Smart Wallets (EIP-7702), use the client directly
+ if (isSmartWallet && client) {
+ console.log("[ScaffoldWrite] Using Smart Wallet client for UserOp (sync)");
+ setIsSmartWalletMining(true);
+ client.sendUserOperation({
+ uo: {
+ target: deployedContractData.address,
+ data: encodedData,
+ value: variables.value || 0n,
+ },
+ }).then(() => {
+ setIsSmartWalletMining(false);
+ }).catch((error: any) => {
+ console.error("Smart Wallet operation failed:", error);
+ setIsSmartWalletMining(false);
+ });
+ } else if (sendUserOperation) {
+ // Use the SDK's sendUserOperation for regular smart accounts
+ sendUserOperation({
+ uo: {
+ target: deployedContractData.address,
+ data: encodedData,
+ value: variables.value,
+ },
+ });
+ }
};
return {
...wagmiContractWrite,
- isMining,
+ isMining: isMining || isSmartWalletMining,
// Overwrite wagmi's writeContactAsync
writeContractAsync: sendContractWriteAsyncTx,
// Overwrite wagmi's writeContract
writeContract: sendContractWriteTx,
};
}
+// feature_1_part_3: useScaffoldWriteContract now supports Smart Wallets (EIP-7702) userOps block end ------
\ No newline at end of file
From 4371d65cbc95ceb321047eb221d6af427e039605 Mon Sep 17 00:00:00 2001
From: Ephyrian <150686927+UMainLove@users.noreply.github.com>
Date: Sat, 12 Jul 2025 21:41:46 +0200
Subject: [PATCH 11/11] feature: Added explicit Smart Contract Account
deployment button
---
README.md | 32 ++--
.../ConnectButton/AddressInfoDropdown.tsx | 46 ++++-
.../nextjs/hooks/scaffold-alchemy/index.ts | 1 +
.../hooks/scaffold-alchemy/useAccountType.ts | 36 +++-
.../useSmartAccountDeployment.ts | 167 ++++++++++++++++++
5 files changed, 262 insertions(+), 20 deletions(-)
create mode 100644 packages/nextjs/hooks/scaffold-alchemy/useSmartAccountDeployment.ts
diff --git a/README.md b/README.md
index f39499a..4cfb502 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,6 @@
-# 🏗 Scaffold-Alchemy
+# 🏗 Scaffold-Alchemy by UMainLove
+
+## THIS FORK CONTAINS EIP-7702 SUPPORT WITH METAMASK WALLETS
Scaffold-Alchemy is a fork of the popular starter project [Scaffold-Eth 2](https://scaffoldeth.io/). It is everything you need to build dApps on Ethereum. You can get started immediately NextJS, TypeScript, Hardhat, AccountKit, Enhanced APIs and Subgraphs 🤩
@@ -16,34 +18,42 @@ Before you begin, you need to install the following tools:
To get started with Scaffold-Alchemy, follow the steps below:
-1. Install the latest version of Scaffold-Alchemy
+Install the latest version of Scaffold-Alchemy
-```
+```bash
npx create-web3-dapp
```
-2. In a terminal, deploy the test contract:
+In a terminal, deploy the test contract:
-```
+```bash
yarn deploy
```
This command deploys a test smart contract to a testnet. You can see the default testnet in `packages/hardhat/hardhat/config.ts`
-3. In a second terminal, start your NextJS app:
+In a second terminal, start your NextJS app:
-```
+```bash
yarn start
```
-Visit your app on: `http://localhost:3000`. You can interact with your smart contract using the `Debug Contracts` page. You can tweak the app config in `packages/nextjs/scaffold.config.ts`.
+Visit your app on: `http://localhost:56900`. You can interact with your smart contract using the `Debug Contracts` page. You can tweak the app config in `packages/nextjs/scaffold.config.ts`.
+
+## Additional Features
+
+- Added deployment button for Modular Smart Contract Accounts (erc4337 + erc6900)
+- Added eip-7702 upgrade button for connected EOA (Tested with: Metamask Wallets on Ethereum Sepolia testnet, private key required)
+- Smart Wallets (EOA + eip7702 + erc4337) interactions fully supported
## Documentation
-Visit our [docs](https://docs.alchemy.com/docs/scaffold-alchemy) to learn all the technical details and guides of Scaffold-Alchemy.
+Visit [docs](https://docs.alchemy.com/docs/scaffold-alchemy) to learn all the technical details and guides of Scaffold-Alchemy.
-## Contributing to Scaffold-Alchemy
+Visit [EIP-7702](https://eips.ethereum.org/EIPS/eip-7702) to learn more about this Ethereum Improvement Proposal.
-We welcome contributions to Scaffold-Alchemy!
+Visit [Implementation](https://www.alchemy.com/docs/wallets/react/using-7702) to learn how eip-7702 could be implemented using Alchemy.
+
+## Contributing to Scaffold-Alchemy
Please see [CONTRIBUTING.MD](https://github.com/alchemyplatform/scaffold-alchemy/blob/main/CONTRIBUTING.md) for more information and guidelines for contributing to Scaffold-Alchemy.
diff --git a/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx b/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx
index 02f5fab..6ac2cce 100644
--- a/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx
+++ b/packages/nextjs/components/scaffold-alchemy/ConnectButton/AddressInfoDropdown.tsx
@@ -1,4 +1,6 @@
/* eslint-disable prettier/prettier */
+/* eslint-disable @typescript-eslint/no-explicit-any */
+
import { useRef, useState } from "react";
import { NetworkOptions } from "./NetworkOptions";
import { useLogout } from "@account-kit/react";
@@ -15,9 +17,10 @@ import {
QrCodeIcon,
ShieldCheckIcon, //feature_1
ExclamationTriangleIcon, // feature_1_part_2
+ RocketLaunchIcon, // feature_2
} from "@heroicons/react/24/outline";
import { BlockieAvatar, isENS } from "~~/components/scaffold-alchemy";
-import { useOutsideClick, useAccountType, useEOAUpgrade } from "~~/hooks/scaffold-alchemy"; // 'useAccountType' is feature_1 AND 'useEOAUpgrade' is feature_1_part_2
+import { useOutsideClick, useAccountType, useEOAUpgrade, useSmartAccountDeployment } from "~~/hooks/scaffold-alchemy"; // 'useAccountType' is feature_1 AND 'useEOAUpgrade' is feature_1_part_2 AND 'useSmartAccountDeployment' is feature_2
import { getTargetNetworks } from "~~/utils/scaffold-alchemy";
import { EIP7702_CONFIG } from "~~/utils/scaffold-alchemy/eip7702.config"; // feature_1_part_2
@@ -46,6 +49,10 @@ export const AddressInfoDropdown = ({
isMetaMaskConnected,
reset: resetUpgrade
} = useEOAUpgrade(); // feature_1_part_2
+ const {
+ isDeploying,
+ deploySmartAccount,
+ } = useSmartAccountDeployment(); // feature_2
const [addressCopied, setAddressCopied] = useState(false);
const [showPrivateKeyModal, setShowPrivateKeyModal] = useState(false); // feature_1_part_2
@@ -67,12 +74,17 @@ export const AddressInfoDropdown = ({
const isUpgradeEnabled = showUpgradeButton && isEIP7702Supported;
const isUpgrading = upgradeStatus === "upgrading";
+ // feature_2: Determine if deploy button should be shown
+ const showDeployButton = accountType === "SCA_4337_UNDEPLOYED";
+
// Get the appropriate message for the account status
const getAccountStatusMessage = () => {
if (isUpgrading) return "Upgrading...";
+ if (isDeploying) return "Deploying...";
if (accountType === "EOA" && isMetaMaskConnected) return EIP7702_CONFIG.messages.UPGRADE_BUTTON;
if (accountType === "EOA_7702") return EIP7702_CONFIG.messages.UPGRADED_EOA;
if (accountType === "SCA_4337") return EIP7702_CONFIG.messages.SMART_ACCOUNT;
+ if (accountType === "SCA_4337_UNDEPLOYED") return "Deploy Smart Account";
if (accountType === "UNKNOWN") return EIP7702_CONFIG.messages.DETECTING;
return "This is an EOA";
};
@@ -86,6 +98,14 @@ export const AddressInfoDropdown = ({
}
};
+ // Handle deploy button click for SCA_4337_UNDEPLOYED accounts
+ const handleDeployClick = async () => {
+ if (!isDeploying) {
+ closeDropdown();
+ await deploySmartAccount();
+ }
+ };
+
const handlePrivateKeyUpgrade = async () => {
if (!privateKey) return;
@@ -167,9 +187,23 @@ export const AddressInfoDropdown = ({
- {showUpgradeButton ? (
+ {showDeployButton ? (
+
+ ) : showUpgradeButton ? (