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
1 change: 1 addition & 0 deletions apps/dashboard/knip.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
],
"ignoreBinaries": ["only-allow"],
"ignoreDependencies": [
"@thirdweb-dev/api",
"@thirdweb-dev/service-utils",
"@thirdweb-dev/vault-sdk",
"thirdweb",
Expand Down
1 change: 1 addition & 0 deletions apps/dashboard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@shazow/whatsabi": "0.22.2",
"@tanstack/react-query": "5.81.5",
"@tanstack/react-table": "^8.21.3",
"@thirdweb-dev/api": "workspace:*",
"@thirdweb-dev/service-utils": "workspace:*",
"@thirdweb-dev/vault-sdk": "workspace:*",
"@vercel/functions": "2.2.2",
Expand Down
Original file line number Diff line number Diff line change
@@ -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 { THIRDWEB_EWS_API_HOST } from "@/constants/urls";
import { THIRDWEB_API_HOST } from "@/constants/urls";
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(
authToken: string,
clientId: string | undefined,
Expand All @@ -10,50 +82,57 @@ export async function searchUsers(
searchType: SearchType,
query: string,
): 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
if (ecosystemSlug) {
url.searchParams.append("ecosystemSlug", `ecosystem.${ecosystemSlug}`);
} else if (clientId) {
url.searchParams.append("clientId", clientId);
}
// Add search parameter based on search type
switch (searchType) {
case "email":
queryParams.email = query;
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;
}

// 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;
}
// 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}`,
}),
},
});

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}`,
);
}
// Handle response
if (response.error || !response.data) {
console.error(
"Error searching users:",
response.error || "No data returned",
);
return [];
}

const data = await response.json();
return data.users as WalletUser[];
return response.data.result.wallets.map(transformToWalletUser);
} catch (error) {
console.error("Error searching users:", error);
return [];
}
}
5 changes: 2 additions & 3 deletions apps/dashboard/src/@/constants/urls.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export const THIRDWEB_EWS_API_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_EWS_API_HOST ||
"https://in-app-wallet.thirdweb.com";
export const THIRDWEB_API_HOST =
process.env.NEXT_PUBLIC_THIRDWEB_API_HOST || "https://api.thirdweb-dev.com";

export const THIRDWEB_PAY_DOMAIN =
process.env.NEXT_PUBLIC_PAY_URL || "pay.thirdweb-dev.com";
Expand Down
141 changes: 113 additions & 28 deletions apps/dashboard/src/@/hooks/useEmbeddedWallets.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
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 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";

// 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 = ({
jwt,
clientId,
Expand All @@ -18,37 +34,104 @@ const fetchAccountList = ({
pageNumber: number;
}) => {
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
if (ecosystemSlug) {
url.searchParams.append("ecosystemSlug", `ecosystem.${ecosystemSlug}`);
} else if (clientId) {
url.searchParams.append("clientId", clientId);
}
// Use the generated API function with Bearer authentication
const response = await listUserWallets({
query: queryParams,
headers: {
Authorization: `Bearer ${jwt}`,
"Content-Type": "application/json",
"x-thirdweb-team-id": teamId,
...(clientId && { "x-client-id": clientId }),
...(ecosystemSlug && {
"x-ecosystem-id": `ecosystem.${ecosystemSlug}`,
}),
},
});

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()}`);
}
// Handle response
if (response.error || !response.data) {
const errorMessage =
typeof response.error === "string"
? response.error
: "No data returned";
throw new Error(errorMessage);
}

const json = await res.json();
return json as {
users: WalletUser[];
};
// 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;
}
};
};

// 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: {
clientId?: string;
ecosystemSlug?: string;
Expand Down Expand Up @@ -98,6 +181,7 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
while (true) {
const res = await queryClient.fetchQuery<{
users: WalletUser[];
hasMore: boolean;
}>({
queryFn: fetchAccountList({
clientId,
Expand All @@ -113,12 +197,13 @@ export function useAllEmbeddedWallets(params: { authToken: string }) {
),
});

if (res.users.length === 0) {
responses.push(...res.users);

if (!res.hasMore) {
break;
}

page++;
responses.push(...res.users);
}

return responses;
Expand Down
Loading
Loading