Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d635dc266 | ||
|
|
1083316ef6 | ||
|
|
2c1ec47a04 | ||
|
|
e01af9db71 | ||
|
|
fd7e10d3de | ||
|
|
3126d87740 | ||
|
|
b9e63ff0a7 | ||
|
|
77053049a5 |
@@ -13,7 +13,6 @@ import type {
|
||||
UniversalBridgeWalletStats,
|
||||
UserOpStats,
|
||||
WalletStats,
|
||||
WalletUserStats,
|
||||
WebhookLatencyStats,
|
||||
WebhookRequestStats,
|
||||
WebhookSummaryStats,
|
||||
@@ -147,7 +146,13 @@ const cached_getWalletConnections = unstable_cache(
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
return json.data as WalletStats[];
|
||||
return (json.data as WalletStats[]).filter(
|
||||
(w) =>
|
||||
w.walletType !== "smart" &&
|
||||
w.walletType !== "smartWallet" &&
|
||||
w.walletType !== "inApp" &&
|
||||
w.walletType !== "inAppWallet",
|
||||
);
|
||||
},
|
||||
["getWalletConnections"],
|
||||
{
|
||||
@@ -398,44 +403,6 @@ export function getRpcUsageByType(
|
||||
return cached_getRpcUsageByType(normalizedParams(params), authToken);
|
||||
}
|
||||
|
||||
const cached_getWalletUsers = unstable_cache(
|
||||
async (
|
||||
params: AnalyticsQueryParams,
|
||||
authToken: string,
|
||||
): Promise<WalletUserStats[]> => {
|
||||
const searchParams = buildSearchParams(params);
|
||||
const res = await fetchAnalytics({
|
||||
authToken,
|
||||
url: `v2/sdk/wallet-connects/users?${searchParams.toString()}`,
|
||||
init: {
|
||||
method: "GET",
|
||||
},
|
||||
});
|
||||
|
||||
if (res?.status !== 200) {
|
||||
const reason = await res?.text();
|
||||
console.error(
|
||||
`Failed to fetch wallet user stats: ${res?.status} - ${res.statusText} - ${reason}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
|
||||
const json = await res.json();
|
||||
return json.data as WalletUserStats[];
|
||||
},
|
||||
["getWalletUsers"],
|
||||
{
|
||||
revalidate: 60 * 60, // 1 hour
|
||||
},
|
||||
);
|
||||
|
||||
export function getWalletUsers(
|
||||
params: AnalyticsQueryParams,
|
||||
authToken: string,
|
||||
) {
|
||||
return cached_getWalletUsers(normalizedParams(params), authToken);
|
||||
}
|
||||
|
||||
type ActiveStatus = {
|
||||
bundler: boolean;
|
||||
storage: boolean;
|
||||
|
||||
@@ -58,6 +58,7 @@ interface WebhookSingleResponse {
|
||||
}
|
||||
|
||||
interface TestWebhookPayload {
|
||||
webhook_id?: string;
|
||||
webhook_url: string;
|
||||
type?: "event" | "transaction";
|
||||
}
|
||||
|
||||
@@ -86,7 +86,9 @@ export function getLastNDaysRange(id: DurationId) {
|
||||
throw new Error(`Invalid duration id: ${id}`);
|
||||
}
|
||||
|
||||
const todayDate = new Date(Date.now());
|
||||
const todayDate = new Date();
|
||||
todayDate.setDate(todayDate.getDate() + 1); // Move to next day
|
||||
todayDate.setHours(0, 0, 0, 0); // Set to start of next day (00:00)
|
||||
|
||||
const value: Range = {
|
||||
from: subDays(todayDate, durationInfo.days),
|
||||
|
||||
@@ -7,17 +7,11 @@ export interface WalletStats {
|
||||
walletType: string;
|
||||
}
|
||||
|
||||
export interface WalletUserStats {
|
||||
date: string;
|
||||
newUsers: number;
|
||||
returningUsers: number;
|
||||
totalUsers: number;
|
||||
}
|
||||
|
||||
export interface InAppWalletStats {
|
||||
date: string;
|
||||
authenticationMethod: string;
|
||||
uniqueWalletsConnected: number;
|
||||
newUsers: number;
|
||||
}
|
||||
|
||||
export interface EcosystemWalletStats extends InAppWalletStats {
|
||||
|
||||
@@ -40,6 +40,8 @@ export function TeamSidebarLayout(props: {
|
||||
icon: ChartNoAxesColumnIcon,
|
||||
label: "Analytics",
|
||||
},
|
||||
// ecosystem below here
|
||||
// TODO: make this one link to an overview page that has a list of all the ecosystems currently deployed
|
||||
{
|
||||
links: [
|
||||
...props.ecosystems.map((ecosystem) => ({
|
||||
@@ -66,29 +68,28 @@ export function TeamSidebarLayout(props: {
|
||||
icon: DatabaseIcon,
|
||||
label: "Usage",
|
||||
},
|
||||
...(props.chainSubscriptions.length > 0
|
||||
? [
|
||||
{
|
||||
separator: true,
|
||||
} as const,
|
||||
{
|
||||
links: [
|
||||
...props.chainSubscriptions.map((chainSubscription) => ({
|
||||
href: `${layoutPath}/~/infrastructure/${chainSubscription.slug}`,
|
||||
label: chainSubscription.chainName,
|
||||
})),
|
||||
{
|
||||
href: `${layoutPath}/~/infrastructure/deploy`,
|
||||
label: "Deploy Infrastructure",
|
||||
},
|
||||
],
|
||||
subMenu: {
|
||||
icon: WalletCardsIcon,
|
||||
label: "Chain Infrastucture",
|
||||
},
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
||||
{
|
||||
separator: true,
|
||||
} as const,
|
||||
// infrastructure below here
|
||||
// TODO: make this one link to an overview page that has a list of all the chains currently deployed
|
||||
{
|
||||
links: [
|
||||
...props.chainSubscriptions.map((chainSubscription) => ({
|
||||
href: `${layoutPath}/~/infrastructure/${chainSubscription.slug}`,
|
||||
label: chainSubscription.chainName,
|
||||
})),
|
||||
{
|
||||
href: `${layoutPath}/~/infrastructure/deploy`,
|
||||
label: "Deploy Infrastructure",
|
||||
},
|
||||
],
|
||||
subMenu: {
|
||||
icon: WalletCardsIcon,
|
||||
label: "Chain Infrastucture",
|
||||
},
|
||||
},
|
||||
]}
|
||||
footerSidebarLinks={[
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { subDays } from "date-fns";
|
||||
import { redirect } from "next/navigation";
|
||||
import { getWalletConnections } from "@/api/analytics";
|
||||
import { getInAppWalletUsage } from "@/api/analytics";
|
||||
import { getAuthToken } from "@/api/auth-token";
|
||||
import { getProjects, type Project } from "@/api/project/projects";
|
||||
import { getTeamBySlug } from "@/api/team/get-team";
|
||||
@@ -90,7 +90,8 @@ async function getProjectsWithAnalytics(
|
||||
const today = new Date();
|
||||
const thirtyDaysAgo = subDays(today, 30);
|
||||
|
||||
const data = await getWalletConnections(
|
||||
// TODO (stats): also add the external wallet usage?
|
||||
const data = await getInAppWalletUsage(
|
||||
{
|
||||
from: thirtyDaysAgo,
|
||||
period: "all",
|
||||
|
||||
+36
-23
@@ -2,7 +2,7 @@
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { useSetResponsiveSearchParams } from "responsive-rsc";
|
||||
import type { UniversalBridgeStats, WalletUserStats } from "@/types/analytics";
|
||||
import type { InAppWalletStats, UniversalBridgeStats } from "@/types/analytics";
|
||||
import { CombinedBarChartCard } from "../../../../components/Analytics/CombinedBarChartCard";
|
||||
import { EmptyStateContent } from "../../../../components/Analytics/EmptyStateCard";
|
||||
|
||||
@@ -19,7 +19,7 @@ export function TeamHighlightsCard({
|
||||
selectedChart,
|
||||
selectedChartQueryParam,
|
||||
}: {
|
||||
userStats: WalletUserStats[];
|
||||
userStats: InAppWalletStats[];
|
||||
volumeStats: UniversalBridgeStats[];
|
||||
selectedChart: string | undefined;
|
||||
selectedChartQueryParam: string;
|
||||
@@ -32,18 +32,6 @@ export function TeamHighlightsCard({
|
||||
|
||||
const chartConfig = {
|
||||
activeUsers: { color: "hsl(var(--chart-1))", label: "Active Users" },
|
||||
feesCollected: {
|
||||
color: "hsl(var(--chart-4))",
|
||||
emptyContent: (
|
||||
<EmptyStateContent
|
||||
description="Your apps haven't collected any fees yet."
|
||||
link={"https://portal.thirdweb.com/payments"}
|
||||
metric="Fees"
|
||||
/>
|
||||
),
|
||||
isCurrency: true,
|
||||
label: "Fee Revenue",
|
||||
},
|
||||
newUsers: { color: "hsl(var(--chart-3))", label: "New Users" },
|
||||
totalVolume: {
|
||||
color: "hsl(var(--chart-2))",
|
||||
@@ -57,6 +45,18 @@ export function TeamHighlightsCard({
|
||||
isCurrency: true,
|
||||
label: "Total Volume",
|
||||
},
|
||||
feesCollected: {
|
||||
color: "hsl(var(--chart-4))",
|
||||
emptyContent: (
|
||||
<EmptyStateContent
|
||||
description="Your apps haven't collected any fees yet."
|
||||
link={"https://portal.thirdweb.com/payments"}
|
||||
metric="Fees"
|
||||
/>
|
||||
),
|
||||
isCurrency: true,
|
||||
label: "Fee Revenue",
|
||||
},
|
||||
} as const;
|
||||
|
||||
return (
|
||||
@@ -96,33 +96,46 @@ type TimeSeriesMetrics = AggregatedMetrics & {
|
||||
};
|
||||
|
||||
function processTimeSeriesData(
|
||||
userStats: WalletUserStats[],
|
||||
userStats: InAppWalletStats[],
|
||||
volumeStats: UniversalBridgeStats[],
|
||||
): TimeSeriesMetrics[] {
|
||||
const metrics: TimeSeriesMetrics[] = [];
|
||||
const dates = [...new Set(userStats.map((a) => a.date))];
|
||||
|
||||
for (const date of dates) {
|
||||
const activeUsers = userStats
|
||||
.filter(
|
||||
(u) => new Date(u.date).toISOString() === new Date(date).toISOString(),
|
||||
)
|
||||
.reduce((acc, curr) => acc + curr.uniqueWalletsConnected, 0);
|
||||
|
||||
const newUsers = userStats
|
||||
.filter(
|
||||
(u) => new Date(u.date).toISOString() === new Date(date).toISOString(),
|
||||
)
|
||||
.reduce((acc, curr) => acc + curr.newUsers, 0);
|
||||
|
||||
for (const stat of userStats) {
|
||||
const volume = volumeStats
|
||||
.filter(
|
||||
(v) =>
|
||||
new Date(v.date).toISOString() ===
|
||||
new Date(stat.date).toISOString() && v.status === "completed",
|
||||
new Date(v.date).toISOString() === new Date(date).toISOString() &&
|
||||
v.status === "completed",
|
||||
)
|
||||
.reduce((acc, curr) => acc + curr.amountUsdCents / 100, 0);
|
||||
|
||||
const fees = volumeStats
|
||||
.filter(
|
||||
(v) =>
|
||||
new Date(v.date).toISOString() ===
|
||||
new Date(stat.date).toISOString() && v.status === "completed",
|
||||
new Date(v.date).toISOString() === new Date(date).toISOString() &&
|
||||
v.status === "completed",
|
||||
)
|
||||
.reduce((acc, curr) => acc + curr.developerFeeUsdCents / 100, 0);
|
||||
|
||||
metrics.push({
|
||||
activeUsers: stat.totalUsers ?? 0,
|
||||
date: stat.date,
|
||||
activeUsers: activeUsers,
|
||||
date: date,
|
||||
feesCollected: fees,
|
||||
newUsers: stat.newUsers ?? 0,
|
||||
newUsers: newUsers,
|
||||
totalVolume: volume,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,7 +13,6 @@ import {
|
||||
getUniversalBridgeUsage,
|
||||
getUserOpUsage,
|
||||
getWalletConnections,
|
||||
getWalletUsers,
|
||||
} from "@/api/analytics";
|
||||
import { getAuthToken } from "@/api/auth-token";
|
||||
import { getTeamBySlug } from "@/api/team/get-team";
|
||||
@@ -193,7 +192,7 @@ async function AsyncTeamHighlightsCard(props: {
|
||||
}) {
|
||||
const [walletUserStatsTimeSeries, universalBridgeUsage] =
|
||||
await Promise.allSettled([
|
||||
getWalletUsers(
|
||||
getInAppWalletUsage(
|
||||
{
|
||||
from: props.range.from,
|
||||
period: props.interval,
|
||||
@@ -216,7 +215,7 @@ async function AsyncTeamHighlightsCard(props: {
|
||||
if (
|
||||
walletUserStatsTimeSeries.status === "fulfilled" &&
|
||||
universalBridgeUsage.status === "fulfilled" &&
|
||||
walletUserStatsTimeSeries.value.some((w) => w.totalUsers !== 0)
|
||||
walletUserStatsTimeSeries.value.some((w) => w.uniqueWalletsConnected !== 0)
|
||||
) {
|
||||
return (
|
||||
<TeamHighlightsCard
|
||||
@@ -255,8 +254,8 @@ async function AsyncWalletDistributionCard(props: {
|
||||
<WalletDistributionCard data={walletConnections} />
|
||||
) : (
|
||||
<EmptyStateCard
|
||||
link="https://portal.thirdweb.com/wallets"
|
||||
metric="Wallets"
|
||||
link="https://portal.thirdweb.com/wallets/external-wallets"
|
||||
metric="External Wallets"
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -381,19 +380,17 @@ async function AsyncTotalSponsoredCard(props: {
|
||||
|
||||
async function WalletDistributionCard({ data }: { data: WalletStats[] }) {
|
||||
const formattedData = await Promise.all(
|
||||
data
|
||||
.filter((w) => w.walletType !== "smart" && w.walletType !== "smartWallet")
|
||||
.map(async (w) => {
|
||||
const wallet = await getWalletInfo(w.walletType as WalletId).catch(
|
||||
() => ({ name: w.walletType }),
|
||||
);
|
||||
return {
|
||||
totalConnections: w.totalConnections,
|
||||
uniqueWalletsConnected: w.uniqueWalletsConnected,
|
||||
walletName: wallet.name,
|
||||
walletType: w.walletType,
|
||||
};
|
||||
}),
|
||||
data.map(async (w) => {
|
||||
const wallet = await getWalletInfo(w.walletType as WalletId).catch(
|
||||
() => ({ name: w.walletType }),
|
||||
);
|
||||
return {
|
||||
totalConnections: w.totalConnections,
|
||||
uniqueWalletsConnected: w.uniqueWalletsConnected,
|
||||
walletName: wallet.name,
|
||||
walletType: w.walletType,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -404,7 +401,7 @@ async function WalletDistributionCard({ data }: { data: WalletStats[] }) {
|
||||
value: uniqueWalletsConnected,
|
||||
};
|
||||
})}
|
||||
title="Wallets Connected"
|
||||
title="External Wallets Connected"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+1
@@ -54,6 +54,7 @@ function ecosystemWalletStatsStub(
|
||||
date: formattedDate,
|
||||
ecosystemPartnerId: "123",
|
||||
uniqueWalletsConnected: Math.floor(Math.random() * 1000) + 1,
|
||||
newUsers: Math.floor(Math.random() * 100) + 1,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+37
-23
@@ -2,7 +2,7 @@
|
||||
|
||||
import { EmptyStateContent } from "app/(app)/team/components/Analytics/EmptyStateCard";
|
||||
import { useSetResponsiveSearchParams } from "responsive-rsc";
|
||||
import type { UniversalBridgeStats, WalletUserStats } from "@/types/analytics";
|
||||
import type { InAppWalletStats, UniversalBridgeStats } from "@/types/analytics";
|
||||
import { CombinedBarChartCard } from "../../../../components/Analytics/CombinedBarChartCard";
|
||||
|
||||
type AggregatedMetrics = {
|
||||
@@ -14,7 +14,7 @@ type AggregatedMetrics = {
|
||||
|
||||
export function ProjectHighlightsCard(props: {
|
||||
selectedChart: string | undefined;
|
||||
userStats: WalletUserStats[];
|
||||
userStats: InAppWalletStats[];
|
||||
volumeStats: UniversalBridgeStats[];
|
||||
teamSlug: string;
|
||||
projectSlug: string;
|
||||
@@ -34,18 +34,6 @@ export function ProjectHighlightsCard(props: {
|
||||
|
||||
const chartConfig = {
|
||||
activeUsers: { color: "hsl(var(--chart-1))", label: "Active Users" },
|
||||
feesCollected: {
|
||||
color: "hsl(var(--chart-4))",
|
||||
emptyContent: (
|
||||
<EmptyStateContent
|
||||
description="Your app hasn't collected any fees yet."
|
||||
link={`/team/${teamSlug}/${projectSlug}/payments/settings`}
|
||||
metric="Fees"
|
||||
/>
|
||||
),
|
||||
isCurrency: true,
|
||||
label: "Fee Revenue",
|
||||
},
|
||||
newUsers: { color: "hsl(var(--chart-3))", label: "New Users" },
|
||||
totalVolume: {
|
||||
color: "hsl(var(--chart-2))",
|
||||
@@ -59,6 +47,18 @@ export function ProjectHighlightsCard(props: {
|
||||
isCurrency: true,
|
||||
label: "Total Volume",
|
||||
},
|
||||
feesCollected: {
|
||||
color: "hsl(var(--chart-4))",
|
||||
emptyContent: (
|
||||
<EmptyStateContent
|
||||
description="Your app hasn't collected any fees yet."
|
||||
link={`/team/${teamSlug}/${projectSlug}/payments/settings`}
|
||||
metric="Fees"
|
||||
/>
|
||||
),
|
||||
isCurrency: true,
|
||||
label: "Fee Revenue",
|
||||
},
|
||||
} as const;
|
||||
|
||||
return (
|
||||
@@ -101,33 +101,47 @@ type TimeSeriesMetrics = AggregatedMetrics & {
|
||||
* Processes time series data to combine wallet and user statistics
|
||||
*/
|
||||
function processTimeSeriesData(
|
||||
userStats: WalletUserStats[],
|
||||
userStats: InAppWalletStats[],
|
||||
volumeStats: UniversalBridgeStats[],
|
||||
): TimeSeriesMetrics[] {
|
||||
const metrics: TimeSeriesMetrics[] = [];
|
||||
const dates = [
|
||||
...new Set([
|
||||
...userStats.map((a) => new Date(a.date).toISOString().slice(0, 10)),
|
||||
...volumeStats.map((a) => new Date(a.date).toISOString().slice(0, 10)),
|
||||
]),
|
||||
];
|
||||
|
||||
for (const date of dates) {
|
||||
const activeUsers = userStats
|
||||
.filter((u) => new Date(u.date).toISOString().slice(0, 10) === date)
|
||||
.reduce((acc, curr) => acc + curr.uniqueWalletsConnected, 0);
|
||||
|
||||
const newUsers = userStats
|
||||
.filter((u) => new Date(u.date).toISOString().slice(0, 10) === date)
|
||||
.reduce((acc, curr) => acc + curr.newUsers, 0);
|
||||
|
||||
for (const stat of userStats) {
|
||||
const volume = volumeStats
|
||||
.filter(
|
||||
(v) =>
|
||||
new Date(v.date).toISOString() ===
|
||||
new Date(stat.date).toISOString() && v.status === "completed",
|
||||
new Date(v.date).toISOString().slice(0, 10) === date &&
|
||||
v.status === "completed",
|
||||
)
|
||||
.reduce((acc, curr) => acc + curr.amountUsdCents / 100, 0);
|
||||
|
||||
const fees = volumeStats
|
||||
.filter(
|
||||
(v) =>
|
||||
new Date(v.date).toISOString() ===
|
||||
new Date(stat.date).toISOString() && v.status === "completed",
|
||||
new Date(v.date).toISOString().slice(0, 10) === date &&
|
||||
v.status === "completed",
|
||||
)
|
||||
.reduce((acc, curr) => acc + curr.developerFeeUsdCents / 100, 0);
|
||||
|
||||
metrics.push({
|
||||
activeUsers: stat.totalUsers ?? 0,
|
||||
date: stat.date,
|
||||
activeUsers: activeUsers,
|
||||
date: date,
|
||||
feesCollected: fees,
|
||||
newUsers: stat.newUsers ?? 0,
|
||||
newUsers: newUsers,
|
||||
totalVolume: volume,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import {
|
||||
getUniversalBridgeUsage,
|
||||
getUserOpUsage,
|
||||
getWalletConnections,
|
||||
getWalletUsers,
|
||||
isProjectActive,
|
||||
} from "@/api/analytics";
|
||||
import { getAuthToken } from "@/api/auth-token";
|
||||
@@ -364,7 +363,7 @@ async function AsyncAppHighlightsCard(props: {
|
||||
}) {
|
||||
const [walletUserStatsTimeSeries, universalBridgeUsage] =
|
||||
await Promise.allSettled([
|
||||
getWalletUsers(
|
||||
getInAppWalletUsage(
|
||||
{
|
||||
from: props.range.from,
|
||||
period: props.interval,
|
||||
@@ -389,7 +388,7 @@ async function AsyncAppHighlightsCard(props: {
|
||||
if (
|
||||
walletUserStatsTimeSeries.status === "fulfilled" &&
|
||||
universalBridgeUsage.status === "fulfilled"
|
||||
)
|
||||
) {
|
||||
return (
|
||||
<div>
|
||||
<ProjectHighlightsCard
|
||||
@@ -410,6 +409,7 @@ async function AsyncAppHighlightsCard(props: {
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EmptyStateCard
|
||||
@@ -439,27 +439,25 @@ async function AsyncWalletDistributionCard(props: {
|
||||
<WalletDistributionCard data={walletConnections} />
|
||||
) : (
|
||||
<EmptyStateCard
|
||||
link="https://portal.thirdweb.com/wallets"
|
||||
metric="Wallets"
|
||||
link="https://portal.thirdweb.com/wallets/external-wallets"
|
||||
metric="External Wallets"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
async function WalletDistributionCard({ data }: { data: WalletStats[] }) {
|
||||
const formattedData = await Promise.all(
|
||||
data
|
||||
.filter((w) => w.walletType !== "smart" && w.walletType !== "smartWallet")
|
||||
.map(async (w) => {
|
||||
const wallet = await getWalletInfo(w.walletType as WalletId).catch(
|
||||
() => ({ name: w.walletType }),
|
||||
);
|
||||
return {
|
||||
totalConnections: w.totalConnections,
|
||||
uniqueWalletsConnected: w.uniqueWalletsConnected,
|
||||
walletName: wallet.name,
|
||||
walletType: w.walletType,
|
||||
};
|
||||
}),
|
||||
data.map(async (w) => {
|
||||
const wallet = await getWalletInfo(w.walletType as WalletId).catch(
|
||||
() => ({ name: w.walletType }),
|
||||
);
|
||||
return {
|
||||
totalConnections: w.totalConnections,
|
||||
uniqueWalletsConnected: w.uniqueWalletsConnected,
|
||||
walletName: wallet.name,
|
||||
walletType: w.walletType,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
return (
|
||||
@@ -470,7 +468,7 @@ async function WalletDistributionCard({ data }: { data: WalletStats[] }) {
|
||||
value: uniqueWalletsConnected,
|
||||
};
|
||||
})}
|
||||
title="Wallets Connected"
|
||||
title="External Wallets Connected"
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
+1
@@ -120,6 +120,7 @@ function createInAppWalletStatsStub(days: number): InAppWalletStats[] {
|
||||
authenticationMethod: pickRandomAuthMethod(),
|
||||
date: new Date(2024, 11, d).toLocaleString(),
|
||||
uniqueWalletsConnected: uniqueWallets,
|
||||
newUsers: Math.floor(Math.random() * 100) + 1,
|
||||
});
|
||||
|
||||
if (Math.random() > 0.7) {
|
||||
|
||||
+22
-53
@@ -1,48 +1,35 @@
|
||||
import { subDays } from "date-fns";
|
||||
import { ActivityIcon, UserIcon } from "lucide-react";
|
||||
import { Suspense } from "react";
|
||||
import { getInAppWalletUsage } from "@/api/analytics";
|
||||
import type { Range } from "@/components/analytics/date-range-selector";
|
||||
import { StatCard } from "@/components/analytics/stat";
|
||||
import type { InAppWalletStats } from "@/types/analytics";
|
||||
|
||||
function InAppWalletsSummaryInner(props: {
|
||||
allTimeStats: InAppWalletStats[] | undefined;
|
||||
monthlyStats: InAppWalletStats[] | undefined;
|
||||
stats: InAppWalletStats[] | undefined;
|
||||
isPending: boolean;
|
||||
}) {
|
||||
const allTimeStats = props.allTimeStats?.reduce(
|
||||
(acc, curr) => {
|
||||
acc.uniqueWalletsConnected += curr.uniqueWalletsConnected;
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
uniqueWalletsConnected: 0,
|
||||
},
|
||||
);
|
||||
const activeUsers = props.stats?.reduce((acc, curr) => {
|
||||
return acc + curr.uniqueWalletsConnected;
|
||||
}, 0);
|
||||
|
||||
const monthlyStats = props.monthlyStats?.reduce(
|
||||
(acc, curr) => {
|
||||
acc.uniqueWalletsConnected += curr.uniqueWalletsConnected;
|
||||
return acc;
|
||||
},
|
||||
{
|
||||
uniqueWalletsConnected: 0,
|
||||
},
|
||||
);
|
||||
const newUsers = props.stats?.reduce((acc, curr) => {
|
||||
return acc + curr.newUsers;
|
||||
}, 0);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<StatCard
|
||||
icon={ActivityIcon}
|
||||
isPending={props.isPending}
|
||||
label="Total Users"
|
||||
value={allTimeStats?.uniqueWalletsConnected || 0}
|
||||
label="Active Users"
|
||||
value={activeUsers || 0}
|
||||
/>
|
||||
<StatCard
|
||||
icon={UserIcon}
|
||||
isPending={props.isPending}
|
||||
label="Monthly Active Users"
|
||||
value={monthlyStats?.uniqueWalletsConnected || 0}
|
||||
label="New Users"
|
||||
value={newUsers || 0}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
@@ -52,40 +39,26 @@ async function AsyncInAppWalletsSummary(props: {
|
||||
teamId: string;
|
||||
projectId: string;
|
||||
authToken: string;
|
||||
range: Range;
|
||||
}) {
|
||||
const { teamId, projectId, authToken } = props;
|
||||
const allTimeStatsPromise = getInAppWalletUsage(
|
||||
const { teamId, projectId, authToken, range } = props;
|
||||
const aggregatedStatsPromise = getInAppWalletUsage(
|
||||
{
|
||||
from: new Date(2022, 0, 1),
|
||||
from: range.from,
|
||||
period: "all",
|
||||
projectId,
|
||||
teamId,
|
||||
to: new Date(),
|
||||
to: range.to,
|
||||
},
|
||||
authToken,
|
||||
);
|
||||
|
||||
const monthlyStatsPromise = getInAppWalletUsage(
|
||||
{
|
||||
from: subDays(new Date(), 30),
|
||||
period: "month",
|
||||
projectId,
|
||||
teamId,
|
||||
to: new Date(),
|
||||
},
|
||||
authToken,
|
||||
);
|
||||
|
||||
const [allTimeStats, monthlyStats] = await Promise.all([
|
||||
allTimeStatsPromise,
|
||||
monthlyStatsPromise,
|
||||
]).catch(() => [null, null]);
|
||||
const aggregatedStats = await aggregatedStatsPromise.catch(() => null);
|
||||
|
||||
return (
|
||||
<InAppWalletsSummaryInner
|
||||
allTimeStats={allTimeStats || undefined}
|
||||
stats={aggregatedStats || undefined}
|
||||
isPending={false}
|
||||
monthlyStats={monthlyStats || undefined}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -94,21 +67,17 @@ export function InAppWalletsSummary(props: {
|
||||
teamId: string;
|
||||
projectId: string;
|
||||
authToken: string;
|
||||
range: Range;
|
||||
}) {
|
||||
return (
|
||||
<Suspense
|
||||
fallback={
|
||||
<InAppWalletsSummaryInner
|
||||
allTimeStats={undefined}
|
||||
isPending={true}
|
||||
monthlyStats={undefined}
|
||||
/>
|
||||
}
|
||||
fallback={<InAppWalletsSummaryInner stats={undefined} isPending={true} />}
|
||||
>
|
||||
<AsyncInAppWalletsSummary
|
||||
projectId={props.projectId}
|
||||
teamId={props.teamId}
|
||||
authToken={props.authToken}
|
||||
range={props.range}
|
||||
/>
|
||||
</Suspense>
|
||||
);
|
||||
|
||||
+1
-1
@@ -40,7 +40,7 @@ type AsyncInAppWalletAnalyticsProps = Omit<
|
||||
async function AsyncInAppWalletAnalytics(
|
||||
props: AsyncInAppWalletAnalyticsProps,
|
||||
) {
|
||||
const range = props.range ?? getLastNDaysRange("last-120");
|
||||
const range = props.range ?? getLastNDaysRange("last-30");
|
||||
|
||||
const stats = await getInAppWalletUsage(
|
||||
{
|
||||
|
||||
+3
-2
@@ -45,13 +45,14 @@ export default async function Page(props: {
|
||||
return (
|
||||
<ResponsiveSearchParamsProvider value={searchParams}>
|
||||
<div>
|
||||
<ResponsiveTimeFilters defaultRange={defaultRange} />
|
||||
<div className="h-6" />
|
||||
<InAppWalletsSummary
|
||||
range={range}
|
||||
projectId={project.id}
|
||||
teamId={project.teamId}
|
||||
authToken={authToken}
|
||||
/>
|
||||
<div className="h-10" />
|
||||
<ResponsiveTimeFilters defaultRange={defaultRange} />
|
||||
<div className="h-6" />
|
||||
<InAppWalletAnalytics
|
||||
interval={interval}
|
||||
|
||||
+1
-1
@@ -41,7 +41,7 @@ export function useTestWebhook(clientId: string) {
|
||||
});
|
||||
|
||||
const result = await testWebhook(
|
||||
{ type, webhook_url: webhookUrl },
|
||||
{ type, webhook_url: webhookUrl, webhook_id: id },
|
||||
clientId,
|
||||
);
|
||||
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @thirdweb-dev/nebula
|
||||
|
||||
## 0.2.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`1083316`](https://github.com/thirdweb-dev/js/commit/1083316ef681c3f6ca4138acd00b1e945c5156d5)]:
|
||||
- thirdweb@5.105.37
|
||||
|
||||
## 0.2.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`e01af9d`](https://github.com/thirdweb-dev/js/commit/e01af9db7128a66b8bc7297911f713eb828da3cc)]:
|
||||
- thirdweb@5.105.36
|
||||
|
||||
## 0.2.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -57,5 +57,5 @@
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/thirdweb.d.ts",
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "0.2.35"
|
||||
"version": "0.2.37"
|
||||
}
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
# thirdweb
|
||||
|
||||
## 5.105.37
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#7872](https://github.com/thirdweb-dev/js/pull/7872) [`1083316`](https://github.com/thirdweb-dev/js/commit/1083316ef681c3f6ca4138acd00b1e945c5156d5) Thanks [@gregfromstl](https://github.com/gregfromstl)! - Update createToken to return a prepared transaction
|
||||
|
||||
## 5.105.36
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#7887](https://github.com/thirdweb-dev/js/pull/7887) [`e01af9d`](https://github.com/thirdweb-dev/js/commit/e01af9db7128a66b8bc7297911f713eb828da3cc) Thanks [@leo-shi-dacheng](https://github.com/leo-shi-dacheng)! - fix: #7886
|
||||
|
||||
## 5.105.35
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -417,5 +417,5 @@
|
||||
}
|
||||
},
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "5.105.35"
|
||||
"version": "5.105.37"
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ export const mode = /* @__PURE__ */ defineChain({
|
||||
url: "https://explorer.mode.network/",
|
||||
},
|
||||
],
|
||||
id: 919,
|
||||
id: 34443,
|
||||
name: "Mode",
|
||||
nativeCurrency: { decimals: 18, name: "Ether", symbol: "ETH" },
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@ export {
|
||||
DEFAULT_DEVELOPER_ADDRESS,
|
||||
DEFAULT_DEVELOPER_REWARD_BPS,
|
||||
} from "../tokens/constants.js";
|
||||
export { createToken } from "../tokens/create-token.js";
|
||||
export { createToken, prepareCreateToken } from "../tokens/create-token.js";
|
||||
export { distributeToken } from "../tokens/distribute-token.js";
|
||||
export {
|
||||
getDeployedContractFactory,
|
||||
|
||||
@@ -62,3 +62,39 @@ export async function createToken(options: CreateTokenOptions) {
|
||||
|
||||
return decodedEvent[0]?.args.asset;
|
||||
}
|
||||
|
||||
export async function prepareCreateToken(options: CreateTokenOptions) {
|
||||
const { client, params, account, launchConfig } = options;
|
||||
|
||||
const creator = params.owner || account.address;
|
||||
const encodedInitData = await encodeInitParams({
|
||||
client,
|
||||
creator,
|
||||
params,
|
||||
});
|
||||
|
||||
const salt: Hex = generateSalt(options.salt || bytesToHex(randomBytes(31)));
|
||||
|
||||
const entrypoint = await getDeployedEntrypointERC20(options);
|
||||
|
||||
let hookData: Hex = "0x";
|
||||
let contractId = padHex(toHex("ERC20Asset"), { size: 32 });
|
||||
if (launchConfig?.kind === "pool") {
|
||||
hookData = encodePoolConfig(launchConfig.config);
|
||||
contractId = padHex(toHex("ERC20Asset_Pool"), { size: 32 });
|
||||
}
|
||||
|
||||
const transaction = createById({
|
||||
contract: entrypoint,
|
||||
contractId,
|
||||
params: {
|
||||
data: encodedInitData,
|
||||
hookData,
|
||||
developer: options.developerAddress || DEFAULT_DEVELOPER_ADDRESS,
|
||||
salt,
|
||||
},
|
||||
creator,
|
||||
});
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
import type { CreateTokenOptions } from "./types.js";
|
||||
|
||||
export async function predictAddress(options: CreateTokenOptions) {
|
||||
const { client, account, params, launchConfig } = options;
|
||||
const { client, params, launchConfig, account } = options;
|
||||
|
||||
const creator = params.owner || account.address;
|
||||
const encodedInitData = await encodeInitParams({
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
# @thirdweb-dev/wagmi-adapter
|
||||
|
||||
## 0.2.134
|
||||
|
||||
## 0.2.133
|
||||
|
||||
## 0.2.132
|
||||
|
||||
## 0.2.131
|
||||
|
||||
@@ -55,5 +55,5 @@
|
||||
"type": "module",
|
||||
"types": "./dist/types/exports/thirdweb.d.ts",
|
||||
"typings": "./dist/types/exports/thirdweb.d.ts",
|
||||
"version": "0.2.132"
|
||||
"version": "0.2.134"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user