diff --git a/.changeset/breezy-heads-crash.md b/.changeset/breezy-heads-crash.md new file mode 100644 index 00000000000..d661321e877 --- /dev/null +++ b/.changeset/breezy-heads-crash.md @@ -0,0 +1,5 @@ +--- +"@thirdweb-dev/api": patch +--- + +added support for userId across wallet apis diff --git a/apps/dashboard/src/@/components/in-app-wallet-users-content/AdvancedSearchInput.tsx b/apps/dashboard/src/@/components/in-app-wallet-users-content/AdvancedSearchInput.tsx index 2e6d0eddf3f..b037d66e1ac 100644 --- a/apps/dashboard/src/@/components/in-app-wallet-users-content/AdvancedSearchInput.tsx +++ b/apps/dashboard/src/@/components/in-app-wallet-users-content/AdvancedSearchInput.tsx @@ -14,6 +14,15 @@ import { } from "@/components/ui/select"; import type { SearchType } from "./types"; +const searchTypeLabels: Record = { + email: "Email", + phone: "Phone", + id: "Auth Identifier", + address: "Address", + externalWallet: "External Wallet", + userId: "User Identifier", +}; + export function AdvancedSearchInput(props: { onSearch: (searchType: SearchType, query: string) => void; onClear: () => void; @@ -52,7 +61,8 @@ export function AdvancedSearchInput(props: { Email Phone - ID + Auth Identifier + User Identifier Address External Wallet @@ -62,7 +72,7 @@ export function AdvancedSearchInput(props: {
setQuery(e.target.value)} onKeyDown={handleKeyDown} diff --git a/apps/dashboard/src/@/components/in-app-wallet-users-content/SearchResults.tsx b/apps/dashboard/src/@/components/in-app-wallet-users-content/SearchResults.tsx index 701d0e71161..ec1c664a197 100644 --- a/apps/dashboard/src/@/components/in-app-wallet-users-content/SearchResults.tsx +++ b/apps/dashboard/src/@/components/in-app-wallet-users-content/SearchResults.tsx @@ -12,18 +12,25 @@ import { TooltipProvider, TooltipTrigger, } from "@/components/ui/tooltip"; +import { CopyTextButton } from "../ui/CopyTextButton"; -const getUserIdentifier = (user: WalletUser) => { +const getAuthIdentifier = (user: WalletUser) => { const mainDetail = user.linkedAccounts[0]?.details; return ( + mainDetail?.id ?? mainDetail?.email ?? mainDetail?.phone ?? - mainDetail?.address ?? - mainDetail?.id ?? - user.id + mainDetail?.address ); }; +const truncateIdentifier = (value: string) => { + if (value.length <= 18) { + return value; + } + return `${value.slice(0, 8)}...${value.slice(-4)}`; +}; + export function SearchResults(props: { results: WalletUser[]; client: ThirdwebClient; @@ -51,6 +58,8 @@ export function SearchResults(props: { const mainDetail = user.linkedAccounts?.[0]?.details; const email = mainDetail?.email as string | undefined; const phone = mainDetail?.phone as string | undefined; + const authIdentifier = getAuthIdentifier(user); + const userIdentifier = user.id; // Get external wallet addresses from linkedAccounts where type is 'siwe' const externalWalletAccounts = @@ -68,7 +77,34 @@ export function SearchResults(props: {

User Identifier

-

{getUserIdentifier(user)}

+ {userIdentifier ? ( + + ) : ( +

N/A

+ )} +
+ +
+

+ Auth Identifier +

+ {authIdentifier ? ( + + ) : ( +

N/A

+ )}
{walletAddress && ( diff --git a/apps/dashboard/src/@/components/in-app-wallet-users-content/in-app-wallet-users-content.tsx b/apps/dashboard/src/@/components/in-app-wallet-users-content/in-app-wallet-users-content.tsx index 95fe1e3501a..280c8aa14e6 100644 --- a/apps/dashboard/src/@/components/in-app-wallet-users-content/in-app-wallet-users-content.tsx +++ b/apps/dashboard/src/@/components/in-app-wallet-users-content/in-app-wallet-users-content.tsx @@ -28,22 +28,42 @@ import { SearchResults } from "./SearchResults"; import { searchUsers } from "./searchUsers"; import type { SearchType } from "./types"; -const getUserIdentifier = (accounts: WalletUser["linkedAccounts"]) => { +const getAuthIdentifier = (accounts: WalletUser["linkedAccounts"]) => { const mainDetail = accounts[0]?.details; return ( + mainDetail?.id ?? mainDetail?.email ?? mainDetail?.phone ?? - mainDetail?.address ?? - mainDetail?.id + mainDetail?.address ); }; -const getExternalWallets = (accounts: WalletUser["linkedAccounts"]) => { - return accounts?.filter((account) => account.type === "siwe") || []; +const getPrimaryEmail = (accounts: WalletUser["linkedAccounts"]) => { + const emailFromPrimary = accounts[0]?.details?.email; + if (emailFromPrimary) { + return emailFromPrimary; + } + + const emailAccount = accounts.find((account) => { + return typeof account.details?.email === "string" && account.details.email; + }); + + if (emailAccount && typeof emailAccount.details.email === "string") { + return emailAccount.details.email; + } + + return undefined; }; const columnHelper = createColumnHelper(); +const truncateIdentifier = (value: string) => { + if (value.length <= 18) { + return value; + } + return `${value.slice(0, 8)}...${value.slice(-4)}`; +}; + export function InAppWalletUsersPageContent( props: { authToken: string; @@ -56,9 +76,30 @@ export function InAppWalletUsersPageContent( ) { const columns = useMemo(() => { return [ + columnHelper.accessor("id", { + cell: (cell) => { + const userId = cell.getValue(); + + if (!userId) { + return "N/A"; + } + + return ( + + ); + }, + header: "User Identifier", + id: "user_identifier", + }), columnHelper.accessor("linkedAccounts", { cell: (cell) => { - const identifier = getUserIdentifier(cell.getValue()); + const identifier = getAuthIdentifier(cell.getValue()); if (!identifier) { return "N/A"; @@ -66,21 +107,17 @@ export function InAppWalletUsersPageContent( return ( 30 - ? `${identifier.slice(0, 30)}...` - : identifier - } + textToShow={truncateIdentifier(identifier)} textToCopy={identifier} - tooltip="Copy User Identifier" + tooltip="Copy Auth Identifier" copyIconPosition="left" variant="ghost" /> ); }, enableColumnFilter: true, - header: "User Identifier", - id: "user_identifier", + header: "Auth Identifier", + id: "auth_identifier", }), columnHelper.accessor("wallets", { cell: (cell) => { @@ -94,33 +131,24 @@ export function InAppWalletUsersPageContent( }), columnHelper.accessor("linkedAccounts", { cell: (cell) => { - const externalWallets = getExternalWallets(cell.getValue()); - if (externalWallets.length === 0) { - return None; + const email = getPrimaryEmail(cell.getValue()); + + if (!email) { + return N/A; } + return ( -
- {externalWallets.slice(0, 2).map((account) => { - const address = account.details?.address as string | undefined; - return address ? ( -
- -
- ) : null; - })} - {externalWallets.length > 2 && ( - - +{externalWallets.length - 2} more - - )} -
+ ); }, - header: "External Wallets", - id: "external_wallets", + header: "Email", + id: "email", }), columnHelper.accessor("wallets", { cell: (cell) => { @@ -230,20 +258,17 @@ export function InAppWalletUsersPageContent( }); const csv = Papa.unparse( usersWallets.map((row) => { - const externalWallets = getExternalWallets(row.linkedAccounts); - const externalWalletAddresses = externalWallets - .map((account) => account.details?.address) - .filter(Boolean) - .join(", "); + const email = getPrimaryEmail(row.linkedAccounts); return { address: row.wallets[0]?.address || "Uninitialized", created: row.wallets[0]?.createdAt ? new Date(row.wallets[0].createdAt).toISOString() : "Wallet not created yet", - external_wallets: externalWalletAddresses || "None", + email: email || "N/A", login_methods: row.linkedAccounts.map((acc) => acc.type).join(", "), - user_identifier: getUserIdentifier(row.linkedAccounts), + auth_identifier: getAuthIdentifier(row.linkedAccounts) || "N/A", + user_identifier: row.id || "N/A", }; }), ); diff --git a/apps/dashboard/src/@/components/in-app-wallet-users-content/searchUsers.ts b/apps/dashboard/src/@/components/in-app-wallet-users-content/searchUsers.ts index 36fe6882b5e..918016a200a 100644 --- a/apps/dashboard/src/@/components/in-app-wallet-users-content/searchUsers.ts +++ b/apps/dashboard/src/@/components/in-app-wallet-users-content/searchUsers.ts @@ -18,10 +18,10 @@ configure({ type APIWallet = ListUserWalletsResponses[200]["result"]["wallets"][0]; type APIProfile = APIWallet["profiles"][0]; -// Transform API response to match existing WalletUser format +// Transform API response to match wallet user format function transformToWalletUser(apiWallet: APIWallet): WalletUser { return { - id: getProfileId(apiWallet.profiles[0]) || "", + id: apiWallet.userId || getProfileId(apiWallet.profiles[0]) || "", linkedAccounts: apiWallet.profiles.map((profile) => { // Create details object based on the profile data let details: @@ -105,6 +105,9 @@ export async function searchUsers( case "externalWallet": queryParams.externalWalletAddress = query; break; + case "userId": + queryParams.userId = query; + break; } // Use the generated API function with Bearer authentication diff --git a/apps/dashboard/src/@/components/in-app-wallet-users-content/types.ts b/apps/dashboard/src/@/components/in-app-wallet-users-content/types.ts index fa825d6a8d1..d30927c0880 100644 --- a/apps/dashboard/src/@/components/in-app-wallet-users-content/types.ts +++ b/apps/dashboard/src/@/components/in-app-wallet-users-content/types.ts @@ -3,4 +3,5 @@ export type SearchType = | "phone" | "id" | "address" - | "externalWallet"; + | "externalWallet" + | "userId"; diff --git a/apps/dashboard/src/@/hooks/useEmbeddedWallets.ts b/apps/dashboard/src/@/hooks/useEmbeddedWallets.ts index 1ca70f938a4..20ab3a0e34a 100644 --- a/apps/dashboard/src/@/hooks/useEmbeddedWallets.ts +++ b/apps/dashboard/src/@/hooks/useEmbeddedWallets.ts @@ -76,10 +76,10 @@ const fetchAccountList = ({ }; }; -// Transform API response to match existing WalletUser format +// Transform API response to match the wallet user format function transformToWalletUser(apiWallet: APIWallet): WalletUser { return { - id: getProfileId(apiWallet.profiles[0]) || "", + id: apiWallet.userId || getProfileId(apiWallet.profiles[0]) || "", linkedAccounts: apiWallet.profiles.map((profile) => { // Create details object based on the profile data let details: diff --git a/packages/api/src/client/sdk.gen.ts b/packages/api/src/client/sdk.gen.ts index 013925ca486..56bebbd45a9 100644 --- a/packages/api/src/client/sdk.gen.ts +++ b/packages/api/src/client/sdk.gen.ts @@ -244,6 +244,7 @@ export const initiateAuthentication = ( * - `isNewUser` - Whether this is a new wallet creation * - `token` - JWT token for authenticated API requests * - `type` - The authentication method used + * - `userId` - Unique identifier for the authenticated user * - `walletAddress` - Your new or existing wallet address * * **Next step - Verify your token:** @@ -442,6 +443,7 @@ export const socialAuthentication = ( * Retrieve the authenticated user's wallet information including wallet addresses and linked authentication wallets. This endpoint provides comprehensive user data for the currently authenticated session. * * **Returns:** + * - userId - Unique identifier for this wallet in thirdweb auth * - Primary wallet address * - Smart wallet address (if available) * - Wallet creation timestamp diff --git a/packages/api/src/client/types.gen.ts b/packages/api/src/client/types.gen.ts index 2a68b900335..7c7f6ba61b3 100644 --- a/packages/api/src/client/types.gen.ts +++ b/packages/api/src/client/types.gen.ts @@ -330,6 +330,10 @@ export type CompleteAuthenticationResponses = { * Type of authentication completed */ type: string; + /** + * Unique identifier for the authenticated user + */ + userId: string; /** * The wallet address */ @@ -875,6 +879,10 @@ export type GetMyWalletResponses = { */ 200: { result: { + /** + * Unique identifier for the user wallet within the thirdweb auth system. + */ + userId?: string; /** * The EOA (Externally Owned Wallet) address of the wallet. This is the traditional wallet address. */ @@ -1076,6 +1084,10 @@ export type ListUserWalletsData = { address?: string; externalWalletAddress?: string; id?: string; + /** + * Filter results by the unique user identifier returned by auth flows. + */ + userId?: string; }; url: "/v1/wallets/user"; }; @@ -1122,6 +1134,10 @@ export type ListUserWalletsResponses = { * Array of user wallets */ wallets: Array<{ + /** + * Unique identifier for the user wallet within the thirdweb auth system. + */ + userId?: string; /** * The EOA (Externally Owned Wallet) address of the wallet. This is the traditional wallet address. */ @@ -1362,6 +1378,10 @@ export type CreateUserWalletResponses = { */ 200: { result: { + /** + * Unique identifier for the user wallet within the thirdweb auth system. + */ + userId?: string; /** * The EOA (Externally Owned Wallet) address of the wallet. This is the traditional wallet address. */ @@ -1604,6 +1624,10 @@ export type ListServerWalletsResponses = { * Array of server wallets */ wallets: Array<{ + /** + * Unique identifier for the user wallet within the thirdweb auth system. + */ + userId?: string; /** * The EOA (Externally Owned Wallet) address of the wallet. This is the traditional wallet address. */ @@ -1831,6 +1855,10 @@ export type CreateServerWalletResponses = { */ 200: { result: { + /** + * Unique identifier for the user wallet within the thirdweb auth system. + */ + userId?: string; /** * The EOA (Externally Owned Wallet) address of the wallet. This is the traditional wallet address. */