Skip to content

Commit 0bcc997

Browse files
[SDK] Support EIP7702 execution for ecosystem wallets
1 parent dfe4cf0 commit 0bcc997

File tree

8 files changed

+212
-138
lines changed

8 files changed

+212
-138
lines changed

.changeset/small-moons-add.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"thirdweb": patch
3+
---
4+
5+
Support EIP7702 execution for ecosystem wallets

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/EcosystemPermissionsPage.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"use client";
2+
import { getClientThirdwebClient } from "@/constants/thirdweb-client.client";
23
import { useEcosystem } from "../../../hooks/use-ecosystem";
34
import { AuthOptionsSection } from "../server/auth-options-section";
45
import { EcosystemPartnersSection } from "../server/ecosystem-partners-section";
@@ -18,6 +19,8 @@ export function EcosystemPermissionsPage({
1819
teamIdOrSlug: params.team_slug,
1920
});
2021

22+
const client = getClientThirdwebClient({ jwt: authToken, teamId });
23+
2124
return (
2225
<div className="flex flex-col gap-8">
2326
<IntegrationPermissionsSection
@@ -29,6 +32,7 @@ export function EcosystemPermissionsPage({
2932
ecosystem={ecosystem}
3033
authToken={authToken}
3134
teamId={teamId}
35+
client={client}
3236
/>
3337
{ecosystem?.permission === "PARTNER_WHITELIST" && (
3438
<EcosystemPartnersSection

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/client/auth-options-form.client.tsx

Lines changed: 139 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"use client";
2+
import { SingleNetworkSelector } from "@/components/blocks/NetworkSelectors";
23
import { SettingsCard } from "@/components/blocks/SettingsCard";
34
import { Button } from "@/components/ui/button";
45
import { Checkbox } from "@/components/ui/checkbox";
@@ -27,6 +28,7 @@ import { PlusIcon } from "lucide-react";
2728
import { useFieldArray, useForm } from "react-hook-form";
2829
import { toast } from "sonner";
2930
import { isAddress } from "thirdweb";
31+
import type { ThirdwebClient } from "thirdweb";
3032
import { getSocialIcon } from "thirdweb/wallets/in-app";
3133
import {
3234
DEFAULT_ACCOUNT_FACTORY_V0_6,
@@ -47,13 +49,20 @@ type AuthOptionsFormData = {
4749
defaultChainId: number;
4850
accountFactoryType: "v0.6" | "v0.7" | "custom";
4951
customAccountFactoryAddress: string;
52+
executionMode: "EIP4337" | "EIP7702";
5053
};
5154

5255
export function AuthOptionsForm({
5356
ecosystem,
5457
authToken,
5558
teamId,
56-
}: { ecosystem: Ecosystem; authToken: string; teamId: string }) {
59+
client,
60+
}: {
61+
ecosystem: Ecosystem;
62+
authToken: string;
63+
teamId: string;
64+
client: ThirdwebClient;
65+
}) {
5766
const form = useForm<AuthOptionsFormData>({
5867
defaultValues: {
5968
authOptions: ecosystem.authOptions || [],
@@ -71,6 +80,7 @@ export function AuthOptionsForm({
7180
DEFAULT_ACCOUNT_FACTORY_V0_6
7281
? "v0.6"
7382
: "custom",
83+
executionMode: ecosystem.smartAccountOptions?.executionMode || "EIP4337",
7484
customAccountFactoryAddress:
7585
ecosystem.smartAccountOptions?.accountFactoryAddress || "",
7686
},
@@ -97,6 +107,7 @@ export function AuthOptionsForm({
97107
.optional(),
98108
accountFactoryType: z.enum(["v0.6", "v0.7", "custom"]),
99109
customAccountFactoryAddress: z.string().optional(),
110+
executionMode: z.enum(["EIP4337", "EIP7702"]),
100111
})
101112
.refine(
102113
(data) => {
@@ -203,6 +214,7 @@ export function AuthOptionsForm({
203214
defaultChainId: data.defaultChainId,
204215
sponsorGas: data.sponsorGas,
205216
accountFactoryAddress,
217+
executionMode: data.executionMode,
206218
};
207219
}
208220

@@ -437,34 +449,83 @@ export function AuthOptionsForm({
437449
/>
438450
{form.watch("useSmartAccount") && (
439451
<div className="mt-1 flex flex-col gap-4">
440-
<FormField
441-
control={form.control}
442-
name="sponsorGas"
443-
render={({ field }) => (
444-
<FormItem className="flex flex-row items-center space-x-3 space-y-0">
445-
<FormControl>
446-
<Switch
447-
checked={field.value}
448-
onCheckedChange={field.onChange}
449-
/>
450-
</FormControl>
451-
<div className="space-y-1 leading-none">
452-
<FormLabel>Sponsor Gas</FormLabel>
453-
<FormDescription>
454-
Enable gas sponsorship for smart accounts
455-
</FormDescription>
456-
</div>
457-
</FormItem>
458-
)}
459-
/>
452+
<div className="flex flex-col gap-4 md:flex-row md:gap-6">
453+
<FormField
454+
control={form.control}
455+
name="executionMode"
456+
render={({ field }) => (
457+
<FormItem className="flex-1">
458+
<FormLabel>Execution Mode</FormLabel>
459+
<Select
460+
onValueChange={field.onChange}
461+
defaultValue={field.value}
462+
>
463+
<FormControl>
464+
<SelectTrigger>
465+
<SelectValue placeholder="Select execution mode" />
466+
</SelectTrigger>
467+
</FormControl>
468+
<SelectContent>
469+
<SelectItem value="EIP4337">EIP-4337</SelectItem>
470+
<SelectItem value="EIP7702">EIP-7702</SelectItem>
471+
</SelectContent>
472+
</Select>
473+
{(() => {
474+
const originalExecutionMode =
475+
ecosystem.smartAccountOptions?.executionMode ||
476+
"EIP4337";
477+
const currentExecutionMode =
478+
form.watch("executionMode");
479+
const hasChanged =
480+
currentExecutionMode !== originalExecutionMode;
481+
482+
return (
483+
<FormDescription
484+
className={hasChanged ? "text-warning-text" : ""}
485+
>
486+
{hasChanged
487+
? "Changing execution mode will change the final user addresses when they connect to your ecosystem."
488+
: "Smart account standard (EIP-7702 is recommended)"}
489+
</FormDescription>
490+
);
491+
})()}
492+
<FormMessage />
493+
</FormItem>
494+
)}
495+
/>
496+
<FormField
497+
control={form.control}
498+
name="sponsorGas"
499+
render={({ field }) => (
500+
<FormItem className="flex flex-row items-center space-x-3 space-y-0 md:flex-1">
501+
<FormControl>
502+
<Switch
503+
checked={field.value}
504+
onCheckedChange={field.onChange}
505+
/>
506+
</FormControl>
507+
<div className="space-y-1 leading-none">
508+
<FormLabel>Sponsor Gas</FormLabel>
509+
<FormDescription>
510+
Enable gas sponsorship for smart accounts
511+
</FormDescription>
512+
</div>
513+
</FormItem>
514+
)}
515+
/>
516+
</div>
460517
<FormField
461518
control={form.control}
462519
name="defaultChainId"
463520
render={({ field }) => (
464521
<FormItem>
465522
<FormLabel>Default Chain ID</FormLabel>
466523
<FormControl>
467-
<Input {...field} placeholder="1" />
524+
<SingleNetworkSelector
525+
client={client}
526+
chainId={field.value}
527+
onChange={field.onChange}
528+
/>
468529
</FormControl>
469530
<FormDescription>
470531
This will be the chain ID the smart account will be
@@ -484,56 +545,63 @@ export function AuthOptionsForm({
484545
)}
485546
/>
486547

487-
<FormField
488-
control={form.control}
489-
name="accountFactoryType"
490-
render={({ field }) => (
491-
<FormItem>
492-
<FormLabel>Account Factory</FormLabel>
493-
<Select
494-
onValueChange={field.onChange}
495-
defaultValue={field.value}
496-
>
497-
<FormControl>
498-
<SelectTrigger>
499-
<SelectValue placeholder="Select account factory type" />
500-
</SelectTrigger>
501-
</FormControl>
502-
<SelectContent>
503-
<SelectItem value="v0.6">
504-
Default Account Factory (v0.6)
505-
</SelectItem>
506-
<SelectItem value="v0.7">
507-
Default Account Factory (v0.7)
508-
</SelectItem>
509-
<SelectItem value="custom">Custom factory</SelectItem>
510-
</SelectContent>
511-
</Select>
512-
<FormDescription>
513-
Choose a default account factory or select custom to enter
514-
your own address
515-
</FormDescription>
516-
<FormMessage />
517-
</FormItem>
518-
)}
519-
/>
520-
{form.watch("accountFactoryType") === "custom" && (
521-
<FormField
522-
control={form.control}
523-
name="customAccountFactoryAddress"
524-
render={({ field }) => (
525-
<FormItem>
526-
<FormLabel>Custom Account Factory Address</FormLabel>
527-
<FormControl>
528-
<Input {...field} placeholder="0x..." />
529-
</FormControl>
530-
<FormDescription>
531-
Enter your own smart account factory contract address
532-
</FormDescription>
533-
<FormMessage />
534-
</FormItem>
548+
{form.watch("executionMode") === "EIP4337" && (
549+
<>
550+
<FormField
551+
control={form.control}
552+
name="accountFactoryType"
553+
render={({ field }) => (
554+
<FormItem>
555+
<FormLabel>Account Factory</FormLabel>
556+
<Select
557+
onValueChange={field.onChange}
558+
defaultValue={field.value}
559+
>
560+
<FormControl>
561+
<SelectTrigger>
562+
<SelectValue placeholder="Select account factory type" />
563+
</SelectTrigger>
564+
</FormControl>
565+
<SelectContent>
566+
<SelectItem value="v0.6">
567+
Default Account Factory (v0.6)
568+
</SelectItem>
569+
<SelectItem value="v0.7">
570+
Default Account Factory (v0.7)
571+
</SelectItem>
572+
<SelectItem value="custom">
573+
Custom factory
574+
</SelectItem>
575+
</SelectContent>
576+
</Select>
577+
<FormDescription>
578+
Choose a default account factory or select custom to
579+
enter your own address
580+
</FormDescription>
581+
<FormMessage />
582+
</FormItem>
583+
)}
584+
/>
585+
{form.watch("accountFactoryType") === "custom" && (
586+
<FormField
587+
control={form.control}
588+
name="customAccountFactoryAddress"
589+
render={({ field }) => (
590+
<FormItem>
591+
<FormLabel>Custom Account Factory Address</FormLabel>
592+
<FormControl>
593+
<Input {...field} placeholder="0x..." />
594+
</FormControl>
595+
<FormDescription>
596+
Enter your own smart account factory contract
597+
address
598+
</FormDescription>
599+
<FormMessage />
600+
</FormItem>
601+
)}
602+
/>
535603
)}
536-
/>
604+
</>
537605
)}
538606
</div>
539607
)}

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/[slug]/(active)/configuration/components/server/auth-options-section.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { ThirdwebClient } from "thirdweb";
12
import type { Ecosystem } from "../../../../../types";
23
import {
34
AuthOptionsForm,
@@ -8,14 +9,21 @@ export function AuthOptionsSection({
89
ecosystem,
910
authToken,
1011
teamId,
11-
}: { ecosystem?: Ecosystem; authToken: string; teamId: string }) {
12+
client,
13+
}: {
14+
ecosystem?: Ecosystem;
15+
authToken: string;
16+
teamId: string;
17+
client: ThirdwebClient;
18+
}) {
1219
return (
1320
<section className="flex flex-col gap-4 md:gap-8">
1421
{ecosystem ? (
1522
<AuthOptionsForm
1623
ecosystem={ecosystem}
1724
authToken={authToken}
1825
teamId={teamId}
26+
client={client}
1927
/>
2028
) : (
2129
<AuthOptionsFormSkeleton />

apps/dashboard/src/app/(app)/team/[team_slug]/(team)/~/ecosystem/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ export type Ecosystem = {
3838
smartAccountOptions?: {
3939
defaultChainId: number;
4040
sponsorGas: boolean;
41-
accountFactoryAddress: string;
41+
accountFactoryAddress?: string;
42+
executionMode?: "EIP4337" | "EIP7702";
4243
} | null;
4344
url: string;
4445
status: "active" | "requested" | "paymentFailed";

apps/playground-web/src/components/in-app-wallet/ecosystem.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ const getEcosystemWallet = () => {
88
process.env.NEXT_PUBLIC_IN_APP_WALLET_URL?.endsWith(".thirdweb-dev.com")
99
) {
1010
// dev ecosystem
11-
return ecosystemWallet("ecosystem.catlovers");
11+
return ecosystemWallet("ecosystem.catfans");
1212
}
1313
// prod ecosystem
1414
return ecosystemWallet("ecosystem.thirdweb-engs", {

packages/thirdweb/src/wallets/ecosystem/get-ecosystem-wallet-auth-options.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ type EcosystemOptions = {
1515
type SmartAccountOptions = {
1616
defaultChainId: number;
1717
sponsorGas: boolean;
18-
accountFactoryAddress: string;
18+
accountFactoryAddress?: string;
19+
executionMode?: "EIP4337" | "EIP7702";
1920
};
2021

2122
/**

0 commit comments

Comments
 (0)