Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hot-adults-doubt.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"thirdweb": minor
---

New `useLinkProfile()` hook to link profiles to inapp and ecosystem accounts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
export const dynamic = "force-dynamic";
import { CodeExample } from "@/components/code/code-example";
import { EcosystemConnectEmbed } from "../../../../components/in-app-wallet/ecosystem";
import { Profiles } from "../../../../components/in-app-wallet/profile-sections";
import ThirdwebProvider from "../../../../components/thirdweb-provider";

export default function Page() {
return (
<ThirdwebProvider>
<section className="space-y-8">
<section className="space-y-6">
<AnyAuth />
</section>
<section className="space-y-8">
<Profiles />
</section>
</ThirdwebProvider>
);
}
Expand All @@ -17,7 +22,7 @@ function AnyAuth() {
<>
<div className="space-y-2">
<h2 className="font-semibold text-2xl tracking-tight sm:text-3xl">
Your own Ecosystem
Build your own Ecosystem
</h2>
<p className="max-w-[600px]">
Build a public or permissioned ecosystem by allowing third party apps
Expand Down
5 changes: 5 additions & 0 deletions apps/playground-web/src/app/connect/in-app-wallet/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export const dynamic = "force-dynamic";
import { CodeExample } from "@/components/code/code-example";
import { InAppConnectEmbed } from "../../../components/in-app-wallet/connect-button";
import { Profiles } from "../../../components/in-app-wallet/profile-sections";
import ThirdwebProvider from "../../../components/thirdweb-provider";

export default function Page() {
Expand All @@ -8,6 +10,9 @@ export default function Page() {
<section className="space-y-8">
<AnyAuth />
</section>
<section className="mt-8 space-y-8">
<Profiles />
</section>
</ThirdwebProvider>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { Suspense, lazy } from "react";
import { LoadingDots } from "../../../../components/ui/LoadingDots";
import { CodeLoading } from "../../../../components/code/code.client";
import type { ConnectPlaygroundOptions } from "./types";

const CodeClient = lazy(
() => import("../../../../components/code/code.client"),
);

function CodeLoading() {
return (
<div className="flex h-[300px] items-center justify-center xl:h-[calc(100vh-100px)]">
<LoadingDots />
</div>
);
}

export function CodeGen(props: {
connectOptions: ConnectPlaygroundOptions;
}) {
Expand Down
4 changes: 2 additions & 2 deletions apps/playground-web/src/app/navLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,11 @@ export const navLinks: SidebarLink[] = [
expanded: true,
links: [
{
name: "Any auth method",
name: "Any Auth",
href: "/connect/in-app-wallet",
},
{
name: "Your own Ecosystem",
name: "Ecosystems",
href: "/connect/in-app-wallet/ecosystem",
},
{
Expand Down
9 changes: 9 additions & 0 deletions apps/playground-web/src/components/code/code.client.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { keepPreviousData, useQuery } from "@tanstack/react-query";
import type { BundledLanguage } from "shiki";
import { LoadingDots } from "../ui/LoadingDots";
import { RenderCode } from "./RenderCode";
import { getCodeHtml } from "./getCodeHtml";

Expand All @@ -14,6 +15,14 @@ export type CodeProps = {
scrollableClassName?: string;
};

export function CodeLoading() {
return (
<div className="flex h-[300px] items-center justify-center xl:h-[calc(100vh-100px)]">
<LoadingDots />
</div>
);
}

export const CodeClient: React.FC<CodeProps> = ({
code,
lang,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { CodeExample } from "@/components/code/code-example";
import { LinkAccount, LinkedAccounts } from "./profiles";

export function Profiles() {
return (
<>
<div className="space-y-2">
<h2 className="font-semibold text-xl tracking-tight sm:text-2xl">
View Linked Profiles
</h2>
<p className="max-w-[600px]">
View all web2 and web3 linked profiles for a user along with specific
details for each profile type, including name, email, profile picture
and more.
</p>
</div>

<CodeExample
preview={<LinkedAccounts />}
code={`import { useProfiles } from "thirdweb/react";

function App() {
const { data: profiles } = useProfiles({
client,
});

return (
<div>
{profiles?.map((profile) => (
<div key={profile.type}>
<ProfileCard profile={profile} />
</div>
))}
</div>
);
};`}
lang="tsx"
/>

<div className="space-y-2">
<h2 className="font-semibold text-xl tracking-tight sm:text-2xl">
Link another profile
</h2>
<p className="max-w-[600px]">
Link a web2 or web3 profile to the connected account.
<br />
You can do this with hooks like shown here or from the prebuilt
connect UI.
</p>
</div>

<CodeExample
preview={<LinkAccount />}
code={`import { useLinkProfile } from "thirdweb/react";

function App() {
const { mutate: linkProfile, isPending, error } = useLinkProfile();

return (
<div>
<button
onClick={() => linkProfile({
client: THIRDWEB_CLIENT,
strategy: "wallet",
wallet: createWallet("com.coinbase.wallet"),
chain: baseSepolia,
})}
>
Link Coinbase Wallet
</button>
</div>
);
};`}
lang="tsx"
/>
</>
);
}
92 changes: 92 additions & 0 deletions apps/playground-web/src/components/in-app-wallet/profiles.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
"use client";
import { baseSepolia } from "thirdweb/chains";
import { useActiveAccount, useLinkProfile, useProfiles } from "thirdweb/react";
import { type WalletId, createWallet } from "thirdweb/wallets";
import { THIRDWEB_CLIENT } from "../../lib/client";
import CodeClient, { CodeLoading } from "../code/code.client";
import { Button } from "../ui/button";

export function LinkedAccounts() {
const { data: profiles } = useProfiles({
client: THIRDWEB_CLIENT,
});

return (
<div className="flex flex-col gap-4 p-6">
{profiles ? (
<CodeClient
code={JSON.stringify(profiles, null, 2)}
lang={"json"}
loader={<CodeLoading />}
/>
) : (
<p>Login to see linked profiles</p>
)}
</div>
);
}

export function LinkAccount() {
const { mutate: linkProfile, isPending, error } = useLinkProfile();
const account = useActiveAccount();
const linkWallet = async (walletId: WalletId) => {
linkProfile({
client: THIRDWEB_CLIENT,
strategy: "wallet",
wallet: createWallet(walletId),
chain: baseSepolia,
});
};

const linkPasskey = async () => {
linkProfile({
client: THIRDWEB_CLIENT,
strategy: "passkey",
type: "sign-up",
});
};

return (
<div className="flex flex-col gap-4 p-6">
{account ? (
<>
{isPending ? (
<p>Linking...</p>
) : (
<>
{/*
TODO make cb smart wallet linking work
<Button
variant="default"
onClick={() => linkWallet("com.coinbase.wallet")}
className="rounded-full p-6"
disabled={isPending}
>
Link Coinbase Wallet
</Button> */}
<Button
variant="default"
onClick={() => linkWallet("io.metamask")}
className="rounded-full p-6"
disabled={isPending}
>
Link MetaMask
</Button>
<Button
variant="default"
onClick={linkPasskey}
className="rounded-full p-6"
disabled={isPending}
>
Link Passkey
</Button>
</>
)}
{error && <p className="text-red-500">Error: {error.message}</p>}
</>
) : (
<p>Login to link another account.</p>
)}
</div>
);
}
10 changes: 10 additions & 0 deletions apps/portal/src/app/react-native/v5/sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,16 @@ export const sidebar: SideBar = {
},
],
},
{
name: "Ecosystem Wallets",
links: [
{
name: "React API",
href: "/react/v5/ecosystem-wallet/get-started",
icon: <ReactIcon />,
},
],
},
{
name: "Account Abstraction",
links: [
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import {
Tabs,
TabsList,
TabsTrigger,
TabsContent,
DocImage,
createMetadata,
} from "@doc";

export const metadata = createMetadata({
title: "Connect users with Ecosystem Wallets",
description:
"use the prebuilt connect UI components to authenticate users and connect ecosystem wallets",
image: {
title: "Connect users with Ecosystem Wallets",
icon: "wallets",
},
});

# Connect Users to your Ecosystem

## Using the Connect UI components

If you're building a [React website](/typescript/react/v5/ConnectButton), [React Native app](/react-native/v5/), or [Unity game](/unity/ConnectWallet) you can use the prebuilt connect UI components to authenticate users and connect their wallets accross your ecosystem.

```jsx
import { ThirdwebProvider, ConnectButton } from "thirdweb/react";
import { inAppWallet } from "thirdweb/wallets";

const client = createThirdwebClient({ clientId });
const wallets = [ecosystemWallet("ecosystem.your-ecosystem-id")];

export default function App() {
return (
<ThirdwebProvider>
<ConnectButton client={client} wallets={wallets} />
</ThirdwebProvider>
);
}
```

## Using your own UI

You can also build your own UI using the low-level hooks and functions. Remember to wrap your app in a `ThirdwebProvider` to ensure that the wallet is available to all components in your app.

```tsx
import { ecosystemWallet } from "thirdweb/wallets";

const wallet = ecosystemWallet("ecosystem.your-ecosystem-id");

const LoginComponent = () => {
const { connect, isLoading } = useConnect();

return <button onClick={() => connect(async () => {
await wallet.connect({
client,
strategy: "discord", // or any supported auth strategy
})
return wallet;
})}>Connect</button>;
};
```

## Passing a partner ID

For closed ecosystems, you can pass a valid `partnerId` to the `ecosystemWallet` provided by the ecosystem owner.

```tsx
const wallet = ecosystemWallet("ecosystem.your-ecosystem-id", {
partnerId: "your-partner-id",
});
```
Loading