[SDK] Feature: Ecosystem account linking (#4733)
## Problem solved
Short description of the bug fixed or feature added
<!-- start pr-codex -->
---
## PR-Codex overview
This PR enables account linking for ecosystem wallets.
### Detailed summary
- Added support for ecosystem wallets in account linking functionality
- Updated conditions for multi-auth support in various components
- Imported and utilized `isEcosystemWallet` function in multiple files
- Modified functions to handle both in-app and ecosystem wallets in account linking operations
> ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}`
<!-- end pr-codex -->
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"thirdweb": patch
|
||||
---
|
||||
|
||||
Enable account linking for ecosystem wallets
|
||||
@@ -1,4 +1,5 @@
|
||||
import { type UseQueryResult, useQuery } from "@tanstack/react-query";
|
||||
import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
|
||||
import type { Profile } from "../../../../wallets/in-app/core/authentication/types.js";
|
||||
import { getProfiles } from "../../../../wallets/in-app/core/wallet/profiles.js";
|
||||
import type { Wallet } from "../../../../wallets/interfaces/wallet.js";
|
||||
@@ -28,7 +29,7 @@ export function useProfiles(): UseQueryResult<Profile[]> {
|
||||
|
||||
return useQuery({
|
||||
queryKey: ["profiles", wallet?.id],
|
||||
enabled: !!wallet && wallet.id === "inApp",
|
||||
enabled: !!wallet && (wallet.id === "inApp" || isEcosystemWallet(wallet)),
|
||||
queryFn: async () => {
|
||||
return getProfiles(wallet as Wallet<"inApp">);
|
||||
},
|
||||
|
||||
@@ -3,10 +3,13 @@ import { CrossCircledIcon } from "@radix-ui/react-icons";
|
||||
import { useQueryClient } from "@tanstack/react-query";
|
||||
import { Suspense, lazy } from "react";
|
||||
import type { ThirdwebClient } from "../../../../../client/client.js";
|
||||
import { isEcosystemWallet } from "../../../../../wallets/ecosystem/is-ecosystem-wallet.js";
|
||||
import type { Wallet } from "../../../../../wallets/interfaces/wallet.js";
|
||||
import type { EcosystemWalletId } from "../../../../../wallets/wallet-types.js";
|
||||
import { iconSize } from "../../../../core/design-system/index.js";
|
||||
import { useActiveWallet } from "../../../../core/hooks/wallets/useActiveWallet.js";
|
||||
import { useActiveWalletChain } from "../../../../core/hooks/wallets/useActiveWalletChain.js";
|
||||
import EcosystemWalletConnectUI from "../../../wallets/ecosystem/EcosystemWalletConnectUI.js";
|
||||
import { LoadingScreen } from "../../../wallets/shared/LoadingScreen.js";
|
||||
import { Container, Line, ModalHeader } from "../../components/basic.js";
|
||||
import { Text } from "../../components/text.js";
|
||||
@@ -58,6 +61,30 @@ export function LinkProfileScreen(props: {
|
||||
);
|
||||
}
|
||||
|
||||
if (isEcosystemWallet(activeWallet)) {
|
||||
return (
|
||||
<Suspense fallback={<LoadingScreen />}>
|
||||
<EcosystemWalletConnectUI
|
||||
wallet={activeWallet as Wallet<EcosystemWalletId>}
|
||||
done={() => {
|
||||
queryClient.invalidateQueries({ queryKey: ["profiles"] });
|
||||
props.onBack();
|
||||
}}
|
||||
connectLocale={props.locale}
|
||||
client={props.client}
|
||||
size="compact"
|
||||
chain={chain}
|
||||
meta={{
|
||||
title: props.locale.manageWallet.linkProfile,
|
||||
showThirdwebBranding: false,
|
||||
}}
|
||||
isLinking={true}
|
||||
goBack={props.onBack}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container
|
||||
style={{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
import { ShuffleIcon } from "@radix-ui/react-icons";
|
||||
import type { ThirdwebClient } from "../../../../../client/client.js";
|
||||
import { isEcosystemWallet } from "../../../../../wallets/ecosystem/is-ecosystem-wallet.js";
|
||||
import { isInAppWallet } from "../../../../../wallets/in-app/core/wallet/index.js";
|
||||
import { injectedProvider } from "../../../../../wallets/injected/mipdStore.js";
|
||||
import { fontSize, iconSize } from "../../../../core/design-system/index.js";
|
||||
@@ -56,21 +57,23 @@ export function ManageWalletScreen(props: {
|
||||
/>
|
||||
|
||||
{/* Multi-auth */}
|
||||
{activeWallet?.id === "inApp" && (
|
||||
<MenuButton
|
||||
onClick={() => {
|
||||
props.setScreen("linked-profiles");
|
||||
}}
|
||||
style={{
|
||||
fontSize: fontSize.sm,
|
||||
}}
|
||||
>
|
||||
<MultiUserIcon size={iconSize.md} />
|
||||
<Text color="primaryText">
|
||||
{props.locale.manageWallet.linkedProfiles}
|
||||
</Text>
|
||||
</MenuButton>
|
||||
)}
|
||||
{activeWallet &&
|
||||
(activeWallet?.id === "inApp" ||
|
||||
isEcosystemWallet(activeWallet)) && (
|
||||
<MenuButton
|
||||
onClick={() => {
|
||||
props.setScreen("linked-profiles");
|
||||
}}
|
||||
style={{
|
||||
fontSize: fontSize.sm,
|
||||
}}
|
||||
>
|
||||
<MultiUserIcon size={iconSize.md} />
|
||||
<Text color="primaryText">
|
||||
{props.locale.manageWallet.linkedProfiles}
|
||||
</Text>
|
||||
</MenuButton>
|
||||
)}
|
||||
|
||||
{/* Wallet Connect Receiver */}
|
||||
<MenuButton
|
||||
|
||||
@@ -47,12 +47,12 @@ function EcosystemWalletConnectUI(props: {
|
||||
return <LoadingScreen />;
|
||||
}
|
||||
|
||||
const goBackToMain =
|
||||
props.size === "compact"
|
||||
? props.goBack
|
||||
: () => {
|
||||
setSelectionData({});
|
||||
};
|
||||
const goBackToMain = () => {
|
||||
if (props.size === "compact") {
|
||||
props.goBack?.();
|
||||
}
|
||||
setSelectionData({});
|
||||
};
|
||||
|
||||
const done = () => {
|
||||
props.done();
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
"use client";
|
||||
import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js";
|
||||
import { fontSize } from "../../../core/design-system/index.js";
|
||||
import { StyledButton } from "../../ui/design-system/elements.js";
|
||||
|
||||
export const LinkButton = /* @__PURE__ */ StyledButton((_) => {
|
||||
const theme = useCustomTheme();
|
||||
return {
|
||||
all: "unset",
|
||||
color: theme.colors.accentText,
|
||||
fontSize: fontSize.sm,
|
||||
cursor: "pointer",
|
||||
textAlign: "center",
|
||||
fontWeight: 500,
|
||||
width: "100%",
|
||||
"&:hover": {
|
||||
color: theme.colors.primaryText,
|
||||
},
|
||||
};
|
||||
});
|
||||
@@ -266,7 +266,7 @@ export const ConnectWalletSocialOptions = (
|
||||
|
||||
const connectPromise = (() => {
|
||||
if (props.isLinking) {
|
||||
if (wallet.id !== "inApp") {
|
||||
if (wallet.id !== "inApp" && !isEcosystemWallet(wallet)) {
|
||||
throw new Error("Only in-app wallets support multi-auth");
|
||||
}
|
||||
return linkProfile(wallet, connectOptions);
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import type { ThirdwebClient } from "../../../../client/client.js";
|
||||
import { getThirdwebBaseUrl } from "../../../../utils/domains.js";
|
||||
import { getClientFetch } from "../../../../utils/fetch.js";
|
||||
import type { Ecosystem } from "../../web/types.js";
|
||||
import type { Profile } from "./types.js";
|
||||
|
||||
/**
|
||||
@@ -11,14 +13,17 @@ import type { Profile } from "./types.js";
|
||||
*/
|
||||
export async function linkAccount({
|
||||
client,
|
||||
ecosystem,
|
||||
tokenToLink,
|
||||
}: {
|
||||
client: ThirdwebClient;
|
||||
ecosystem?: Ecosystem;
|
||||
tokenToLink: string;
|
||||
}): Promise<Profile[]> {
|
||||
const clientFetch = getClientFetch(client, ecosystem);
|
||||
const IN_APP_URL = getThirdwebBaseUrl("inAppWallet");
|
||||
const currentAccountToken = localStorage.getItem(
|
||||
`walletToken-${client.clientId}`,
|
||||
`walletToken-${client.clientId}${ecosystem?.id ? `-${ecosystem.id}` : ""}`,
|
||||
);
|
||||
|
||||
if (!currentAccountToken) {
|
||||
@@ -26,12 +31,10 @@ export async function linkAccount({
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer iaw-auth-token:${currentAccountToken}`,
|
||||
"x-thirdweb-client-id": client.clientId,
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
|
||||
const linkedDetailsResp = await fetch(
|
||||
const linkedDetailsResp = await clientFetch(
|
||||
`${IN_APP_URL}/api/2024-05-05/account/connect`,
|
||||
{
|
||||
method: "POST",
|
||||
@@ -61,23 +64,23 @@ export async function linkAccount({
|
||||
*/
|
||||
export async function getLinkedProfilesInternal({
|
||||
client,
|
||||
}: { client: ThirdwebClient }): Promise<Profile[]> {
|
||||
ecosystem,
|
||||
}: { client: ThirdwebClient; ecosystem?: Ecosystem }): Promise<Profile[]> {
|
||||
const clientFetch = getClientFetch(client, ecosystem);
|
||||
const IN_APP_URL = getThirdwebBaseUrl("inAppWallet");
|
||||
const currentAccountToken = localStorage.getItem(
|
||||
`walletToken-${client.clientId}`,
|
||||
`walletToken-${client.clientId}${ecosystem?.id ? `-${ecosystem.id}` : ""}`,
|
||||
);
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer iaw-auth-token:${currentAccountToken}`,
|
||||
"Content-Type": "application/json",
|
||||
};
|
||||
if (!currentAccountToken) {
|
||||
throw new Error("Failed to get linked accounts, no user logged in");
|
||||
}
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
"Content-Type": "application/json",
|
||||
Authorization: `Bearer iaw-auth-token:${currentAccountToken}`,
|
||||
"x-thirdweb-client-id": client.clientId,
|
||||
};
|
||||
|
||||
const linkedAccountsResp = await fetch(
|
||||
const linkedAccountsResp = await clientFetch(
|
||||
`${IN_APP_URL}/api/2024-05-05/accounts`,
|
||||
{
|
||||
method: "GET",
|
||||
|
||||
@@ -8,6 +8,16 @@ import type {
|
||||
CreateWalletArgs,
|
||||
EcosystemWalletId,
|
||||
} from "../../../wallet-types.js";
|
||||
import type { Ecosystem } from "../../web/types.js";
|
||||
import {
|
||||
getLinkedProfilesInternal,
|
||||
linkAccount as linkProfileWithToken,
|
||||
} from "../authentication/linkAccount.js";
|
||||
import type {
|
||||
MultiStepAuthArgsType,
|
||||
Profile,
|
||||
SingleStepAuthArgsType,
|
||||
} from "../authentication/types.js";
|
||||
import type { InAppConnector } from "../interfaces/connector.js";
|
||||
import { getOrCreateInAppWalletConnector } from "./in-app-core.js";
|
||||
|
||||
@@ -25,6 +35,10 @@ export function createEcosystemWallet(args: {
|
||||
let account: Account | undefined = undefined;
|
||||
let chain: Chain | undefined = undefined;
|
||||
let client: ThirdwebClient | undefined;
|
||||
const ecosystem: Ecosystem = {
|
||||
id,
|
||||
partnerId: createOptions?.partnerId,
|
||||
};
|
||||
|
||||
return {
|
||||
id,
|
||||
@@ -38,7 +52,13 @@ export function createEcosystemWallet(args: {
|
||||
return chain;
|
||||
},
|
||||
getConfig: () => createOptions,
|
||||
getProfiles: async () => {
|
||||
if (!client) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return getLinkedProfilesInternal({ client, ecosystem });
|
||||
},
|
||||
getAccount: () => account,
|
||||
autoConnect: async (options) => {
|
||||
const { autoConnectInAppWallet } = await import("./index.js");
|
||||
@@ -46,10 +66,7 @@ export function createEcosystemWallet(args: {
|
||||
const connector = await getOrCreateInAppWalletConnector(
|
||||
options.client,
|
||||
connectorFactory,
|
||||
{
|
||||
id,
|
||||
partnerId: createOptions?.partnerId,
|
||||
},
|
||||
ecosystem,
|
||||
);
|
||||
|
||||
const [connectedAccount, connectedChain] = await autoConnectInAppWallet(
|
||||
@@ -75,10 +92,7 @@ export function createEcosystemWallet(args: {
|
||||
const connector = await getOrCreateInAppWalletConnector(
|
||||
options.client,
|
||||
connectorFactory,
|
||||
{
|
||||
id,
|
||||
partnerId: createOptions?.partnerId,
|
||||
},
|
||||
ecosystem,
|
||||
);
|
||||
|
||||
const [connectedAccount, connectedChain] = await connectInAppWallet(
|
||||
@@ -104,10 +118,7 @@ export function createEcosystemWallet(args: {
|
||||
const connector = await getOrCreateInAppWalletConnector(
|
||||
client,
|
||||
connectorFactory,
|
||||
{
|
||||
id,
|
||||
partnerId: createOptions?.partnerId,
|
||||
},
|
||||
ecosystem,
|
||||
);
|
||||
const result = await connector.logout();
|
||||
if (!result.success) {
|
||||
@@ -122,5 +133,28 @@ export function createEcosystemWallet(args: {
|
||||
chain = newChain;
|
||||
emitter.emit("chainChanged", newChain);
|
||||
},
|
||||
};
|
||||
// This is not included on the global interface but is force-resolved in linkProfile
|
||||
linkProfile: async (
|
||||
options: SingleStepAuthArgsType | MultiStepAuthArgsType,
|
||||
): Promise<Profile[]> => {
|
||||
if (!client) {
|
||||
throw new Error(
|
||||
"No client found, please connect the wallet before linking a profile",
|
||||
);
|
||||
}
|
||||
|
||||
const connector = await getOrCreateInAppWalletConnector(
|
||||
client,
|
||||
connectorFactory,
|
||||
ecosystem,
|
||||
);
|
||||
|
||||
const { storedToken } = await connector.authenticate(options);
|
||||
return await linkProfileWithToken({
|
||||
client,
|
||||
ecosystem,
|
||||
tokenToLink: storedToken.cookieString,
|
||||
});
|
||||
},
|
||||
} as Wallet<EcosystemWalletId>;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { isEcosystemWallet } from "../../../../wallets/ecosystem/is-ecosystem-wallet.js";
|
||||
import type { EcosystemWalletId } from "../../../../wallets/wallet-types.js";
|
||||
import type { Wallet } from "../../../interfaces/wallet.js";
|
||||
import type {
|
||||
MultiStepAuthArgsType,
|
||||
@@ -25,8 +27,8 @@ import type {
|
||||
* ```
|
||||
* @wallet
|
||||
*/
|
||||
export async function getProfiles(wallet: Wallet<"inApp">) {
|
||||
if (wallet.id !== "inApp") {
|
||||
export async function getProfiles(wallet: Wallet<"inApp" | EcosystemWalletId>) {
|
||||
if (wallet.id !== "inApp" && !isEcosystemWallet(wallet)) {
|
||||
throw new Error("Multi-auth currently only supports in-app wallets");
|
||||
}
|
||||
|
||||
@@ -59,10 +61,10 @@ export async function getProfiles(wallet: Wallet<"inApp">) {
|
||||
* @wallet
|
||||
*/
|
||||
export async function linkProfile(
|
||||
wallet: Wallet<"inApp">,
|
||||
wallet: Wallet<"inApp" | EcosystemWalletId>,
|
||||
auth: MultiStepAuthArgsType | SingleStepAuthArgsType,
|
||||
): Promise<Profile[]> {
|
||||
if (wallet.id !== "inApp") {
|
||||
if (wallet.id !== "inApp" && !isEcosystemWallet(wallet)) {
|
||||
throw new Error("Multi-auth currently only supports in-app wallets");
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user