diff --git a/.changeset/chilled-dancers-cheer.md b/.changeset/chilled-dancers-cheer.md new file mode 100644 index 00000000000..c87e5d44ed1 --- /dev/null +++ b/.changeset/chilled-dancers-cheer.md @@ -0,0 +1,5 @@ +--- +"thirdweb": minor +--- + +Enable ecosystem wallets in React Native diff --git a/packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts b/packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts index 64211ff30ea..511fa340cfb 100644 --- a/packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts +++ b/packages/thirdweb/src/react/core/hooks/wallets/useAutoConnect.ts @@ -1,6 +1,5 @@ import { useQuery } from "@tanstack/react-query"; import type { AsyncStorage } from "../../../../utils/storage/AsyncStorage.js"; -import { createWallet } from "../../../../wallets/create-wallet.js"; import { getUrlToken } from "../../../../wallets/in-app/web/lib/get-url-token.js"; import type { Wallet } from "../../../../wallets/interfaces/wallet.js"; import { @@ -8,6 +7,7 @@ import { getStoredActiveWalletId, getStoredConnectedWalletIds, } from "../../../../wallets/manager/index.js"; +import type { WalletId } from "../../../../wallets/wallet-types.js"; import { useConnectionManagerCtx } from "../../providers/connection-manager.js"; import { setLastAuthProvider } from "../../utils/storage.js"; import { timeoutPromise } from "../../utils/timeoutPromise.js"; @@ -18,6 +18,7 @@ import { useSetActiveWalletConnectionStatus } from "./useSetActiveWalletConnecti export function useAutoConnectCore( storage: AsyncStorage, props: AutoConnectProps & { wallets: Wallet[] }, + createWalletFn: (id: WalletId) => Wallet, getInstalledWallets?: () => Wallet[], ) { const manager = useConnectionManagerCtx("useAutoConnect"); @@ -73,7 +74,7 @@ export function useAutoConnectCore( const activeWallet = lastActiveWalletId && (availableWallets.find((w) => w.id === lastActiveWalletId) || - createWallet(lastActiveWalletId)); + createWalletFn(lastActiveWalletId)); if (activeWallet) { try { diff --git a/packages/thirdweb/src/react/native/hooks/wallets/useAutoConnect.ts b/packages/thirdweb/src/react/native/hooks/wallets/useAutoConnect.ts index 3a7504088c0..a887a0d7b2b 100644 --- a/packages/thirdweb/src/react/native/hooks/wallets/useAutoConnect.ts +++ b/packages/thirdweb/src/react/native/hooks/wallets/useAutoConnect.ts @@ -1,4 +1,5 @@ import { nativeLocalStorage } from "../../../../utils/storage/nativeStorage.js"; +import { createWallet } from "../../../../wallets/native/create-wallet.js"; import type { AutoConnectProps } from "../../../core/hooks/connection/types.js"; import { useAutoConnectCore } from "../../../core/hooks/wallets/useAutoConnect.js"; import { getDefaultWallets } from "../../wallets/defaultWallets.js"; @@ -23,8 +24,12 @@ import { getDefaultWallets } from "../../wallets/defaultWallets.js"; * @returns whether the auto connect was successful. */ export function useAutoConnect(props: AutoConnectProps) { - return useAutoConnectCore(nativeLocalStorage, { - ...props, - wallets: props.wallets || getDefaultWallets(props), - }); + return useAutoConnectCore( + nativeLocalStorage, + { + ...props, + wallets: props.wallets || getDefaultWallets(props), + }, + createWallet, + ); } diff --git a/packages/thirdweb/src/react/web/hooks/wallets/useAutoConnect.ts b/packages/thirdweb/src/react/web/hooks/wallets/useAutoConnect.ts index b3060b9d2a6..97c5432ae7d 100644 --- a/packages/thirdweb/src/react/web/hooks/wallets/useAutoConnect.ts +++ b/packages/thirdweb/src/react/web/hooks/wallets/useAutoConnect.ts @@ -33,6 +33,7 @@ export function useAutoConnect(props: AutoConnectProps) { ...props, wallets, }, + createWallet, () => { const specifiedWalletIds = new Set(wallets.map((x) => x.id)); diff --git a/packages/thirdweb/src/wallets/ecosystem/types.ts b/packages/thirdweb/src/wallets/ecosystem/types.ts index 3d91f50a758..4a482ee191d 100644 --- a/packages/thirdweb/src/wallets/ecosystem/types.ts +++ b/packages/thirdweb/src/wallets/ecosystem/types.ts @@ -1,10 +1,22 @@ import type { InAppWalletAutoConnectOptions, InAppWalletConnectionOptions, - InAppWalletCreationOptions, } from "../in-app/core/wallet/types.js"; -export type EcosystemWalletCreationOptions = InAppWalletCreationOptions & { +export type EcosystemWalletCreationOptions = { + auth?: { + /** + * Whether to display the social auth prompt in a popup or redirect + */ + mode?: "popup" | "redirect" | "window"; + /** + * Optional url to redirect to after authentication + */ + redirectUrl?: string; + }; + /** + * The partnerId of the ecosystem wallet to connect to + */ partnerId?: string; }; diff --git a/packages/thirdweb/src/wallets/in-app/core/authentication/authEndpoint.ts b/packages/thirdweb/src/wallets/in-app/core/authentication/authEndpoint.ts new file mode 100644 index 00000000000..2b438732d44 --- /dev/null +++ b/packages/thirdweb/src/wallets/in-app/core/authentication/authEndpoint.ts @@ -0,0 +1,42 @@ +import type { ThirdwebClient } from "../../../../client/client.js"; +import { getSessionHeaders } from "../../native/helpers/api/fetchers.js"; +import { ROUTE_AUTH_ENDPOINT_CALLBACK } from "../../native/helpers/constants.js"; +import { createErrorMessage } from "../../native/helpers/errors.js"; +import type { ClientScopedStorage } from "./client-scoped-storage.js"; +import type { AuthStoredTokenWithCookieReturnType } from "./types.js"; + +export async function authEndpoint(args: { + payload: string; + client: ThirdwebClient; + storage: ClientScopedStorage; +}): Promise { + const resp = await fetch(ROUTE_AUTH_ENDPOINT_CALLBACK, { + method: "POST", + headers: { + ...getSessionHeaders(), + }, + body: JSON.stringify({ + payload: args.payload, + developerClientId: args.client.clientId, + }), + }); + if (!resp.ok) { + const error = await resp.json(); + throw new Error( + `Custom auth endpoint authentication error: ${error.message}`, + ); + } + + try { + const { verifiedToken } = await resp.json(); + + return { storedToken: verifiedToken }; + } catch (e) { + throw new Error( + createErrorMessage( + "Malformed response from post auth_endpoint authentication", + e, + ), + ); + } +} diff --git a/packages/thirdweb/src/wallets/in-app/core/authentication/jwt.ts b/packages/thirdweb/src/wallets/in-app/core/authentication/jwt.ts new file mode 100644 index 00000000000..139c353b81e --- /dev/null +++ b/packages/thirdweb/src/wallets/in-app/core/authentication/jwt.ts @@ -0,0 +1,37 @@ +import type { ThirdwebClient } from "../../../../client/client.js"; +import { getSessionHeaders } from "../../native/helpers/api/fetchers.js"; +import { ROUTE_AUTH_JWT_CALLBACK } from "../../native/helpers/constants.js"; +import { createErrorMessage } from "../../native/helpers/errors.js"; +import type { ClientScopedStorage } from "./client-scoped-storage.js"; +import type { AuthStoredTokenWithCookieReturnType } from "./types.js"; + +export async function customJwt(args: { + jwt: string; + client: ThirdwebClient; + storage: ClientScopedStorage; +}): Promise { + const resp = await fetch(ROUTE_AUTH_JWT_CALLBACK, { + method: "POST", + headers: { + ...getSessionHeaders(), + }, + body: JSON.stringify({ + jwt: args.jwt, + developerClientId: args.client.clientId, + }), + }); + + if (!resp.ok) { + const error = await resp.json(); + throw new Error(`JWT authentication error: ${error.message}`); + } + + try { + const { verifiedToken } = await resp.json(); + return { storedToken: verifiedToken }; + } catch (e) { + throw new Error( + createErrorMessage("Malformed response from post jwt authentication", e), + ); + } +} diff --git a/packages/thirdweb/src/wallets/in-app/core/authentication/types.ts b/packages/thirdweb/src/wallets/in-app/core/authentication/types.ts index 965049cfaa1..73f5d31f33f 100644 --- a/packages/thirdweb/src/wallets/in-app/core/authentication/types.ts +++ b/packages/thirdweb/src/wallets/in-app/core/authentication/types.ts @@ -129,6 +129,11 @@ export type AuthAndWalletRpcReturnType = AuthStoredTokenWithCookieReturnType & { walletDetails: SetUpWalletRpcReturnType | WalletAddressObjectType; }; +export type AuthResultAndRecoveryCode = AuthStoredTokenWithCookieReturnType & { + deviceShareStored?: string; + encryptionKey?: string; +}; + export type AuthLoginReturnType = { user: InitializedUser }; // Auth Types @@ -156,7 +161,7 @@ type InitializedUser = { // In App Wallet Types -export type WalletAddressObjectType = { +type WalletAddressObjectType = { /** * User's wallet address */ diff --git a/packages/thirdweb/src/wallets/in-app/web/lib/enclave-wallet.ts b/packages/thirdweb/src/wallets/in-app/core/wallet/enclave-wallet.ts similarity index 88% rename from packages/thirdweb/src/wallets/in-app/web/lib/enclave-wallet.ts rename to packages/thirdweb/src/wallets/in-app/core/wallet/enclave-wallet.ts index 0b0b0b551ec..5feda5b9129 100644 --- a/packages/thirdweb/src/wallets/in-app/web/lib/enclave-wallet.ts +++ b/packages/thirdweb/src/wallets/in-app/core/wallet/enclave-wallet.ts @@ -11,18 +11,18 @@ import type { Account, SendTransactionOption, } from "../../../interfaces/wallet.js"; -import type { ClientScopedStorage } from "../../core/authentication/client-scoped-storage.js"; +import { getUserStatus } from "../../web/lib/actions/get-enclave-user-status.js"; +import { signMessage as signEnclaveMessage } from "../../web/lib/actions/sign-message.enclave.js"; +import { signTransaction as signEnclaveTransaction } from "../../web/lib/actions/sign-transaction.enclave.js"; +import { signTypedData as signEnclaveTypedData } from "../../web/lib/actions/sign-typed-data.enclave.js"; +import type { ClientScopedStorage } from "../authentication/client-scoped-storage.js"; import type { AuthDetails, + AuthResultAndRecoveryCode, GetUser, - WalletAddressObjectType, -} from "../../core/authentication/types.js"; -import type { Ecosystem } from "../../core/wallet/types.js"; -import { getUserStatus } from "./actions/get-enclave-user-status.js"; -import { signMessage as signEnclaveMessage } from "./actions/sign-message.enclave.js"; -import { signTransaction as signEnclaveTransaction } from "./actions/sign-transaction.enclave.js"; -import { signTypedData as signEnclaveTypedData } from "./actions/sign-typed-data.enclave.js"; -import type { IWebWallet, PostWalletSetup } from "./web-wallet.js"; +} from "../authentication/types.js"; +import type { Ecosystem } from "./types.js"; +import type { IWebWallet } from "./web-wallet.js"; export type UserStatus = { linkedAccounts: { @@ -73,12 +73,8 @@ export class EnclaveWallet implements IWebWallet { * @returns `{walletAddress: string }` The user's wallet details * @internal */ - async postWalletSetUp({ - walletAddress, - authToken, - }: PostWalletSetup): Promise { - await this.localStorage.saveAuthCookie(authToken); - return { walletAddress }; + async postWalletSetUp(authResult: AuthResultAndRecoveryCode): Promise { + await this.localStorage.saveAuthCookie(authResult.storedToken.cookieString); } /** diff --git a/packages/thirdweb/src/wallets/in-app/core/wallet/web-wallet.ts b/packages/thirdweb/src/wallets/in-app/core/wallet/web-wallet.ts new file mode 100644 index 00000000000..e2800979630 --- /dev/null +++ b/packages/thirdweb/src/wallets/in-app/core/wallet/web-wallet.ts @@ -0,0 +1,14 @@ +import type { Account } from "../../../interfaces/wallet.js"; +import type { + AuthResultAndRecoveryCode, + GetUser, +} from "../authentication/types.js"; + +/** + * + */ +export interface IWebWallet { + postWalletSetUp(authResult: AuthResultAndRecoveryCode): Promise; + getUserWalletStatus(): Promise; + getAccount(): Promise; +} diff --git a/packages/thirdweb/src/wallets/in-app/native/auth/native-auth.ts b/packages/thirdweb/src/wallets/in-app/native/auth/native-auth.ts index 274f648d519..832e31252b4 100644 --- a/packages/thirdweb/src/wallets/in-app/native/auth/native-auth.ts +++ b/packages/thirdweb/src/wallets/in-app/native/auth/native-auth.ts @@ -1,75 +1,27 @@ import * as WebBrowser from "expo-web-browser"; -import type { Chain } from "../../../../chains/types.js"; import type { ThirdwebClient } from "../../../../client/client.js"; -import { nativeLocalStorage } from "../../../../utils/storage/nativeStorage.js"; -import type { Wallet } from "../../../interfaces/wallet.js"; import type { ClientScopedStorage } from "../../core/authentication/client-scoped-storage.js"; import { getLoginUrl } from "../../core/authentication/getLoginPath.js"; -import { guestAuthenticate } from "../../core/authentication/guest.js"; -import { siweAuthenticate } from "../../core/authentication/siwe.js"; import type { AuthStoredTokenWithCookieReturnType, - MultiStepAuthArgsType, OAuthRedirectObject, } from "../../core/authentication/types.js"; import type { Ecosystem } from "../../core/wallet/types.js"; -import { verifyOtp } from "../../web/lib/auth/otp.js"; -import { - deleteAccount, - getSessionHeaders, - verifyClientId, -} from "../helpers/api/fetchers.js"; -import { postAuth, postAuthUserManaged } from "../helpers/auth/middleware.js"; -import { - ROUTE_AUTH_ENDPOINT_CALLBACK, - ROUTE_AUTH_JWT_CALLBACK, -} from "../helpers/constants.js"; +import { deleteAccount, verifyClientId } from "../helpers/api/fetchers.js"; import { createErrorMessage } from "../helpers/errors.js"; -export async function otpLogin( - options: MultiStepAuthArgsType & { - client: ThirdwebClient; - ecosystem?: Ecosystem; - storage: ClientScopedStorage; - }, -): Promise { - const { storedToken } = await verifyOtp(options); - try { - const toStoreToken: AuthStoredTokenWithCookieReturnType["storedToken"] = { - jwtToken: storedToken.jwtToken, - authDetails: storedToken.authDetails, - authProvider: storedToken.authProvider, - developerClientId: storedToken.developerClientId, - cookieString: storedToken.cookieString, - // we should always store the jwt cookie since there's no concept of cookie in react native - shouldStoreCookieString: true, - isNewUser: storedToken.isNewUser, - }; - - await postAuth({ - storedToken: toStoreToken, - client: options.client, - storage: options.storage, - }); - - return { storedToken }; - } catch (e) { - throw new Error( - createErrorMessage("Malformed response from post authentication", e), - ); - } -} - -export async function authenticate(args: { +export async function socialAuth(args: { auth: OAuthRedirectObject; client: ThirdwebClient; + ecosystem?: Ecosystem; }): Promise { - const { auth, client } = args; + const { auth, client, ecosystem } = args; const loginUrl = getLoginUrl({ authOption: auth.strategy, client, mode: "window", redirectUrl: auth.redirectUrl, + ecosystem, }); const result = await WebBrowser.openAuthSessionAsync( @@ -106,215 +58,6 @@ export async function authenticate(args: { return JSON.parse(authResult); } -export async function socialLogin(args: { - auth: OAuthRedirectObject; - client: ThirdwebClient; - storage: ClientScopedStorage; -}): Promise { - const { auth, client, storage } = args; - const { storedToken } = await authenticate({ auth, client }); - try { - const toStoreToken: AuthStoredTokenWithCookieReturnType["storedToken"] = { - jwtToken: storedToken.jwtToken, - authDetails: storedToken.authDetails, - authProvider: storedToken.authProvider, - developerClientId: storedToken.developerClientId, - cookieString: storedToken.cookieString, - // we should always store the jwt cookie since there's no concept of cookie in react native - shouldStoreCookieString: true, - isNewUser: storedToken.isNewUser, - }; - - await postAuth({ storedToken: toStoreToken, client, storage }); - - return { storedToken }; - } catch (e) { - throw new Error( - createErrorMessage("Malformed response from post authentication", e), - ); - } -} - -export async function siweLogin(args: { - client: ThirdwebClient; - wallet: Wallet; - chain: Chain; - ecosystem?: Ecosystem; - storage: ClientScopedStorage; -}): Promise { - const { client, ecosystem, wallet, chain, storage } = args; - const { storedToken } = await siweAuthenticate({ - client, - ecosystem, - wallet, - chain, - }); - try { - const toStoreToken: AuthStoredTokenWithCookieReturnType["storedToken"] = { - jwtToken: storedToken.jwtToken, - authDetails: storedToken.authDetails, - authProvider: storedToken.authProvider, - developerClientId: storedToken.developerClientId, - cookieString: storedToken.cookieString, - // we should always store the jwt cookie since there's no concept of cookie in react native - shouldStoreCookieString: true, - isNewUser: storedToken.isNewUser, - }; - - await postAuth({ storedToken: toStoreToken, client, storage }); - - return { storedToken }; - } catch (e) { - throw new Error( - createErrorMessage("Malformed response from post authentication", e), - ); - } -} - -export async function guestLogin(args: { - client: ThirdwebClient; - ecosystem?: Ecosystem; - storage: ClientScopedStorage; -}): Promise { - const { client, ecosystem, storage } = args; - const { storedToken } = await guestAuthenticate({ - client, - ecosystem, - storage: nativeLocalStorage, - }); - try { - const toStoreToken: AuthStoredTokenWithCookieReturnType["storedToken"] = { - jwtToken: storedToken.jwtToken, - authDetails: storedToken.authDetails, - authProvider: storedToken.authProvider, - developerClientId: storedToken.developerClientId, - cookieString: storedToken.cookieString, - // we should always store the jwt cookie since there's no concept of cookie in react native - shouldStoreCookieString: true, - isNewUser: storedToken.isNewUser, - }; - - await postAuth({ storedToken: toStoreToken, client, storage }); - - return { storedToken }; - } catch (e) { - throw new Error( - createErrorMessage("Malformed response from post authentication", e), - ); - } -} - -export async function customJwt(args: { - authOptions: { jwt: string; password: string }; - client: ThirdwebClient; - storage: ClientScopedStorage; -}): Promise { - const { jwt, password } = args.authOptions; - - const resp = await fetch(ROUTE_AUTH_JWT_CALLBACK, { - method: "POST", - headers: { - ...getSessionHeaders(), - }, - body: JSON.stringify({ - jwt: jwt, - developerClientId: args.client.clientId, - }), - }); - if (!resp.ok) { - const error = await resp.json(); - throw new Error(`JWT authentication error: ${error.message}`); - } - - try { - const { verifiedToken, verifiedTokenJwtString } = await resp.json(); - - const toStoreToken: AuthStoredTokenWithCookieReturnType["storedToken"] = { - jwtToken: verifiedToken.jwtToken, - authProvider: verifiedToken.authProvider, - authDetails: { - ...verifiedToken.authDetails, - email: verifiedToken.authDetails.email, - }, - developerClientId: verifiedToken.developerClientId, - cookieString: verifiedTokenJwtString, - shouldStoreCookieString: true, - isNewUser: verifiedToken.isNewUser, - }; - - await postAuthUserManaged({ - storedToken: toStoreToken, - client: args.client, - password, - storage: args.storage, - }); - - return { storedToken: verifiedToken }; - } catch (e) { - throw new Error( - createErrorMessage("Malformed response from post jwt authentication", e), - ); - } -} - -export async function authEndpoint(args: { - authOptions: { payload: string; encryptionKey: string }; - client: ThirdwebClient; - storage: ClientScopedStorage; -}): Promise { - const { payload, encryptionKey } = args.authOptions; - - const resp = await fetch(ROUTE_AUTH_ENDPOINT_CALLBACK, { - method: "POST", - headers: { - ...getSessionHeaders(), - }, - body: JSON.stringify({ - payload: payload, - developerClientId: args.client.clientId, - }), - }); - if (!resp.ok) { - const error = await resp.json(); - throw new Error( - `Custom auth endpoint authentication error: ${error.message}`, - ); - } - - try { - const { verifiedToken, verifiedTokenJwtString } = await resp.json(); - - const toStoreToken: AuthStoredTokenWithCookieReturnType["storedToken"] = { - jwtToken: verifiedToken.jwtToken, - authProvider: verifiedToken.authProvider, - authDetails: { - ...verifiedToken.authDetails, - email: verifiedToken.authDetails.email, - }, - developerClientId: verifiedToken.developerClientId, - cookieString: verifiedTokenJwtString, - shouldStoreCookieString: true, - isNewUser: verifiedToken.isNewUser, - }; - - await postAuthUserManaged({ - storedToken: toStoreToken, - client: args.client, - password: encryptionKey, - storage: args.storage, - }); - - return { storedToken: verifiedToken }; - } catch (e) { - throw new Error( - createErrorMessage( - "Malformed response from post auth_endpoint authentication", - e, - ), - ); - } -} - export async function deleteActiveAccount(options: { client: ThirdwebClient; storage: ClientScopedStorage; diff --git a/packages/thirdweb/src/wallets/in-app/native/ecosystem.ts b/packages/thirdweb/src/wallets/in-app/native/ecosystem.ts index 552432c5ddd..836d4eba141 100644 --- a/packages/thirdweb/src/wallets/in-app/native/ecosystem.ts +++ b/packages/thirdweb/src/wallets/in-app/native/ecosystem.ts @@ -71,13 +71,18 @@ export function ecosystemWallet( }; return createInAppWallet({ ecosystem, - createOptions, + createOptions: { + auth: { + ...createOptions?.auth, + options: [], // controlled by ecosystem + }, + }, connectorFactory: async (client: ThirdwebClient) => { const { InAppNativeConnector } = await import("./native-connector.js"); return new InAppNativeConnector({ client, - passkeyDomain: createOptions?.auth?.passkeyDomain, ecosystem, + // TODO (enclave): passkeyDomain for ecosystem wallets }); }, }) as Wallet; diff --git a/packages/thirdweb/src/wallets/in-app/native/helpers/auth/middleware.ts b/packages/thirdweb/src/wallets/in-app/native/helpers/auth/middleware.ts index e3073fe53df..a6979f136d6 100644 --- a/packages/thirdweb/src/wallets/in-app/native/helpers/auth/middleware.ts +++ b/packages/thirdweb/src/wallets/in-app/native/helpers/auth/middleware.ts @@ -13,12 +13,12 @@ import { setUpShareForNewDevice } from "../wallet/retrieval.js"; export async function postAuth({ storedToken, client, - recoveryCode, + encryptionKey, storage, }: { storedToken: AuthStoredTokenWithCookieReturnType["storedToken"]; client: ThirdwebClient; - recoveryCode?: string; + encryptionKey?: string; storage: ClientScopedStorage; }) { if (storedToken.shouldStoreCookieString) { @@ -40,7 +40,7 @@ export async function postAuth({ const _recoveryCode = await getRecoveryCode({ storedToken, client, - recoveryCode, + recoveryCode: encryptionKey, storage, }); if (!_recoveryCode) { @@ -59,7 +59,7 @@ export async function postAuth({ const _recoveryCode = await getRecoveryCode({ storedToken, client, - recoveryCode, + recoveryCode: encryptionKey, storage, }); if (!_recoveryCode) { @@ -81,63 +81,6 @@ export async function postAuth({ return storedToken; } -export async function postAuthUserManaged(args: { - storedToken: AuthStoredTokenWithCookieReturnType["storedToken"]; - client: ThirdwebClient; - password: string; - storage: ClientScopedStorage; -}) { - const { storedToken, client, password, storage } = args; - const _password = await getRecoveryCode({ - storedToken, - client, - recoveryCode: password, - storage, - }); - - if (storedToken.shouldStoreCookieString) { - await storage.saveAuthCookie(storedToken.cookieString); - } - - await setWallerUserDetails({ - clientId: client.clientId, - userId: storedToken.authDetails.userWalletId, - email: - "email" in storedToken.authDetails - ? storedToken.authDetails.email - : "phoneNumber" in storedToken.authDetails - ? storedToken.authDetails.phoneNumber - : undefined, - }); - - if (storedToken.isNewUser) { - await setUpNewUserWallet({ - client, - recoveryCode: _password, - storage, - }); - } else { - try { - // existing device share - await getDeviceShare(client.clientId); - } catch { - // trying to recreate device share from recovery code to derive wallet - try { - await setUpShareForNewDevice({ - client, - recoveryCode: _password, - storage, - }); - } catch (error) { - console.error("Error setting up wallet on device", error); - throw error; - } - } - } - - return storedToken; -} - async function getRecoveryCode(args: { storedToken: AuthStoredTokenWithCookieReturnType["storedToken"]; client: ThirdwebClient; diff --git a/packages/thirdweb/src/wallets/in-app/native/helpers/wallet/sharded-wallet.ts b/packages/thirdweb/src/wallets/in-app/native/helpers/wallet/sharded-wallet.ts new file mode 100644 index 00000000000..e0f08d22eae --- /dev/null +++ b/packages/thirdweb/src/wallets/in-app/native/helpers/wallet/sharded-wallet.ts @@ -0,0 +1,72 @@ +import type { ThirdwebClient } from "../../../../../client/client.js"; +import type { Account } from "../../../../interfaces/wallet.js"; +import type { ClientScopedStorage } from "../../../core/authentication/client-scoped-storage.js"; +import type { + AuthResultAndRecoveryCode, + GetUser, +} from "../../../core/authentication/types.js"; +import type { IWebWallet } from "../../../core/wallet/web-wallet.js"; +import { fetchUserDetails } from "../api/fetchers.js"; +import { postAuth } from "../auth/middleware.js"; +import { getWalletUserDetails } from "../storage/local.js"; +import { getExistingUserAccount } from "./retrieval.js"; + +export class ShardedWallet implements IWebWallet { + private client: ThirdwebClient; + private storage: ClientScopedStorage; + + constructor(args: { + client: ThirdwebClient; + storage: ClientScopedStorage; + }) { + this.client = args.client; + this.storage = args.storage; + } + + async postWalletSetUp(authResult: AuthResultAndRecoveryCode): Promise { + await postAuth({ + storedToken: authResult.storedToken, + client: this.client, + storage: this.storage, + encryptionKey: authResult.encryptionKey, + }); + } + + async getUserWalletStatus(): Promise { + const localData = await getWalletUserDetails(this.client.clientId); + const userStatus = await fetchUserDetails({ + client: this.client, + email: localData?.email, + storage: this.storage, + }); + if (userStatus.status === "Logged In, Wallet Initialized") { + return { + status: userStatus.status, + authDetails: userStatus.storedToken.authDetails, + walletAddress: userStatus.walletAddress, + account: await this.getAccount(), + }; + } + if (userStatus.status === "Logged In, New Device") { + return { + status: "Logged In, New Device", + authDetails: userStatus.storedToken.authDetails, + walletAddress: userStatus.walletAddress, + }; + } + if (userStatus.status === "Logged In, Wallet Uninitialized") { + return { + status: "Logged In, Wallet Uninitialized", + authDetails: userStatus.storedToken.authDetails, + }; + } + // Logged out + return { status: "Logged Out" }; + } + getAccount(): Promise { + return getExistingUserAccount({ + client: this.client, + storage: this.storage, + }); + } +} diff --git a/packages/thirdweb/src/wallets/in-app/native/native-connector.ts b/packages/thirdweb/src/wallets/in-app/native/native-connector.ts index 9b1403bbfae..402985cefbb 100644 --- a/packages/thirdweb/src/wallets/in-app/native/native-connector.ts +++ b/packages/thirdweb/src/wallets/in-app/native/native-connector.ts @@ -1,10 +1,11 @@ -import type { Chain } from "../../../chains/types.js"; import type { ThirdwebClient } from "../../../client/client.js"; import { stringify } from "../../../utils/json.js"; import { nativeLocalStorage } from "../../../utils/storage/nativeStorage.js"; -import type { Account, Wallet } from "../../interfaces/wallet.js"; +import type { Account } from "../../interfaces/wallet.js"; +import { authEndpoint } from "../core/authentication/authEndpoint.js"; import { ClientScopedStorage } from "../core/authentication/client-scoped-storage.js"; import { guestAuthenticate } from "../core/authentication/guest.js"; +import { customJwt } from "../core/authentication/jwt.js"; import { getLinkedProfilesInternal, linkAccount, @@ -22,26 +23,17 @@ import type { LogoutReturnType, MultiStepAuthArgsType, MultiStepAuthProviderType, - OAuthRedirectObject, + SingleStepAuthArgsType, } from "../core/authentication/types.js"; import type { InAppConnector } from "../core/interfaces/connector.js"; +import { EnclaveWallet } from "../core/wallet/enclave-wallet.js"; import type { Ecosystem } from "../core/wallet/types.js"; +import type { IWebWallet } from "../core/wallet/web-wallet.js"; +import { getUserStatus } from "../web/lib/actions/get-enclave-user-status.js"; import { sendOtp, verifyOtp } from "../web/lib/auth/otp.js"; -import { - authEndpoint, - authenticate, - customJwt, - deleteActiveAccount, - guestLogin, - otpLogin, - siweLogin, - socialLogin, -} from "./auth/native-auth.js"; -import { fetchUserDetails } from "./helpers/api/fetchers.js"; +import { deleteActiveAccount, socialAuth } from "./auth/native-auth.js"; import { logoutUser } from "./helpers/auth/logout.js"; -import { postAuth } from "./helpers/auth/middleware.js"; -import { getWalletUserDetails } from "./helpers/storage/local.js"; -import { getExistingUserAccount } from "./helpers/wallet/retrieval.js"; +import { ShardedWallet } from "./helpers/wallet/sharded-wallet.js"; type NativeConnectorOptions = { client: ThirdwebClient; @@ -54,6 +46,7 @@ export class InAppNativeConnector implements InAppConnector { private ecosystem?: Ecosystem; private passkeyDomain?: string; private localStorage: ClientScopedStorage; + private wallet?: IWebWallet; constructor(options: NativeConnectorOptions) { this.client = options.client; @@ -66,42 +59,57 @@ export class InAppNativeConnector implements InAppConnector { }); } - async getUser(): Promise { - const localData = await getWalletUserDetails(this.client.clientId); - const userStatus = await fetchUserDetails({ + async initializeWallet(authToken?: string) { + const storedAuthToken = await this.localStorage.getAuthCookie(); + if (!authToken && storedAuthToken === null) { + throw new Error( + "No auth token provided and no stored auth token found to initialize the wallet", + ); + } + const user = await getUserStatus({ + authToken: authToken || (storedAuthToken as string), client: this.client, - email: localData?.email, - storage: this.localStorage, + ecosystem: this.ecosystem, }); - if (userStatus.status === "Logged In, Wallet Initialized") { - return { - status: userStatus.status, - authDetails: userStatus.storedToken.authDetails, - walletAddress: userStatus.walletAddress, - account: await this.getAccount(), - }; + if (!user) { + throw new Error("Cannot initialize wallet, no user logged in"); } - if (userStatus.status === "Logged In, New Device") { - return { - status: "Logged In, New Device", - authDetails: userStatus.storedToken.authDetails, - walletAddress: userStatus.walletAddress, - }; + const wallet = user.wallets[0]; + // TODO (enclaves): Migration to enclave wallet if sharded + if (wallet && wallet.type === "enclave") { + this.wallet = new EnclaveWallet({ + client: this.client, + ecosystem: this.ecosystem, + address: wallet.address, + storage: this.localStorage, + }); + } else { + this.wallet = new ShardedWallet({ + client: this.client, + storage: this.localStorage, + }); + } + } + + async getUser(): Promise { + if (!this.wallet) { + const localAuthToken = await this.localStorage.getAuthCookie(); + if (!localAuthToken) { + return { status: "Logged Out" }; + } + await this.initializeWallet(localAuthToken); } - if (userStatus.status === "Logged In, Wallet Uninitialized") { - return { - status: "Logged In, Wallet Uninitialized", - authDetails: userStatus.storedToken.authDetails, - }; + if (!this.wallet) { + throw new Error("Wallet not initialized"); } - // Logged out - return { status: "Logged Out" }; + return this.wallet.getUserWalletStatus(); } + getAccount(): Promise { - return getExistingUserAccount({ - client: this.client, - storage: this.localStorage, - }); + if (!this.wallet) { + throw new Error("Wallet not initialized"); + } + return this.wallet.getAccount(); } preAuthenticate(args: MultiStepAuthProviderType): Promise { @@ -144,102 +152,65 @@ export class InAppNativeConnector implements InAppConnector { const ExpoLinking = require("expo-linking"); const redirectUrl = params.redirectUrl || (ExpoLinking.createURL("") as string); - return authenticate({ + return socialAuth({ auth: { strategy, redirectUrl }, client: this.client, + ecosystem: this.ecosystem, }); } case "passkey": return this.passkeyAuth(params); - default: - throw new Error(`Unsupported authentication type: ${strategy}`); - } - } - - async connect(params: AuthArgsType): Promise { - const strategy = params.strategy; - switch (strategy) { - case "email": { - return await this.validateOtp({ - email: params.email, - verificationCode: params.verificationCode, - strategy: "email", - client: this.client, - ecosystem: params.ecosystem, - }); - } - case "phone": { - return await this.validateOtp({ - phoneNumber: params.phoneNumber, - verificationCode: params.verificationCode, - strategy: "phone", - client: this.client, - ecosystem: params.ecosystem, - }); - } - case "google": - case "facebook": - case "discord": - case "line": - case "x": - case "farcaster": - case "telegram": - case "coinbase": - case "apple": { - const ExpoLinking = require("expo-linking"); - const redirectUrl = - params.redirectUrl || (ExpoLinking.createURL("") as string); // Will default to the app scheme - return this.socialLogin({ - strategy, - redirectUrl, - }); - } - case "guest": { - return this.guestLogin({ - ecosystem: params.ecosystem, - }); - } - case "wallet": { - return this.siweLogin({ - wallet: params.wallet, - chain: params.chain, - }); - } - case "jwt": { - return this.customJwt({ + case "jwt": + return customJwt({ jwt: params.jwt, - password: params.encryptionKey, + client: this.client, + storage: this.localStorage, }); - } - case "auth_endpoint": { - return this.authEndpoint({ + case "auth_endpoint": + return authEndpoint({ payload: params.payload, - encryptionKey: params.encryptionKey, + client: this.client, + storage: this.localStorage, }); - } - case "passkey": { - const authToken = await this.passkeyAuth(params); - const account = await this.getAccount(); - return { - user: { - status: "Logged In, Wallet Initialized", - account, - authDetails: authToken.storedToken.authDetails, - walletAddress: account.address, - }, - }; - } - case "iframe": { - throw new Error("iframe_email_verification is not supported in native"); - } - case "iframe_email_verification": { - throw new Error("iframe_email_verification is not supported in native"); - } default: - assertUnreachable(strategy); + throw new Error(`Unsupported authentication type: ${strategy}`); } } + async connect( + params: MultiStepAuthArgsType | SingleStepAuthArgsType, + ): Promise { + const authResult = await this.authenticate({ + ...params, + client: this.client, + ecosystem: this.ecosystem, + }); + await this.initializeWallet(authResult.storedToken.cookieString); + if (!this.wallet) { + throw new Error("Wallet not initialized"); + } + const encryptionKey = + params.strategy === "jwt" + ? params.encryptionKey + : params.strategy === "auth_endpoint" + ? params.encryptionKey + : undefined; + + await this.wallet.postWalletSetUp({ + ...authResult, + encryptionKey, + }); + const account = await this.getAccount(); + return { + user: { + status: "Logged In, Wallet Initialized", + account, + authDetails: authResult.storedToken.authDetails, + walletAddress: account.address, + }, + }; + } + private async passkeyAuth(args: { type: "sign-up" | "sign-in"; passkeyName?: string; @@ -284,23 +255,6 @@ export class InAppNativeConnector implements InAppConnector { }); } - const toStoreToken: AuthStoredTokenWithCookieReturnType["storedToken"] = { - jwtToken: authToken.storedToken.jwtToken, - authDetails: authToken.storedToken.authDetails, - authProvider: authToken.storedToken.authProvider, - developerClientId: authToken.storedToken.developerClientId, - cookieString: authToken.storedToken.cookieString, - // we should always store the jwt cookie since there's no concept of cookie in react native - shouldStoreCookieString: true, - isNewUser: authToken.storedToken.isNewUser, - }; - - await postAuth({ - storedToken: toStoreToken, - client, - storage, - }); - return authToken; } catch (error) { console.error( @@ -313,35 +267,6 @@ export class InAppNativeConnector implements InAppConnector { } } - private async validateOtp( - options: MultiStepAuthArgsType & { - client: ThirdwebClient; - ecosystem?: Ecosystem; - }, - ): Promise { - try { - const { storedToken } = await otpLogin({ - ...options, - storage: this.localStorage, - }); - const account = await this.getAccount(); - return { - user: { - status: "Logged In, Wallet Initialized", - account, - authDetails: storedToken.authDetails, - walletAddress: account.address, - }, - }; - } catch (error) { - console.error(`Error while validating OTP: ${error}`); - if (error instanceof Error) { - throw new Error(`Error while validating otp: ${error.message}`); - } - throw new Error("An unknown error occurred while validating otp"); - } - } - // TODO (rn) expose in the interface async deleteActiveAccount() { return deleteActiveAccount({ @@ -350,145 +275,6 @@ export class InAppNativeConnector implements InAppConnector { }); } - private async socialLogin( - auth: OAuthRedirectObject, - ): Promise { - try { - const { storedToken } = await socialLogin({ - auth, - client: this.client, - storage: this.localStorage, - }); - const account = await this.getAccount(); - return { - user: { - status: "Logged In, Wallet Initialized", - account, - authDetails: storedToken.authDetails, - walletAddress: account.address, - }, - }; - } catch (error) { - console.error(`Error while signing in with: ${auth}. ${error}`); - if (error instanceof Error) { - throw new Error(`Error signing in with ${auth}: ${error.message}`); - } - throw new Error(`An unknown error occurred signing in with ${auth}`); - } - } - - private async siweLogin(options: { - wallet: Wallet; - chain: Chain; - }): Promise { - try { - const { storedToken } = await siweLogin({ - client: this.client, - wallet: options.wallet, - chain: options.chain, - ecosystem: this.ecosystem, - storage: this.localStorage, - }); - const account = await this.getAccount(); - return { - user: { - status: "Logged In, Wallet Initialized", - account, - authDetails: storedToken.authDetails, - walletAddress: account.address, - }, - }; - } catch (error) { - console.error( - `Error while signing in with: ${options.wallet.id}. ${error}`, - ); - if (error instanceof Error) { - throw new Error( - `Error signing in with ${options.wallet.id}: ${error.message}`, - ); - } - throw new Error( - `An unknown error occurred signing in with ${options.wallet.id}`, - ); - } - } - - private async guestLogin(options: { - ecosystem?: Ecosystem; - }): Promise { - try { - const { storedToken } = await guestLogin({ - client: this.client, - ecosystem: options.ecosystem, - storage: this.localStorage, - }); - const account = await this.getAccount(); - return { - user: { - status: "Logged In, Wallet Initialized", - account, - authDetails: storedToken.authDetails, - walletAddress: account.address, - }, - }; - } catch (error) { - if (error instanceof Error) { - throw new Error(`Error generating guest account: ${error.message}`); - } - throw new Error("An unknown error occurred generating guest account"); - } - } - - private async customJwt(authOptions: { - jwt: string; - password: string; - }): Promise { - try { - const { storedToken } = await customJwt({ - authOptions, - client: this.client, - storage: this.localStorage, - }); - const account = await this.getAccount(); - return { - user: { - status: "Logged In, Wallet Initialized", - account, - authDetails: storedToken.authDetails, - walletAddress: account.address, - }, - }; - } catch (error) { - console.error(`Error while verifying auth: ${error}`); - throw error; - } - } - - private async authEndpoint(authOptions: { - payload: string; - encryptionKey: string; - }): Promise { - try { - const { storedToken } = await authEndpoint({ - authOptions, - client: this.client, - storage: this.localStorage, - }); - const account = await this.getAccount(); - return { - user: { - status: "Logged In, Wallet Initialized", - account, - authDetails: storedToken.authDetails, - walletAddress: account.address, - }, - }; - } catch (error) { - console.error(`Error while verifying auth_endpoint auth: ${error}`); - throw error; - } - } - logout(): Promise { return logoutUser({ client: this.client, @@ -513,7 +299,3 @@ export class InAppNativeConnector implements InAppConnector { }); } } - -function assertUnreachable(x: never): never { - throw new Error(`Invalid param: ${x}`); -} diff --git a/packages/thirdweb/src/wallets/in-app/web/ecosystem.ts b/packages/thirdweb/src/wallets/in-app/web/ecosystem.ts index b5d6d931af8..8bbfda79d77 100644 --- a/packages/thirdweb/src/wallets/in-app/web/ecosystem.ts +++ b/packages/thirdweb/src/wallets/in-app/web/ecosystem.ts @@ -71,7 +71,12 @@ export function ecosystemWallet( }; return createInAppWallet({ ecosystem, - createOptions, + createOptions: { + auth: { + ...createOptions?.auth, + options: [], // controlled by ecosystem + }, + }, connectorFactory: async (client: ThirdwebClient) => { const { InAppWebConnector } = await import("./lib/web-connector.js"); return new InAppWebConnector({ diff --git a/packages/thirdweb/src/wallets/in-app/web/lib/actions/get-enclave-user-status.ts b/packages/thirdweb/src/wallets/in-app/web/lib/actions/get-enclave-user-status.ts index 2d336169856..b12c02625d9 100644 --- a/packages/thirdweb/src/wallets/in-app/web/lib/actions/get-enclave-user-status.ts +++ b/packages/thirdweb/src/wallets/in-app/web/lib/actions/get-enclave-user-status.ts @@ -1,8 +1,8 @@ import type { ThirdwebClient } from "../../../../../client/client.js"; import { getThirdwebBaseUrl } from "../../../../../utils/domains.js"; import { getClientFetch } from "../../../../../utils/fetch.js"; +import type { UserStatus } from "../../../core/wallet/enclave-wallet.js"; import type { Ecosystem } from "../../../core/wallet/types.js"; -import type { UserStatus } from "../../lib/enclave-wallet.js"; /** * Gets the user's status from the backend. @@ -32,11 +32,13 @@ export async function getUserStatus({ ); if (!response.ok) { + console.log("response", response.status); if (response.status === 401) { // 401 response indicates there is no user logged in, so we return undefined return undefined; } const result = await response.json(); + console.log("result", result); throw new Error(`Failed to get user status: ${result.error}`); } diff --git a/packages/thirdweb/src/wallets/in-app/web/lib/iframe-wallet.ts b/packages/thirdweb/src/wallets/in-app/web/lib/iframe-wallet.ts index 4d0d1324211..8e7a72e48d8 100644 --- a/packages/thirdweb/src/wallets/in-app/web/lib/iframe-wallet.ts +++ b/packages/thirdweb/src/wallets/in-app/web/lib/iframe-wallet.ts @@ -15,11 +15,12 @@ import type { } from "../../../interfaces/wallet.js"; import type { ClientScopedStorage } from "../../core/authentication/client-scoped-storage.js"; import type { + AuthResultAndRecoveryCode, GetUser, GetUserWalletStatusRpcReturnType, - WalletAddressObjectType, } from "../../core/authentication/types.js"; import type { Ecosystem } from "../../core/wallet/types.js"; +import type { IWebWallet } from "../../core/wallet/web-wallet.js"; import type { ClientIdWithQuerierType, GetAddressReturnType, @@ -28,7 +29,6 @@ import type { SignedTypedDataReturnType, } from "../types.js"; import type { InAppWalletIframeCommunicator } from "../utils/iFrameCommunication/InAppWalletIframeCommunicator.js"; -import type { IWebWallet, PostWalletSetup } from "./web-wallet.js"; type WalletManagementTypes = { createWallet: undefined; @@ -102,19 +102,13 @@ export class IFrameWallet implements IWebWallet { * @returns `{walletAddress : string }` The user's wallet details * @internal */ - async postWalletSetUp( - props: PostWalletSetup, - ): Promise { - if ("isIframeStorageEnabled" in props) { - if (!props.isIframeStorageEnabled) { - await this.localStorage.saveDeviceShare( - props.deviceShareStored, - props.walletUserId, - ); - } - return { walletAddress: props.walletAddress }; + async postWalletSetUp(authResult: AuthResultAndRecoveryCode): Promise { + if (authResult.deviceShareStored) { + await this.localStorage.saveDeviceShare( + authResult.deviceShareStored, + authResult.storedToken.authDetails.userWalletId, + ); } - throw new Error("Invalid postWalletSetUp props"); } /** diff --git a/packages/thirdweb/src/wallets/in-app/web/lib/web-connector.ts b/packages/thirdweb/src/wallets/in-app/web/lib/web-connector.ts index fcd7f81a0c1..3c6ee8f6bf3 100644 --- a/packages/thirdweb/src/wallets/in-app/web/lib/web-connector.ts +++ b/packages/thirdweb/src/wallets/in-app/web/lib/web-connector.ts @@ -25,14 +25,15 @@ import type { SingleStepAuthArgsType, } from "../../core/authentication/types.js"; import type { InAppConnector } from "../../core/interfaces/connector.js"; +import { EnclaveWallet } from "../../core/wallet/enclave-wallet.js"; import type { Ecosystem } from "../../core/wallet/types.js"; +import type { IWebWallet } from "../../core/wallet/web-wallet.js"; import { getUserStatus } from "../lib/actions/get-enclave-user-status.js"; import type { InAppWalletConstructorType } from "../types.js"; import { InAppWalletIframeCommunicator } from "../utils/iFrameCommunication/InAppWalletIframeCommunicator.js"; import { Auth, type AuthQuerierTypes } from "./auth/iframe-auth.js"; import { loginWithOauth, loginWithOauthRedirect } from "./auth/oauth.js"; import { sendOtp, verifyOtp } from "./auth/otp.js"; -import { EnclaveWallet } from "./enclave-wallet.js"; import { IFrameWallet } from "./iframe-wallet.js"; /** @@ -44,7 +45,7 @@ export class InAppWebConnector implements InAppConnector { private querier: InAppWalletIframeCommunicator; private localStorage: ClientScopedStorage; - private wallet?: EnclaveWallet | IFrameWallet; + private wallet?: IWebWallet; /** * Used to manage the Auth state of the user. */ @@ -120,10 +121,14 @@ export class InAppWebConnector implements InAppConnector { throw new Error("Failed to initialize wallet"); } + const deviceShareStored = + "deviceShareStored" in authResult.walletDetails + ? authResult.walletDetails.deviceShareStored + : undefined; + await this.wallet.postWalletSetUp({ - ...authResult.walletDetails, - authToken: authResult.storedToken.cookieString, - walletUserId: authResult.storedToken.authDetails.userWalletId, + storedToken: authResult.storedToken, + deviceShareStored, }); if (authResult.storedToken.authDetails.walletType !== "enclave") { @@ -222,11 +227,11 @@ export class InAppWebConnector implements InAppConnector { async getUser(): Promise { // If we don't have a wallet yet we'll create one if (!this.wallet) { - const maybeAuthToken = await this.localStorage.getAuthCookie(); - if (!maybeAuthToken) { + const localAuthToken = await this.localStorage.getAuthCookie(); + if (!localAuthToken) { return { status: "Logged Out" }; } - await this.initializeWallet(maybeAuthToken); + await this.initializeWallet(localAuthToken); } if (!this.wallet) { throw new Error("Wallet not initialized"); diff --git a/packages/thirdweb/src/wallets/in-app/web/lib/web-wallet.ts b/packages/thirdweb/src/wallets/in-app/web/lib/web-wallet.ts deleted file mode 100644 index 9087f625de1..00000000000 --- a/packages/thirdweb/src/wallets/in-app/web/lib/web-wallet.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { Account } from "../../../interfaces/wallet.js"; -import type { - GetUser, - SetUpWalletRpcReturnType, - WalletAddressObjectType, -} from "../../core/authentication/types.js"; - -export type PostWalletSetup = ( - | SetUpWalletRpcReturnType - | WalletAddressObjectType -) & { - walletUserId: string; - authToken: string; -}; - -/** - * - */ -export interface IWebWallet { - postWalletSetUp(args: PostWalletSetup): Promise; - getUserWalletStatus(): Promise; - getAccount(): Promise; -} diff --git a/packages/thirdweb/src/wallets/native/create-wallet.ts b/packages/thirdweb/src/wallets/native/create-wallet.ts index 92c2826fc99..1fa82b6cdd8 100644 --- a/packages/thirdweb/src/wallets/native/create-wallet.ts +++ b/packages/thirdweb/src/wallets/native/create-wallet.ts @@ -10,6 +10,7 @@ import { coinbaseWalletSDK } from "../coinbase/coinbase-wallet.js"; import { getCoinbaseMobileProvider } from "../coinbase/coinbaseMobileSDK.js"; import { COINBASE } from "../constants.js"; import { isEcosystemWallet } from "../ecosystem/is-ecosystem-wallet.js"; +import { ecosystemWallet } from "../in-app/native/ecosystem.js"; import { inAppWallet } from "../in-app/native/in-app.js"; import type { Account, Wallet } from "../interfaces/wallet.js"; import { smartWallet } from "../smart/smart-wallet.js"; @@ -17,6 +18,7 @@ import type { WCConnectOptions } from "../wallet-connect/types.js"; import { createWalletEmitter } from "../wallet-emitter.js"; import type { CreateWalletArgs, + EcosystemWalletId, WalletAutoConnectionOption, WalletId, } from "../wallet-types.js"; @@ -66,9 +68,9 @@ export function createWallet( * ECOSYSTEM WALLETS */ case isEcosystemWallet(id): { - throw new Error( - "Ecosystem wallets are not yet supported in the React Native SDK", - ); + return ecosystemWallet( + ...(args as CreateWalletArgs), + ) as Wallet; } /**