diff --git a/.changeset/wicked-gifts-joke.md b/.changeset/wicked-gifts-joke.md
new file mode 100644
index 00000000000..1b49ec5df9e
--- /dev/null
+++ b/.changeset/wicked-gifts-joke.md
@@ -0,0 +1,5 @@
+---
+"thirdweb": patch
+---
+
+Better handling of ecosystem smart accounts
diff --git a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/auth-options-form.client.tsx b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/auth-options-form.client.tsx
index dcc7c558bde..fc13b31cb83 100644
--- a/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/auth-options-form.client.tsx
+++ b/apps/dashboard/src/app/(dashboard)/dashboard/connect/ecosystem/[slug]/(active)/components/client/auth-options-form.client.tsx
@@ -1,5 +1,4 @@
"use client";
-import { MultiNetworkSelector } from "@/components/blocks/NetworkSelectors";
import { SettingsCard } from "@/components/blocks/SettingsCard";
import { Button } from "@/components/ui/button";
import { Checkbox } from "@/components/ui/checkbox";
@@ -27,6 +26,7 @@ import { zodResolver } from "@hookform/resolvers/zod";
import { PlusIcon } from "lucide-react";
import { useFieldArray, useForm } from "react-hook-form";
import { toast } from "sonner";
+import { isAddress } from "thirdweb";
import { getSocialIcon } from "thirdweb/wallets/in-app";
import {
DEFAULT_ACCOUNT_FACTORY_V0_6,
@@ -57,7 +57,7 @@ export function AuthOptionsForm({ ecosystem }: { ecosystem: Ecosystem }) {
customAuthEndpoint: ecosystem.customAuthOptions?.authEndpoint?.url || "",
customHeaders: ecosystem.customAuthOptions?.authEndpoint?.headers || [],
useSmartAccount: !!ecosystem.smartAccountOptions,
- chainIds: ecosystem.smartAccountOptions?.chainIds || [],
+ chainIds: [], // unused - TODO: remove from service
sponsorGas: ecosystem.smartAccountOptions?.sponsorGas || false,
accountFactoryType:
ecosystem.smartAccountOptions?.accountFactoryAddress ===
@@ -92,14 +92,18 @@ export function AuthOptionsForm({ ecosystem }: { ecosystem: Ecosystem }) {
})
.refine(
(data) => {
- if (data.useSmartAccount && data.chainIds.length === 0) {
+ if (
+ data.useSmartAccount &&
+ data.customAccountFactoryAddress &&
+ !isAddress(data.customAccountFactoryAddress)
+ ) {
return false;
}
return true;
},
{
- message: "Please select at least one chain for smart accounts",
- path: ["chainIds"],
+ message: "Please enter a valid custom account factory address",
+ path: ["customAccountFactoryAddress"],
},
)
.refine(
@@ -166,7 +170,7 @@ export function AuthOptionsForm({ ecosystem }: { ecosystem: Ecosystem }) {
}
smartAccountOptions = {
- chainIds: data.chainIds,
+ chainIds: [], // unused - TODO remove from service
sponsorGas: data.sponsorGas,
accountFactoryAddress,
};
@@ -403,27 +407,6 @@ export function AuthOptionsForm({ ecosystem }: { ecosystem: Ecosystem }) {
/>
{form.watch("useSmartAccount") && (
-
(
-
- Supported Chains
-
- Select the chains you want to support for smart accounts
-
-
-
-
-
-
-
-
- )}
- />
{
if (isEcosystemWallet(wallet)) {
- const options = await getEcosystemOptions(wallet.id);
+ const options = await getEcosystemInfo(wallet.id);
return options?.authOptions ?? null;
}
return null;
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 075318fcd4b..1f3303c2e53 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
@@ -1,8 +1,13 @@
import { getThirdwebBaseUrl } from "../../utils/domains.js";
+import { withCache } from "../../utils/promise/withCache.js";
import type { AuthOption } from "../types.js";
import type { EcosystemWalletId } from "../wallet-types.js";
export type EcosystemOptions = {
+ name: string;
+ imageUrl?: string;
+ slug: string;
+ homepage?: string;
authOptions: AuthOption[];
smartAccountOptions: SmartAccountOptions;
};
@@ -19,32 +24,40 @@ type SmartAccountOptions = {
* @returns {AuthOption[] | undefined} The auth options for the ecosystem wallet.
* @internal
*/
-export async function getEcosystemOptions(
+export async function getEcosystemInfo(
walletId: EcosystemWalletId,
-): Promise {
- const res = await fetch(
- `${getThirdwebBaseUrl("inAppWallet")}/api/2024-05-05/ecosystem-wallet`,
- {
- headers: {
- "x-ecosystem-id": walletId,
- },
- },
- );
+): Promise {
+ return withCache(
+ async () => {
+ const res = await fetch(
+ `${getThirdwebBaseUrl("inAppWallet")}/api/2024-05-05/ecosystem-wallet`,
+ {
+ headers: {
+ "x-ecosystem-id": walletId,
+ },
+ },
+ );
- const data = await res.json();
+ const data = await res.json();
- if (!data || data.code === "UNAUTHORIZED") {
- throw new Error(
- data.message ||
- `Could not find ecosystem wallet with id ${walletId}, please check your ecosystem wallet configuration.`,
- );
- }
+ if (!data || data.code === "UNAUTHORIZED") {
+ throw new Error(
+ data.message ||
+ `Could not find ecosystem wallet with id ${walletId}, please check your ecosystem wallet configuration.`,
+ );
+ }
- // siwe is the auth option in the backend, but we want to use wallet as the auth option in the frontend
- if (data.authOptions?.includes("siwe")) {
- data.authOptions = data.authOptions.filter((o: string) => o !== "siwe");
- data.authOptions.push("wallet");
- }
+ // siwe is the auth option in the backend, but we want to use wallet as the auth option in the frontend
+ if (data.authOptions?.includes("siwe")) {
+ data.authOptions = data.authOptions.filter((o: string) => o !== "siwe");
+ data.authOptions.push("wallet");
+ }
- return data ?? null;
+ return data;
+ },
+ {
+ cacheKey: `ecosystem-wallet-options-${walletId}`,
+ cacheTime: 1000 * 60 * 5, // 5 mins
+ },
+ );
}
diff --git a/packages/thirdweb/src/wallets/ecosystem/get-ecosystem-wallet-info.ts b/packages/thirdweb/src/wallets/ecosystem/get-ecosystem-wallet-info.ts
index 62a71531506..9536c8d698c 100644
--- a/packages/thirdweb/src/wallets/ecosystem/get-ecosystem-wallet-info.ts
+++ b/packages/thirdweb/src/wallets/ecosystem/get-ecosystem-wallet-info.ts
@@ -1,7 +1,7 @@
-import { getThirdwebBaseUrl } from "../../utils/domains.js";
import type { Prettify } from "../../utils/type-utils.js";
import type { WalletInfo } from "../wallet-info.js";
import type { EcosystemWalletId } from "../wallet-types.js";
+import { getEcosystemInfo } from "./get-ecosystem-wallet-auth-options.js";
/**
* Fetches metadata for a given ecosystem wallet.
@@ -14,29 +14,13 @@ import type { EcosystemWalletId } from "../wallet-types.js";
export async function getEcosystemWalletInfo(
walletId: EcosystemWalletId,
): Promise> {
- const res = await fetch(
- `${getThirdwebBaseUrl("inAppWallet")}/api/2024-05-05/ecosystem-wallet`,
- {
- headers: {
- "x-ecosystem-id": walletId,
- },
- },
- );
-
- const data = await res.json();
-
- if (!data || data.code === "UNAUTHORIZED") {
- throw new Error(
- data.message ||
- `Could not find ecosystem wallet with id ${walletId}, please check your ecosystem wallet configuration.`,
- );
- }
+ const data = await getEcosystemInfo(walletId);
return {
id: walletId,
- name: data.name as string,
- image_id: data.imageUrl as string,
- homepage: data.homepage as string,
+ name: data.name,
+ image_id: data.imageUrl || "",
+ homepage: data.homepage || "",
rdns: null,
app: {
browser: null,
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 eec4833136e..921dbd1cb83 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
@@ -2,6 +2,7 @@ import { trackConnect } from "../../../../analytics/track/connect.js";
import type { Chain } from "../../../../chains/types.js";
import { getCachedChainIfExists } from "../../../../chains/utils.js";
import type { ThirdwebClient } from "../../../../client/client.js";
+import { getEcosystemInfo } from "../../../ecosystem/get-ecosystem-wallet-auth-options.js";
import type { Account, Wallet } from "../../../interfaces/wallet.js";
import { createWalletEmitter } from "../../../wallet-emitter.js";
import type {
@@ -38,9 +39,10 @@ export function createInAppWallet(args: {
connectorFactory: (client: ThirdwebClient) => Promise;
ecosystem?: Ecosystem;
}): Wallet<"inApp" | EcosystemWalletId> {
- const { createOptions, connectorFactory, ecosystem } = args;
+ const { createOptions: _createOptions, connectorFactory, ecosystem } = args;
const walletId = ecosystem ? ecosystem.id : "inApp";
const emitter = createWalletEmitter<"inApp">();
+ let createOptions = _createOptions;
let account: Account | undefined = undefined;
let chain: Chain | undefined = undefined;
let client: ThirdwebClient | undefined;
@@ -66,11 +68,32 @@ export function createInAppWallet(args: {
connectorFactory,
ecosystem,
);
+
+ if (ecosystem) {
+ const ecosystemOptions = await getEcosystemInfo(ecosystem.id);
+ const smartAccountOptions = ecosystemOptions?.smartAccountOptions;
+ if (smartAccountOptions) {
+ const preferredChain = options.chain;
+ if (!preferredChain) {
+ throw new Error(
+ "Chain is required for ecosystem smart accounts, pass it via connect() or via UI components",
+ );
+ }
+ createOptions = {
+ ...createOptions,
+ smartAccount: {
+ chain: preferredChain,
+ sponsorGas: smartAccountOptions.sponsorGas,
+ factoryAddress: smartAccountOptions.accountFactoryAddress,
+ },
+ };
+ }
+ }
+
const [connectedAccount, connectedChain] = await autoConnectInAppWallet(
options,
createOptions,
connector,
- ecosystem,
);
// set the states
@@ -94,11 +117,31 @@ export function createInAppWallet(args: {
ecosystem,
);
+ if (ecosystem) {
+ const ecosystemOptions = await getEcosystemInfo(ecosystem.id);
+ const smartAccountOptions = ecosystemOptions?.smartAccountOptions;
+ if (smartAccountOptions) {
+ const preferredChain = options.chain;
+ if (!preferredChain) {
+ throw new Error(
+ "Chain is required for ecosystem smart accounts, pass it via connect() or via UI components",
+ );
+ }
+ createOptions = {
+ ...createOptions,
+ smartAccount: {
+ chain: preferredChain,
+ sponsorGas: smartAccountOptions.sponsorGas,
+ factoryAddress: smartAccountOptions.accountFactoryAddress,
+ },
+ };
+ }
+ }
+
const [connectedAccount, connectedChain] = await connectInAppWallet(
options,
createOptions,
connector,
- ecosystem,
);
// set the states
client = options.client;
@@ -139,6 +182,22 @@ export function createInAppWallet(args: {
connectorFactory,
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,
+ },
+ };
+ }
+ }
+
const [connectedAccount, connectedChain] = await autoConnectInAppWallet(
{
chain: newChain,
@@ -146,7 +205,6 @@ export function createInAppWallet(args: {
},
createOptions,
connector,
- ecosystem,
);
account = connectedAccount;
chain = connectedChain;
diff --git a/packages/thirdweb/src/wallets/in-app/core/wallet/index.ts b/packages/thirdweb/src/wallets/in-app/core/wallet/index.ts
index 9b508436a01..cd21db10632 100644
--- a/packages/thirdweb/src/wallets/in-app/core/wallet/index.ts
+++ b/packages/thirdweb/src/wallets/in-app/core/wallet/index.ts
@@ -1,12 +1,10 @@
import { ethereum } from "../../../../chains/chain-definitions/ethereum.js";
import type { Chain } from "../../../../chains/types.js";
-import { getCachedChain } from "../../../../chains/utils.js";
import type { ThirdwebClient } from "../../../../client/client.js";
import {
type SocialAuthOption,
socialAuthOptions,
} from "../../../../wallets/types.js";
-import { getEcosystemOptions } from "../../../ecosystem/get-ecosystem-wallet-auth-options.js";
import type { Account, Wallet } from "../../../interfaces/wallet.js";
import type { EcosystemWalletId, WalletId } from "../../../wallet-types.js";
import type {
@@ -15,7 +13,6 @@ import type {
WalletConnectionOption,
} from "../../../wallet-types.js";
import type { InAppConnector } from "../interfaces/connector.js";
-import type { Ecosystem } from "./types.js";
/**
* Checks if the provided wallet is an in-app wallet.
@@ -40,7 +37,6 @@ export async function connectInAppWallet(
| CreateWalletArgs<"inApp">[1]
| CreateWalletArgs[1],
connector: InAppConnector,
- ecosystem: Ecosystem | undefined,
): Promise<[Account, Chain]> {
if (
// if auth mode is not specified, the default is popup
@@ -77,33 +73,6 @@ export async function connectInAppWallet(
});
}
- if (ecosystem) {
- const ecosystemOptions = await getEcosystemOptions(ecosystem.id);
- const smartAccountOptions = ecosystemOptions?.smartAccountOptions;
- if (smartAccountOptions) {
- const allowedChains = smartAccountOptions.chainIds;
- const firstAllowedChain = allowedChains[0];
- if (!firstAllowedChain) {
- throw new Error(
- "At least one chain must be allowed for ecosystem smart account",
- );
- }
- const preferredChain =
- options.chain && allowedChains.includes(options.chain.id)
- ? options.chain
- : getCachedChain(firstAllowedChain);
- return convertToSmartAccount({
- client: options.client,
- authAccount,
- smartAccountOptions: {
- chain: preferredChain,
- sponsorGas: smartAccountOptions.sponsorGas,
- factoryAddress: smartAccountOptions.accountFactoryAddress,
- },
- });
- }
- }
-
return [authAccount, options.chain || ethereum] as const;
}
@@ -118,7 +87,6 @@ export async function autoConnectInAppWallet(
| CreateWalletArgs<"inApp">[1]
| CreateWalletArgs[1],
connector: InAppConnector,
- ecosystem: Ecosystem | undefined,
): Promise<[Account, Chain]> {
if (options.authResult && connector.loginWithAuthToken) {
await connector.loginWithAuthToken(options.authResult);
@@ -144,33 +112,6 @@ export async function autoConnectInAppWallet(
});
}
- if (ecosystem) {
- const ecosystemOptions = await getEcosystemOptions(ecosystem.id);
- const smartAccountOptions = ecosystemOptions?.smartAccountOptions;
- if (smartAccountOptions) {
- const allowedChains = smartAccountOptions.chainIds;
- const firstAllowedChain = allowedChains[0];
- if (!firstAllowedChain) {
- throw new Error(
- "At least one chain must be allowed for ecosystem smart account",
- );
- }
- const preferredChain =
- options.chain && allowedChains.includes(options.chain.id)
- ? options.chain
- : getCachedChain(firstAllowedChain);
- return convertToSmartAccount({
- client: options.client,
- authAccount,
- smartAccountOptions: {
- chain: preferredChain,
- sponsorGas: smartAccountOptions.sponsorGas,
- factoryAddress: smartAccountOptions.accountFactoryAddress,
- },
- });
- }
- }
-
return [authAccount, options.chain || ethereum] as const;
}