Refactor wallet user API integration to use tw api (#7987)

This commit is contained in:
Firekeeper
2025-09-05 08:09:24 +07:00
committed by GitHub
parent bb89561a1b
commit 22d736cb24
8 changed files with 4390 additions and 2703 deletions
+1
View File
@@ -8,6 +8,7 @@
], ],
"ignoreBinaries": ["only-allow"], "ignoreBinaries": ["only-allow"],
"ignoreDependencies": [ "ignoreDependencies": [
"@thirdweb-dev/api",
"@thirdweb-dev/service-utils", "@thirdweb-dev/service-utils",
"@thirdweb-dev/vault-sdk", "@thirdweb-dev/vault-sdk",
"thirdweb", "thirdweb",
+1
View File
@@ -23,6 +23,7 @@
"@shazow/whatsabi": "0.22.2", "@shazow/whatsabi": "0.22.2",
"@tanstack/react-query": "5.81.5", "@tanstack/react-query": "5.81.5",
"@tanstack/react-table": "^8.21.3", "@tanstack/react-table": "^8.21.3",
"@thirdweb-dev/api": "workspace:*",
"@thirdweb-dev/service-utils": "workspace:*", "@thirdweb-dev/service-utils": "workspace:*",
"@thirdweb-dev/vault-sdk": "workspace:*", "@thirdweb-dev/vault-sdk": "workspace:*",
"@vercel/functions": "2.2.2", "@vercel/functions": "2.2.2",
@@ -1,7 +1,79 @@
import type {
ListUserWalletsData,
ListUserWalletsResponses,
} from "@thirdweb-dev/api";
import { configure, listUserWallets } from "@thirdweb-dev/api";
import type { WalletUser } from "thirdweb/wallets"; import type { WalletUser } from "thirdweb/wallets";
import { THIRDWEB_EWS_API_HOST } from "@/constants/urls"; import { THIRDWEB_API_HOST } from "@/constants/urls";
import type { SearchType } from "./types"; import type { SearchType } from "./types";
// Configure the API client to use the correct base URL
configure({
override: {
baseUrl: THIRDWEB_API_HOST,
},
});
// Extract types from the generated API
type APIWallet = ListUserWalletsResponses[200]["result"]["wallets"][0];
type APIProfile = APIWallet["profiles"][0];
// Transform API response to match existing WalletUser format
function transformToWalletUser(apiWallet: APIWallet): WalletUser {
return {
id: getProfileId(apiWallet.profiles[0]) || "",
linkedAccounts: apiWallet.profiles.map((profile) => {
// Create details object based on the profile data
let details:
| { email: string; [key: string]: string }
| { phone: string; [key: string]: string }
| { address: string; [key: string]: string }
| { id: string; [key: string]: string };
const profileId = getProfileId(profile);
if ("email" in profile && profile.email) {
details = { email: profile.email, id: profileId };
} else if ("phone" in profile && profile.phone) {
details = { phone: profile.phone, id: profileId };
} else if ("walletAddress" in profile && profile.walletAddress) {
details = { address: profile.walletAddress, id: profileId };
} else {
details = { id: profileId };
}
return {
type: profile.type,
details,
};
}),
wallets: apiWallet.address
? [
{
address: apiWallet.address,
createdAt: apiWallet.createdAt || new Date().toISOString(),
type: "enclave" as const,
},
]
: [],
};
}
// Helper function to safely get ID from any profile type
function getProfileId(profile: APIProfile | undefined): string {
if (!profile) return "";
if ("id" in profile) {
return profile.id;
} else if ("credentialId" in profile) {
return profile.credentialId;
} else if ("identifier" in profile) {
return profile.identifier;
}
return "";
}
export async function searchUsers( export async function searchUsers(
authToken: string, authToken: string,
clientId: string | undefined, clientId: string | undefined,
@@ -10,50 +82,57 @@ export async function searchUsers(
searchType: SearchType, searchType: SearchType,
query: string, query: string,
): Promise<WalletUser[]> { ): Promise<WalletUser[]> {
const url = new URL(`${THIRDWEB_EWS_API_HOST}/api/2024-05-05/account/list`); try {
// Prepare query parameters
const queryParams: ListUserWalletsData["query"] = {
limit: 50,
};
// Add clientId or ecosystemSlug parameter // Add search parameter based on search type
if (ecosystemSlug) { switch (searchType) {
url.searchParams.append("ecosystemSlug", `ecosystem.${ecosystemSlug}`); case "email":
} else if (clientId) { queryParams.email = query;
url.searchParams.append("clientId", clientId); break;
case "phone":
queryParams.phone = query;
break;
case "id":
queryParams.id = query;
break;
case "address":
queryParams.address = query;
break;
case "externalWallet":
queryParams.externalWalletAddress = query;
break;
}
// Use the generated API function with Bearer authentication
const response = await listUserWallets({
query: queryParams,
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-thirdweb-team-id": teamId,
...(clientId && { "x-client-id": clientId }),
...(ecosystemSlug && {
"x-ecosystem-id": `ecosystem.${ecosystemSlug}`,
}),
},
});
// Handle response
if (response.error || !response.data) {
console.error(
"Error searching users:",
response.error || "No data returned",
);
return [];
}
return response.data.result.wallets.map(transformToWalletUser);
} catch (error) {
console.error("Error searching users:", error);
return [];
} }
// Add search parameter based on search type
switch (searchType) {
case "email":
url.searchParams.append("email", query);
break;
case "phone":
url.searchParams.append("phone", query);
break;
case "id":
url.searchParams.append("id", query);
break;
case "address":
url.searchParams.append("address", query);
break;
case "externalWallet":
url.searchParams.append("externalWalletAddress", query);
break;
}
const response = await fetch(url.toString(), {
headers: {
Authorization: `Bearer ${authToken}`,
"Content-Type": "application/json",
"x-thirdweb-team-id": teamId,
...(clientId && { "x-client-id": clientId }),
},
method: "GET",
});
if (!response.ok) {
throw new Error(
`Failed to search users: ${response.status} ${response.statusText}`,
);
}
const data = await response.json();
return data.users as WalletUser[];
} }
+2 -3
View File
@@ -1,6 +1,5 @@
export const THIRDWEB_EWS_API_HOST = export const THIRDWEB_API_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_EWS_API_HOST || process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb-dev.com";
"https://in-app-wallet.thirdweb.com";
export const THIRDWEB_PAY_DOMAIN = export const THIRDWEB_PAY_DOMAIN =
process.env.NEXT_PUBLIC_PAY_URL || "pay.thirdweb-dev.com"; process.env.NEXT_PUBLIC_PAY_URL || "pay.thirdweb-dev.com";
+114 -29
View File
@@ -1,9 +1,25 @@
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import type {
ListUserWalletsData,
ListUserWalletsResponses,
} from "@thirdweb-dev/api";
import { configure, listUserWallets } from "@thirdweb-dev/api";
import { useActiveAccount } from "thirdweb/react"; import { useActiveAccount } from "thirdweb/react";
import type { WalletUser } from "thirdweb/wallets"; import type { WalletUser } from "thirdweb/wallets";
import { THIRDWEB_EWS_API_HOST } from "@/constants/urls"; import { THIRDWEB_API_HOST } from "@/constants/urls";
import { embeddedWalletsKeys } from "../query-keys/cache-keys"; import { embeddedWalletsKeys } from "../query-keys/cache-keys";
// Configure the API client to use the correct base URL
configure({
override: {
baseUrl: THIRDWEB_API_HOST,
},
});
// Extract types from the generated API
type APIWallet = ListUserWalletsResponses[200]["result"]["wallets"][0];
type APIProfile = APIWallet["profiles"][0];
const fetchAccountList = ({ const fetchAccountList = ({
jwt, jwt,
clientId, clientId,
@@ -18,37 +34,104 @@ const fetchAccountList = ({
pageNumber: number; pageNumber: number;
}) => { }) => {
return async () => { return async () => {
const url = new URL(`${THIRDWEB_EWS_API_HOST}/api/2024-05-05/account/list`); try {
// Prepare query parameters for the new API
const queryParams: ListUserWalletsData["query"] = {
page: pageNumber,
limit: 50, // Keep the same page size
};
// Add clientId or ecosystemSlug parameter // Use the generated API function with Bearer authentication
if (ecosystemSlug) { const response = await listUserWallets({
url.searchParams.append("ecosystemSlug", `ecosystem.${ecosystemSlug}`); query: queryParams,
} else if (clientId) { headers: {
url.searchParams.append("clientId", clientId); Authorization: `Bearer ${jwt}`,
"Content-Type": "application/json",
"x-thirdweb-team-id": teamId,
...(clientId && { "x-client-id": clientId }),
...(ecosystemSlug && {
"x-ecosystem-id": `ecosystem.${ecosystemSlug}`,
}),
},
});
// Handle response
if (response.error || !response.data) {
const errorMessage =
typeof response.error === "string"
? response.error
: "No data returned";
throw new Error(errorMessage);
}
// Transform the response to match the expected format
return {
users: response.data.result.wallets.map(transformToWalletUser),
hasMore: response.data.result.pagination.hasMore ?? false,
};
} catch (error) {
console.error("Failed to fetch wallets:", error);
throw error;
} }
url.searchParams.append("page", pageNumber.toString());
const res = await fetch(url.href, {
headers: {
Authorization: `Bearer ${jwt}`,
"Content-Type": "application/json",
"x-thirdweb-team-id": teamId,
...(clientId && { "x-client-id": clientId }),
},
method: "GET",
});
if (!res.ok) {
throw new Error(`Failed to fetch wallets: ${await res.text()}`);
}
const json = await res.json();
return json as {
users: WalletUser[];
};
}; };
}; };
// Transform API response to match existing WalletUser format
function transformToWalletUser(apiWallet: APIWallet): WalletUser {
return {
id: getProfileId(apiWallet.profiles[0]) || "",
linkedAccounts: apiWallet.profiles.map((profile) => {
// Create details object based on the profile data
let details:
| { email: string; [key: string]: string }
| { phone: string; [key: string]: string }
| { address: string; [key: string]: string }
| { id: string; [key: string]: string };
const profileId = getProfileId(profile);
if ("email" in profile && profile.email) {
details = { email: profile.email, id: profileId };
} else if ("phone" in profile && profile.phone) {
details = { phone: profile.phone, id: profileId };
} else if ("walletAddress" in profile && profile.walletAddress) {
details = { address: profile.walletAddress, id: profileId };
} else {
details = { id: profileId };
}
return {
type: profile.type,
details,
};
}),
wallets: apiWallet.address
? [
{
address: apiWallet.address,
createdAt: apiWallet.createdAt || new Date().toISOString(),
type: "enclave" as const,
},
]
: [],
};
}
// Helper function to safely get ID from any profile type
function getProfileId(profile: APIProfile | undefined): string {
if (!profile) return "";
if ("id" in profile) {
return profile.id;
} else if ("credentialId" in profile) {
return profile.credentialId;
} else if ("identifier" in profile) {
return profile.identifier;
}
return "";
}
export function useEmbeddedWallets(params: { export function useEmbeddedWallets(params: {
clientId?: string; clientId?: string;
ecosystemSlug?: string; ecosystemSlug?: string;
@@ -98,6 +181,7 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
while (true) { while (true) {
const res = await queryClient.fetchQuery<{ const res = await queryClient.fetchQuery<{
users: WalletUser[]; users: WalletUser[];
hasMore: boolean;
}>({ }>({
queryFn: fetchAccountList({ queryFn: fetchAccountList({
clientId, clientId,
@@ -113,12 +197,13 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
), ),
}); });
if (res.users.length === 0) { responses.push(...res.users);
if (!res.hasMore) {
break; break;
} }
page++; page++;
responses.push(...res.users);
} }
return responses; return responses;
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+5 -2
View File
@@ -130,6 +130,9 @@ importers:
'@tanstack/react-table': '@tanstack/react-table':
specifier: ^8.21.3 specifier: ^8.21.3
version: 8.21.3([email protected]([email protected]))([email protected]) version: 8.21.3([email protected]([email protected]))([email protected])
'@thirdweb-dev/api':
specifier: workspace:*
version: link:../../packages/api
'@thirdweb-dev/service-utils': '@thirdweb-dev/service-utils':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/service-utils version: link:../../packages/service-utils
@@ -31301,7 +31304,7 @@ snapshots:
[email protected]: [email protected]:
dependencies: dependencies:
'@babel/core': 7.28.0 '@babel/core': 7.28.0
'@babel/parser': 7.28.0 '@babel/parser': 7.28.3
'@istanbuljs/schema': 0.1.3 '@istanbuljs/schema': 0.1.3
istanbul-lib-coverage: 3.2.2 istanbul-lib-coverage: 3.2.2
semver: 7.7.2 semver: 7.7.2
@@ -32343,7 +32346,7 @@ snapshots:
dependencies: dependencies:
'@babel/core': 7.28.0 '@babel/core': 7.28.0
'@babel/generator': 7.28.0 '@babel/generator': 7.28.0
'@babel/parser': 7.28.0 '@babel/parser': 7.28.3
'@babel/types': 7.28.2 '@babel/types': 7.28.2
flow-enums-runtime: 0.0.6 flow-enums-runtime: 0.0.6
metro: 0.81.4([email protected])([email protected]) metro: 0.81.4([email protected])([email protected])