Skip to content

Commit a2d2291

Browse files
committed
[SDK] Feature: Social Extension (Beta) (#4349)
## Problem solved Short description of the bug fixed or feature added <!-- start pr-codex --> --- ## PR-Codex overview This PR adds support for fetching social profiles for Farcaster, Lens, and ENS. ### Detailed summary - Added functions to fetch social profiles for different protocols - Updated components to use social profile data - Updated types for social profiles and profiles metadata > The following files were skipped due to too many changes: `packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx` > ✨ Ask PR-Codex anything about this PR by commenting with `/codex {your question}` <!-- end pr-codex -->
1 parent 9cefe5f commit a2d2291

File tree

12 files changed

+268
-52
lines changed

12 files changed

+268
-52
lines changed

.changeset/early-mayflies-hug.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
---
2+
"thirdweb": minor
3+
---
4+
5+
Adds social profile retrieval for Farcaster, Lens, and ENS.
6+
7+
```ts
8+
import { getSocialProfiles } from "thirdweb/social";
9+
const profiles = await getSocialProfiles({
10+
address: "0x...",
11+
client,
12+
});
13+
```
14+
15+
```json
16+
[
17+
{
18+
"type": "ens",
19+
"name": "joenrv.eth",
20+
"avatar": "ipfs://bafybeic2wvtpv5hpdyeuy6o77yd5fp2ndfygppd6drdxvtfd2jouijn72m",
21+
"metadata": {
22+
"name": "joenrv.eth"
23+
}
24+
},
25+
{
26+
"type": "farcaster",
27+
"name": "joaquim",
28+
"bio": "Eng Lead @ thirdweb",
29+
"avatar": "https://lh3.googleusercontent.com/EUELPFJzdDNcc3qSaEMekh0_W16acnS8MSvWizt-7HPaQhfJsNFC5HA0W4NKcy6CN9zmV7d4Crqg2B8qM9BpiveqVTl2GPBQ16Ax2IQ",
30+
"metadata": {
31+
"fid": 2735,
32+
"bio": "Eng Lead @ thirdweb",
33+
"pfp": "https://lh3.googleusercontent.com/EUELPFJzdDNcc3qSaEMekh0_W16acnS8MSvWizt-7HPaQhfJsNFC5HA0W4NKcy6CN9zmV7d4Crqg2B8qM9BpiveqVTl2GPBQ16Ax2IQ",
34+
"username": "joaquim",
35+
"addresses": [
36+
"0x2247d5d238d0f9d37184d8332ae0289d1ad9991b",
37+
"0xf7970369310b541b8a84086c8c1c81d3beb85e0e"
38+
]
39+
}
40+
},
41+
{
42+
"type": "lens",
43+
"name": "joaquim",
44+
"bio": "Lead engineer @thirdweb",
45+
"avatar": "https://ik.imagekit.io/lens/media-snapshot/557708cc7581172234133c10d473058ace362c5f547fa86cee5be2abe1478e5b.png",
46+
"metadata": {
47+
"name": "joaquim",
48+
"bio": "Lead engineer @thirdweb",
49+
"picture": "https://ik.imagekit.io/lens/media-snapshot/557708cc7581172234133c10d473058ace362c5f547fa86cee5be2abe1478e5b.png"
50+
}
51+
}
52+
]
53+
```
54+
55+
```tsx
56+
import { useSocialProfiles } from "thirdweb/react";
57+
const { data: profiles } = useSocialProfiles({
58+
client,
59+
address: "0x...",
60+
});
61+
```
62+
63+

packages/thirdweb/src/exports/react.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,3 +184,6 @@ export { useConnectionManager } from "../react/core/providers/connection-manager
184184
export { Blobbie } from "../react/web/ui/ConnectWallet/Blobbie.js";
185185

186186
export { useSiweAuth } from "../react/core/hooks/auth/useSiweAuth.js";
187+
188+
// Social
189+
export { useSocialProfiles } from "../react/core/social/useSocialProfiles.js";
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { getSocialProfiles } from "../social/profiles.js";
2+
export { type SocialProfiles } from "../social/types.js";
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { useQuery } from "@tanstack/react-query";
2+
import type { ThirdwebClient } from "../../../client/client.js";
3+
import { getSocialProfiles } from "../../../social/profiles.js";
4+
5+
/**
6+
* Fetches the wallet's available social profiles.
7+
* @param options - The options to use when fetching the social profiles.
8+
* @param options.client - The Thirdweb client.
9+
* @param options.address - The wallet address to fetch the social profiles for.
10+
* @returns A React Query result containing the social profiles.
11+
*
12+
* @example
13+
* ```ts
14+
* import { useSocialProfiles } from "thirdweb/react";
15+
* const { data: profiles } = useSocialProfiles({
16+
* client,
17+
* address: "0x...",
18+
* });
19+
* ```
20+
* @beta
21+
*/
22+
export function useSocialProfiles(options: {
23+
client: ThirdwebClient;
24+
address: string | undefined;
25+
}) {
26+
const { client, address } = options;
27+
return useQuery({
28+
queryKey: ["social-profiles", address],
29+
enabled: !!address,
30+
retry: 3,
31+
queryFn: async () => {
32+
if (!address) {
33+
throw new Error(
34+
"Address is required, should not have reached this point.",
35+
);
36+
}
37+
return await getSocialProfiles({ address, client });
38+
},
39+
});
40+
}

packages/thirdweb/src/react/core/utils/wallet.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@ import type { ThirdwebClient } from "../../../client/client.js";
55
import { resolveAvatar } from "../../../extensions/ens/resolve-avatar.js";
66
import { resolveName } from "../../../extensions/ens/resolve-name.js";
77
import { shortenAddress } from "../../../utils/address.js";
8+
import { parseAvatarRecord } from "../../../utils/ens/avatar.js";
89
import { getWalletInfo } from "../../../wallets/__generated__/getWalletInfo.js";
910
import type { Account, Wallet } from "../../../wallets/interfaces/wallet.js";
1011
import type { WalletInfo } from "../../../wallets/wallet-info.js";
1112
import type { WalletId } from "../../../wallets/wallet-types.js";
1213
import { useWalletBalance } from "../hooks/others/useWalletBalance.js";
14+
import { useSocialProfiles } from "../social/useSocialProfiles.js";
1315

1416
/**
1517
* Get the ENS name and avatar for an address
@@ -98,6 +100,11 @@ export function useConnectedWalletDetails(
98100
ensName: ensNameQuery.data,
99101
});
100102

103+
const socialProfileQuery = useSocialProfiles({
104+
client,
105+
address: activeAccount?.address,
106+
});
107+
101108
const shortAddress = activeAccount?.address
102109
? shortenAddress(activeAccount.address, 4)
103110
: "";
@@ -110,11 +117,31 @@ export function useConnectedWalletDetails(
110117
});
111118

112119
const addressOrENS = ensNameQuery.data || shortAddress;
120+
const pfpUnresolved = socialProfileQuery.data?.filter((p) => p.avatar)[0]
121+
?.avatar;
122+
123+
const { data: pfp } = useQuery({
124+
queryKey: ["ens-avatar", pfpUnresolved],
125+
queryFn: async () => {
126+
if (!pfpUnresolved) {
127+
return undefined;
128+
}
129+
return parseAvatarRecord({ client, uri: pfpUnresolved });
130+
},
131+
enabled: !!pfpUnresolved,
132+
refetchOnWindowFocus: false,
133+
refetchOnMount: false,
134+
});
135+
const name =
136+
socialProfileQuery.data?.filter((p) => p.name)[0]?.name || addressOrENS;
113137

114138
return {
139+
socialProfileQuery,
115140
ensNameQuery,
116141
ensAvatarQuery,
117142
addressOrENS,
143+
pfp,
144+
name,
118145
shortAddress,
119146
balanceQuery,
120147
};

packages/thirdweb/src/react/native/ui/components/WalletImage.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,9 @@ export const WalletImage = (props: {
2121
theme: Theme;
2222
wallet: Wallet;
2323
size: number;
24-
ensAvatar?: string | null;
24+
avatar?: string | null;
2525
}) => {
26-
const { wallet, ensAvatar, size } = props;
26+
const { wallet, avatar, size } = props;
2727

2828
const { data: imageData } = useQuery({
2929
queryKey: ["wallet-image", wallet.id, wallet.getAccount()?.address],
@@ -56,10 +56,10 @@ export const WalletImage = (props: {
5656

5757
return WALLET_ICON;
5858
},
59-
enabled: !ensAvatar,
59+
enabled: !avatar,
6060
});
6161

62-
const data = ensAvatar || imageData || WALLET_ICON;
62+
const data = avatar || imageData || WALLET_ICON;
6363
return <RNImage theme={props.theme} data={data} size={size} />;
6464
};
6565

packages/thirdweb/src/react/native/ui/connect/ConnectedButton.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,12 @@ export function ConnectedButton(
2222
const theme = parseTheme(props.theme);
2323
const { account, wallet } = props;
2424
const walletChain = useActiveWalletChain();
25-
const { ensAvatarQuery, addressOrENS, balanceQuery } =
26-
useConnectedWalletDetails(
27-
props.client,
28-
walletChain,
29-
account,
30-
props.detailsButton?.displayBalanceToken,
31-
);
25+
const { pfp, name, balanceQuery } = useConnectedWalletDetails(
26+
props.client,
27+
walletChain,
28+
account,
29+
props.detailsButton?.displayBalanceToken,
30+
);
3231
return (
3332
<ThemedButton
3433
theme={theme}
@@ -41,12 +40,7 @@ export function ConnectedButton(
4140
}}
4241
>
4342
<View style={styles.row}>
44-
<WalletImage
45-
theme={theme}
46-
size={40}
47-
wallet={wallet}
48-
ensAvatar={ensAvatarQuery.data}
49-
/>
43+
<WalletImage theme={theme} size={40} wallet={wallet} avatar={pfp} />
5044
<View style={styles.col}>
5145
<ThemedText
5246
theme={theme}
@@ -55,7 +49,7 @@ export function ConnectedButton(
5549
color: theme.colors.primaryButtonText,
5650
}}
5751
>
58-
{addressOrENS}
52+
{name}
5953
</ThemedText>
6054
{balanceQuery.data ? (
6155
<ThemedText

packages/thirdweb/src/react/native/ui/connect/ConnectedModal.tsx

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -163,24 +163,18 @@ export function ConnectedModal(props: ConnectedModalProps) {
163163
const AccountHeader = (props: ConnectedModalProps) => {
164164
const { account, wallet, theme } = props;
165165
const walletChain = useActiveWalletChain();
166-
const { ensAvatarQuery, addressOrENS, balanceQuery } =
167-
useConnectedWalletDetails(
168-
props.client,
169-
walletChain,
170-
account,
171-
props.detailsButton?.displayBalanceToken,
172-
);
166+
const { pfp, name, balanceQuery } = useConnectedWalletDetails(
167+
props.client,
168+
walletChain,
169+
account,
170+
props.detailsButton?.displayBalanceToken,
171+
);
173172
return (
174173
<View style={styles.accountHeaderContainer}>
175-
<WalletImage
176-
theme={theme}
177-
size={70}
178-
wallet={wallet}
179-
ensAvatar={ensAvatarQuery.data}
180-
/>
174+
<WalletImage theme={theme} size={70} wallet={wallet} avatar={pfp} />
181175
<SmartAccountBadge client={props.client} theme={theme} />
182176
<Spacer size="smd" />
183-
<Address account={account} theme={theme} addressOrENS={addressOrENS} />
177+
<Address account={account} theme={theme} addressOrENS={name} />
184178
<Spacer size="xxs" />
185179
{balanceQuery.data ? (
186180
<ThemedText

packages/thirdweb/src/react/web/ui/ConnectWallet/Details.tsx

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -131,13 +131,12 @@ export const ConnectedWalletDetails: React.FC<{
131131
const activeAccount = useActiveAccount();
132132
const walletChain = useActiveWalletChain();
133133

134-
const { ensAvatarQuery, addressOrENS, balanceQuery } =
135-
useConnectedWalletDetails(
136-
client,
137-
walletChain,
138-
activeAccount,
139-
props.detailsButton?.displayBalanceToken,
140-
);
134+
const { pfp, name, balanceQuery } = useConnectedWalletDetails(
135+
client,
136+
walletChain,
137+
activeAccount,
138+
props.detailsButton?.displayBalanceToken,
139+
);
141140

142141
function closeModal() {
143142
setRootEl(null);
@@ -185,8 +184,7 @@ export const ConnectedWalletDetails: React.FC<{
185184
);
186185
}
187186

188-
const avatarSrc =
189-
props.detailsButton?.connectedAccountAvatarUrl ?? ensAvatarQuery.data;
187+
const avatarSrc = props.detailsButton?.connectedAccountAvatarUrl || pfp;
190188

191189
return (
192190
<WalletInfoButton
@@ -235,7 +233,7 @@ export const ConnectedWalletDetails: React.FC<{
235233
weight={500}
236234
className={`${TW_CONNECTED_WALLET}__address`}
237235
>
238-
{props.detailsButton?.connectedAccountName ?? addressOrENS}
236+
{props.detailsButton?.connectedAccountName ?? name}
239237
</Text>
240238

241239
{/* Balance */}
@@ -280,13 +278,12 @@ function DetailsModal(props: {
280278
const { client, locale } = props;
281279
const walletChain = useActiveWalletChain();
282280
const activeAccount = useActiveAccount();
283-
const { ensAvatarQuery, addressOrENS, balanceQuery } =
284-
useConnectedWalletDetails(
285-
client,
286-
walletChain,
287-
activeAccount,
288-
props.displayBalanceToken,
289-
);
281+
const { pfp, name, balanceQuery } = useConnectedWalletDetails(
282+
client,
283+
walletChain,
284+
activeAccount,
285+
props.displayBalanceToken,
286+
);
290287
const theme = parseTheme(props.theme);
291288

292289
const activeWallet = useActiveWallet();
@@ -370,8 +367,7 @@ function DetailsModal(props: {
370367
</MenuButton>
371368
);
372369

373-
const avatarSrc =
374-
props.detailsModal?.connectedAccountAvatarUrl ?? ensAvatarQuery.data;
370+
const avatarSrc = props.detailsModal?.connectedAccountAvatarUrl ?? pfp;
375371

376372
const { hideSendFunds, hideReceiveFunds, hideBuyFunds } =
377373
props.detailsModal || {};
@@ -483,7 +479,7 @@ function DetailsModal(props: {
483479
}}
484480
>
485481
<Text color="primaryText" weight={500} size="md">
486-
{props.detailsModal?.connectedAccountName ?? addressOrENS}
482+
{props.detailsModal?.connectedAccountName ?? name}
487483
</Text>
488484
<IconButton>
489485
<CopyIcon
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { ThirdwebClient } from "../client/client.js";
2+
import { getThirdwebBaseUrl } from "../utils/domains.js";
3+
import { getClientFetch } from "../utils/fetch.js";
4+
import type { SocialProfiles } from "./types.js";
5+
6+
/**
7+
* Fetches the wallet's available social profiles.
8+
* @param args - The arguments to use when fetching the social profiles.
9+
* @param args.address - The wallet address to fetch the social profiles for.
10+
* @param args.client - The Thirdweb client.
11+
* @returns A promise resolving to the retrieved social profiles for different protocols. If a profile is not available for a protocol, the value will be `null`.
12+
*
13+
* @example
14+
* ```ts
15+
* import { getProfiles } from "thirdweb/social";
16+
* const profiles = await getProfiles({
17+
* address: "0x...",
18+
* client,
19+
* });
20+
* ```
21+
* @beta
22+
*/
23+
export async function getSocialProfiles(args: {
24+
address: string;
25+
client: ThirdwebClient;
26+
}): Promise<SocialProfiles> {
27+
const { address, client } = args;
28+
29+
const clientFetch = getClientFetch(client);
30+
const response = await clientFetch(
31+
`${getThirdwebBaseUrl("social")}/v1/profiles/${address}`,
32+
);
33+
34+
if (response.status !== 200) {
35+
try {
36+
const errorBody = await response.json();
37+
throw new Error(`Failed to fetch profile: ${errorBody.message}`);
38+
} catch {
39+
throw new Error(
40+
`Failed to fetch profile: ${response.status}\n${await response.text()}`,
41+
);
42+
}
43+
}
44+
45+
return (await response.json()).data as SocialProfiles;
46+
}

0 commit comments

Comments
 (0)