From 0bcc997d18a7fecdd628d6a7f1a59f226a305bf4 Mon Sep 17 00:00:00 2001 From: Joaquim Verges Date: Wed, 4 Jun 2025 11:24:15 +1200 Subject: [PATCH] [SDK] Support EIP7702 execution for ecosystem wallets --- .changeset/small-moons-add.md | 5 + .../client/EcosystemPermissionsPage.tsx | 4 + .../client/auth-options-form.client.tsx | 210 ++++++++++++------ .../server/auth-options-section.tsx | 10 +- .../[team_slug]/(team)/~/ecosystem/types.ts | 3 +- .../components/in-app-wallet/ecosystem.tsx | 2 +- .../get-ecosystem-wallet-auth-options.ts | 3 +- .../wallets/in-app/core/wallet/in-app-core.ts | 113 +++++----- 8 files changed, 212 insertions(+), 138 deletions(-) create mode 100644 .changeset/small-moons-add.md diff --git a/.changeset/small-moons-add.md b/.changeset/small-moons-add.md new file mode 100644 index 00000000000..b4b4dfafe5c --- /dev/null +++ b/.changeset/small-moons-add.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Support EIP7702 execution for ecosystem wallets diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/EcosystemPermissionsPage.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/EcosystemPermissionsPage.tsx index 776dc75e019..cfd9566b951 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/EcosystemPermissionsPage.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/EcosystemPermissionsPage.tsx @@ -1,4 +1,5 @@ "use client"; +import { getClientThirdwebClient } from "@/constants/thirdweb-client.client"; import { useEcosystem } from "../../../hooks/use-ecosystem"; import { AuthOptionsSection } from "../server/auth-options-section"; import { EcosystemPartnersSection } from "../server/ecosystem-partners-section"; @@ -18,6 +19,8 @@ export function EcosystemPermissionsPage({ teamIdOrSlug: params.team_slug, }); + const client = getClientThirdwebClient({ jwt: authToken, teamId }); + return (
{ecosystem?.permission === "PARTNER_WHITELIST" && ( ({ defaultValues: { authOptions: ecosystem.authOptions || [], @@ -71,6 +80,7 @@ export function AuthOptionsForm({ DEFAULT_ACCOUNT_FACTORY_V0_6 ? "v0.6" : "custom", + executionMode: ecosystem.smartAccountOptions?.executionMode || "EIP4337", customAccountFactoryAddress: ecosystem.smartAccountOptions?.accountFactoryAddress || "", }, @@ -97,6 +107,7 @@ export function AuthOptionsForm({ .optional(), accountFactoryType: z.enum(["v0.6", "v0.7", "custom"]), customAccountFactoryAddress: z.string().optional(), + executionMode: z.enum(["EIP4337", "EIP7702"]), }) .refine( (data) => { @@ -203,6 +214,7 @@ export function AuthOptionsForm({ defaultChainId: data.defaultChainId, sponsorGas: data.sponsorGas, accountFactoryAddress, + executionMode: data.executionMode, }; } @@ -437,26 +449,71 @@ export function AuthOptionsForm({ /> {form.watch("useSmartAccount") && (
- ( - - - - -
- Sponsor Gas - - Enable gas sponsorship for smart accounts - -
-
- )} - /> +
+ ( + + Execution Mode + + {(() => { + const originalExecutionMode = + ecosystem.smartAccountOptions?.executionMode || + "EIP4337"; + const currentExecutionMode = + form.watch("executionMode"); + const hasChanged = + currentExecutionMode !== originalExecutionMode; + + return ( + + {hasChanged + ? "Changing execution mode will change the final user addresses when they connect to your ecosystem." + : "Smart account standard (EIP-7702 is recommended)"} + + ); + })()} + + + )} + /> + ( + + + + +
+ Sponsor Gas + + Enable gas sponsorship for smart accounts + +
+
+ )} + /> +
Default Chain ID - + This will be the chain ID the smart account will be @@ -484,56 +545,63 @@ export function AuthOptionsForm({ )} /> - ( - - Account Factory - - - Choose a default account factory or select custom to enter - your own address - - - - )} - /> - {form.watch("accountFactoryType") === "custom" && ( - ( - - Custom Account Factory Address - - - - - Enter your own smart account factory contract address - - - + {form.watch("executionMode") === "EIP4337" && ( + <> + ( + + Account Factory + + + Choose a default account factory or select custom to + enter your own address + + + + )} + /> + {form.watch("accountFactoryType") === "custom" && ( + ( + + Custom Account Factory Address + + + + + Enter your own smart account factory contract + address + + + + )} + /> )} - /> + )}
)} diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/auth-options-section.tsx b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/auth-options-section.tsx index f4c0fa03931..672790a78d2 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/auth-options-section.tsx +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/auth-options-section.tsx @@ -1,3 +1,4 @@ +import type { ThirdwebClient } from "thirdweb"; import type { Ecosystem } from "../../../../../types"; import { AuthOptionsForm, @@ -8,7 +9,13 @@ export function AuthOptionsSection({ ecosystem, authToken, teamId, -}: { ecosystem?: Ecosystem; authToken: string; teamId: string }) { + client, +}: { + ecosystem?: Ecosystem; + authToken: string; + teamId: string; + client: ThirdwebClient; +}) { return (
{ecosystem ? ( @@ -16,6 +23,7 @@ export function AuthOptionsSection({ ecosystem={ecosystem} authToken={authToken} teamId={teamId} + client={client} /> ) : ( diff --git a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/types.ts b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/types.ts index f2d0f66d3be..5db9b603617 100644 --- a/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/types.ts +++ b/apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/types.ts @@ -38,7 +38,8 @@ export type Ecosystem = { smartAccountOptions?: { defaultChainId: number; sponsorGas: boolean; - accountFactoryAddress: string; + accountFactoryAddress?: string; + executionMode?: "EIP4337" | "EIP7702"; } | null; url: string; status: "active" | "requested" | "paymentFailed"; diff --git a/apps/playground-web/src/components/in-app-wallet/ecosystem.tsx b/apps/playground-web/src/components/in-app-wallet/ecosystem.tsx index cecf6c3d4ff..9324c5d0a56 100644 --- a/apps/playground-web/src/components/in-app-wallet/ecosystem.tsx +++ b/apps/playground-web/src/components/in-app-wallet/ecosystem.tsx @@ -8,7 +8,7 @@ const getEcosystemWallet = () => { process.env.NEXT_PUBLIC_IN_APP_WALLET_URL?.endsWith(".thirdweb-dev.com") ) { // dev ecosystem - return ecosystemWallet("ecosystem.catlovers"); + return ecosystemWallet("ecosystem.catfans"); } // prod ecosystem return ecosystemWallet("ecosystem.thirdweb-engs", { diff --git a/packages/thirdweb/src/wallets/ecosystem/get-ecosystem-wallet-auth-options.ts b/packages/thirdweb/src/wallets/ecosystem/get-ecosystem-wallet-auth-options.ts index ac8ab8c37cb..8ea7d776dae 100644 --- a/packages/thirdweb/src/wallets/ecosystem/get-ecosystem-wallet-auth-options.ts +++ b/packages/thirdweb/src/wallets/ecosystem/get-ecosystem-wallet-auth-options.ts @@ -15,7 +15,8 @@ type EcosystemOptions = { type SmartAccountOptions = { defaultChainId: number; sponsorGas: boolean; - accountFactoryAddress: string; + accountFactoryAddress?: string; + executionMode?: "EIP4337" | "EIP7702"; }; /** diff --git a/packages/thirdweb/src/wallets/in-app/core/wallet/in-app-core.ts b/packages/thirdweb/src/wallets/in-app/core/wallet/in-app-core.ts index 209fb344f8f..9c3332f8a5e 100644 --- a/packages/thirdweb/src/wallets/in-app/core/wallet/in-app-core.ts +++ b/packages/thirdweb/src/wallets/in-app/core/wallet/in-app-core.ts @@ -57,6 +57,47 @@ export function createInAppWallet(args: { let client: ThirdwebClient | undefined; let authToken: string | null = null; + const resolveSmartAccountOptionsFromEcosystem = async (options: { + chain?: Chain; + }) => { + if (ecosystem) { + const ecosystemOptions = await getEcosystemInfo(ecosystem.id); + const smartAccountOptions = ecosystemOptions?.smartAccountOptions; + if (smartAccountOptions) { + const executionMode = + ecosystemOptions.smartAccountOptions.executionMode; + if (executionMode === "EIP7702") { + createOptions = { + ...createOptions, + executionMode: { + mode: "EIP7702", + sponsorGas: smartAccountOptions.sponsorGas, + }, + }; + } else { + // default to 4337 + const { defaultChainId } = ecosystemOptions.smartAccountOptions; + const preferredChain = + options.chain ?? + (defaultChainId ? getCachedChain(defaultChainId) : undefined); + if (!preferredChain) { + throw new Error( + `A chain must be provided either via 'chain' in connect options or 'defaultChainId' in ecosystem configuration. Please pass it via connect() or update the ecosystem configuration.`, + ); + } + createOptions = { + ...createOptions, + smartAccount: { + chain: preferredChain, + sponsorGas: smartAccountOptions.sponsorGas, + factoryAddress: smartAccountOptions.accountFactoryAddress, + }, + }; + } + } + } + }; + return { id: walletId, getAuthToken: () => authToken, @@ -80,30 +121,7 @@ export function createInAppWallet(args: { ecosystem, ); - if (ecosystem) { - const ecosystemOptions = await getEcosystemInfo(ecosystem.id); - const smartAccountOptions = ecosystemOptions?.smartAccountOptions; - if (smartAccountOptions) { - const { defaultChainId } = ecosystemOptions.smartAccountOptions; - const preferredChain = - options.chain ?? - (defaultChainId ? getCachedChain(defaultChainId) : undefined); - if (!preferredChain) { - throw new Error( - `A chain must be provided either via 'chain' in connect options or 'defaultChainId' in ecosystem configuration. Please pass it via connect() or update the ecosystem configuration.`, - ); - } - - createOptions = { - ...createOptions, - smartAccount: { - chain: preferredChain, - sponsorGas: smartAccountOptions.sponsorGas, - factoryAddress: smartAccountOptions.accountFactoryAddress, - }, - }; - } - } + await resolveSmartAccountOptionsFromEcosystem(options); const { account: connectedAccount, @@ -140,30 +158,7 @@ export function createInAppWallet(args: { ecosystem, ); - if (ecosystem) { - const ecosystemOptions = await getEcosystemInfo(ecosystem.id); - const smartAccountOptions = ecosystemOptions?.smartAccountOptions; - if (smartAccountOptions) { - const { defaultChainId } = ecosystemOptions.smartAccountOptions; - const preferredChain = - options.chain ?? - (defaultChainId ? getCachedChain(defaultChainId) : undefined); - if (!preferredChain) { - throw new Error( - `A chain must be provided either via 'chain' in connect options or 'defaultChainId' in ecosystem configuration. Please pass it via connect() or update the ecosystem configuration.`, - ); - } - - createOptions = { - ...createOptions, - smartAccount: { - chain: preferredChain, - sponsorGas: smartAccountOptions.sponsorGas, - factoryAddress: smartAccountOptions.accountFactoryAddress, - }, - }; - } - } + await resolveSmartAccountOptionsFromEcosystem(options); const { account: connectedAccount, @@ -212,7 +207,12 @@ export function createInAppWallet(args: { emitter.emit("disconnect", undefined); }, switchChain: async (newChain) => { - if (createOptions?.smartAccount && client && account) { + if ( + (createOptions?.smartAccount || + createOptions?.executionMode?.mode === "EIP4337") && + client && + account + ) { // if account abstraction is enabled, reconnect to smart account on the new chain const { autoConnectInAppWallet } = await import("./index.js"); const connector = await getOrCreateInAppWalletConnector( @@ -221,20 +221,7 @@ export function createInAppWallet(args: { ecosystem, ); - if (ecosystem) { - const ecosystemOptions = await getEcosystemInfo(ecosystem.id); - const smartAccountOptions = ecosystemOptions?.smartAccountOptions; - if (smartAccountOptions) { - createOptions = { - ...createOptions, - smartAccount: { - chain: newChain, - sponsorGas: smartAccountOptions.sponsorGas, - factoryAddress: smartAccountOptions.accountFactoryAddress, - }, - }; - } - } + await resolveSmartAccountOptionsFromEcosystem({ chain: newChain }); const { account: connectedAccount,