Skip to content
Merged
Show file tree
Hide file tree
Changes from 37 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
2e2f8bb
feat: add cached methods
Aug 4, 2025
6e33dd2
feat: add more cached methods
alexcambose Aug 4, 2025
bc390f6
feat(insights): add more cached methods 2
alexcambose Aug 4, 2025
70ba6f9
feat(insights): testing in memory data
alexcambose Aug 4, 2025
80a0429
chore: test with partial cache
alexcambose Aug 5, 2025
a3d71ee
perf: reduce partial cache
alexcambose Aug 5, 2025
092cdd5
chore: test with max memory setting
alexcambose Aug 5, 2025
5ca1c24
chore: bump next version
alexcambose Aug 5, 2025
7c69871
chore: add logs
alexcambose Aug 5, 2025
5a0694a
chore: add cache test function
alexcambose Aug 5, 2025
c8f9edb
chore: test with unstable cache
alexcambose Aug 6, 2025
970fec1
chore: test with function tags
alexcambose Aug 6, 2025
90b3668
perf: chunk cache
alexcambose Aug 7, 2025
ca16ed8
fix: add cache buffer
alexcambose Aug 7, 2025
f7c4e0d
fix: lint
alexcambose Aug 7, 2025
f226d34
feat: chunk cache
alexcambose Aug 8, 2025
f6ffcf7
feat: replaced more cached functions
alexcambose Aug 8, 2025
022793b
fix: use in memory cache
alexcambose Aug 8, 2025
3823e64
fix: cache key issue
alexcambose Aug 8, 2025
b06ac66
chore: added logging
alexcambose Aug 8, 2025
b5ac3f7
chore: revalidate false
alexcambose Aug 8, 2025
effbc28
feat: migrated to redis
alexcambose Aug 11, 2025
0682c73
feat: added fetch routes
alexcambose Aug 12, 2025
8b2719c
feat: added all endpoints
alexcambose Aug 12, 2025
fcbda8a
feat: more endpoint fetching
alexcambose Aug 13, 2025
8daffbb
fix: types
alexcambose Aug 13, 2025
9e29e54
refactor: cleanup
alexcambose Aug 13, 2025
b684cbe
fix: publishers metadata
alexcambose Aug 13, 2025
2744ee0
fix: imports
alexcambose Aug 14, 2025
a3cfcda
refactor: minor fixes
alexcambose Aug 14, 2025
d9456ed
Merge branch 'main' into feat/perf-caching
alexcambose Aug 14, 2025
5da02aa
Merge branch 'main' into feat/perf-caching
alexcambose Aug 14, 2025
038d370
chore: updated pnpm
alexcambose Aug 14, 2025
1872162
chore: updated pnpm
alexcambose Aug 14, 2025
70e5bd4
chore: revert with fetch again
alexcambose Aug 14, 2025
43ff345
refactor: addressed PR comments
alexcambose Aug 15, 2025
9966636
feat: use zod for validation
alexcambose Aug 15, 2025
498dbd0
fix: use origin headers
alexcambose Aug 18, 2025
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: 0 additions & 1 deletion apps/insights/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ const config = {
useCache: true,
reactCompiler: true,
},

reactStrictMode: true,

pageExtensions: ["ts", "tsx", "mdx"],
Expand Down
5 changes: 4 additions & 1 deletion apps/insights/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,12 @@
"@pythnetwork/known-publishers": "workspace:*",
"@react-hookz/web": "catalog:",
"@solana/web3.js": "catalog:",
"async-cache-dedupe": "catalog:",
"bs58": "catalog:",
"clsx": "catalog:",
"cryptocurrency-icons": "catalog:",
"dnum": "catalog:",
"ioredis": "^5.7.0",
"lightweight-charts": "catalog:",
"motion": "catalog:",
"next": "catalog:",
Expand All @@ -44,7 +46,8 @@
"superjson": "catalog:",
"swr": "catalog:",
"zod": "catalog:",
"zod-validation-error": "catalog:"
"zod-validation-error": "catalog:",
"zod-search-params": "catalog:"
},
"devDependencies": {
"@cprussin/eslint-config": "catalog:",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { NextRequest } from "next/server";
import { stringify } from "superjson";
import { z } from "zod";
import { parseSearchParams } from "zod-search-params";

import {
Cluster,
CLUSTER_NAMES,
ClusterToName,
toCluster,
} from "../../../../../services/pyth";
import { getFeeds } from "../../../../../services/pyth/get-feeds";

export const GET = async (
request: NextRequest,
{ params }: { params: Promise<{ publisher: string }> },
) => {
const { publisher } = await params;
const searchParams = request.nextUrl.searchParams;
const parsedSearchParams = parseSearchParams(queryParamsSchema, searchParams);

if (!parsedSearchParams) {
return new Response("Invalid params", {
status: 400,
});
}

const { cluster } = parsedSearchParams;

if (!publisher) {
return new Response("Publisher is required", {
status: 400,
});
}

const feeds = await getFeeds(cluster);

const filteredFeeds = feeds.filter((feed) =>
feed.price.priceComponents.some((c) => c.publisher === publisher),
);

return new Response(stringify(filteredFeeds), {
headers: {
"Content-Type": "application/json",
},
});
};

const queryParamsSchema = z.object({
cluster: z
.enum(CLUSTER_NAMES)
.transform((value) => toCluster(value))
.default(ClusterToName[Cluster.Pythnet]),
});
45 changes: 45 additions & 0 deletions apps/insights/src/app/api/pyth/get-feeds/[symbol]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NextRequest } from "next/server";
import { stringify } from "superjson";
import { z } from "zod";
import { parseSearchParams } from "zod-search-params";

import {
Cluster,
CLUSTER_NAMES,
ClusterToName,
toCluster,
} from "../../../../../services/pyth";
import { getFeeds } from "../../../../../services/pyth/get-feeds";

export const GET = async (
request: NextRequest,
{ params }: { params: Promise<{ symbol: string }> },
) => {
const { symbol } = await params;
const searchParams = request.nextUrl.searchParams;
const parsedSearchParams = parseSearchParams(queryParamsSchema, searchParams);

if (!parsedSearchParams) {
return new Response("Invalid params", {
status: 400,
});
}

const { cluster } = parsedSearchParams;

const feeds = await getFeeds(cluster);
const feed = feeds.find((feed) => feed.symbol === symbol);

return new Response(stringify(feed), {
headers: {
"Content-Type": "application/json",
},
});
};

const queryParamsSchema = z.object({
cluster: z
.enum(CLUSTER_NAMES)
.transform((value) => toCluster(value))
.default(ClusterToName[Cluster.Pythnet]),
});
41 changes: 41 additions & 0 deletions apps/insights/src/app/api/pyth/get-feeds/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { NextRequest } from "next/server";
import { stringify } from "superjson";
import { z } from "zod";
import { parseSearchParams } from "zod-search-params";

import { CLUSTER_NAMES, toCluster } from "../../../../services/pyth";
import { getFeeds } from "../../../../services/pyth/get-feeds";

export const GET = async (request: NextRequest) => {
// get cluster from query params
const searchParams = request.nextUrl.searchParams;
const parsedSearchParams = parseSearchParams(queryParamsSchema, searchParams);

if (!parsedSearchParams) {
return new Response("Invalid params", {
status: 400,
});
}

const { excludePriceComponents, cluster } = parsedSearchParams;

const feeds = await getFeeds(cluster);
const filteredFeeds = excludePriceComponents
? feeds.map((feed) => {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { price, ...rest } = feed;
return rest;
})
: feeds;

return new Response(stringify(filteredFeeds), {
headers: {
"Content-Type": "application/json",
},
});
};

const queryParamsSchema = z.object({
cluster: z.enum(CLUSTER_NAMES).transform((value) => toCluster(value)),
excludePriceComponents: z.boolean(),
});
45 changes: 45 additions & 0 deletions apps/insights/src/app/api/pyth/get-publishers/[symbol]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { NextRequest, NextResponse } from "next/server";
import { z } from "zod";
import { parseSearchParams } from "zod-search-params";

import {
Cluster,
CLUSTER_NAMES,
ClusterToName,
toCluster,
} from "../../../../../services/pyth";
import { getPublishersForCluster } from "../../../../../services/pyth/get-publishers-for-cluster";

export const GET = async (
request: NextRequest,
{ params }: { params: Promise<{ symbol: string }> },
) => {
const { symbol } = await params;
const searchParams = request.nextUrl.searchParams;
const parsedSearchParams = parseSearchParams(queryParamsSchema, searchParams);

if (!parsedSearchParams) {
return new Response("Invalid params", {
status: 400,
});
}

const { cluster } = parsedSearchParams;

if (!symbol) {
return new Response("Symbol is required", {
status: 400,
});
}

const map = await getPublishersForCluster(cluster);

return NextResponse.json(map[symbol] ?? []);
};

const queryParamsSchema = z.object({
cluster: z
.enum(CLUSTER_NAMES)
.transform((value) => toCluster(value))
.default(ClusterToName[Cluster.Pythnet]),
});
4 changes: 2 additions & 2 deletions apps/insights/src/app/component-score-history/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@ export const GET = async (req: NextRequest) => {
);
if (parsed.success) {
const { cluster, publisherKey, symbol, from, to } = parsed.data;
const data = await getFeedScoreHistory(
const data = await getFeedScoreHistory({
cluster,
publisherKey,
symbol,
from,
to,
);
});
return Response.json(data);
} else {
return new Response(fromError(parsed.error).toString(), {
Expand Down
5 changes: 4 additions & 1 deletion apps/insights/src/app/historical-prices/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@ export async function GET(req: NextRequest) {
const symbol = req.nextUrl.searchParams.get("symbol");
const until = req.nextUrl.searchParams.get("until");
if (symbol && until) {
const res = await getHistoricalPrices(decodeURIComponent(symbol), until);
const res = await getHistoricalPrices({
symbol: decodeURIComponent(symbol),
until,
});
return Response.json(res);
} else {
return new Response("Must provide `symbol` and `until`", { status: 400 });
Expand Down
3 changes: 2 additions & 1 deletion apps/insights/src/app/price-feeds/[slug]/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { Metadata } from "next";
import { notFound } from "next/navigation";
import type { ReactNode } from "react";

import { Cluster, getFeeds } from "../../../services/pyth";
import { Cluster } from "../../../services/pyth";
import { getFeeds } from "../../../services/pyth/get-feeds";

export { PriceFeedLayout as default } from "../../../components/PriceFeed/layout";

Expand Down
5 changes: 4 additions & 1 deletion apps/insights/src/components/Overview/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import PriceFeedsLight from "./price-feeds-light.svg";
import PublishersDark from "./publishers-dark.svg";
import PublishersLight from "./publishers-light.svg";
import { TabList } from "./tab-list";
import { Cluster, getFeeds } from "../../services/pyth";
import { Cluster } from "../../services/pyth";
import { getFeeds } from "../../services/pyth/get-feeds";
import {
totalVolumeTraded,
activeChains,
Expand All @@ -25,6 +26,7 @@ import { FormattedNumber } from "../FormattedNumber";
export const Overview = async () => {
const priceFeeds = await getFeeds(Cluster.Pythnet);
const today = new Date();

const feedCounts = [
...activeFeeds.map(({ date, numFeeds }) => ({
x: date,
Expand All @@ -37,6 +39,7 @@ export const Overview = async () => {
y: priceFeeds.length,
},
];

return (
<div className={styles.overview}>
<h1 className={styles.header}>Overview</h1>
Expand Down
19 changes: 10 additions & 9 deletions apps/insights/src/components/PriceFeed/get-feed.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import { notFound } from "next/navigation";

import { Cluster, getFeeds } from "../../services/pyth";
import { getFeedForSymbolRequest, getFeedsRequest } from "../../server/pyth";
import { Cluster } from "../../services/pyth";

export const getFeed = async (params: Promise<{ slug: string }>) => {
"use cache";
const feeds = await getFeedsRequest(Cluster.Pythnet);

const [{ slug }, feeds] = await Promise.all([params, getPythnetFeeds()]);
const { slug } = await params;
const symbol = decodeURIComponent(slug);
const feed = await getFeedForSymbolRequest({
symbol,
cluster: Cluster.Pythnet,
});

return {
feeds,
feed: feeds.find((item) => item.symbol === symbol) ?? notFound(),
feed: feed ?? notFound(),
symbol,
} as const;
};

const getPythnetFeeds = async () => {
"use cache";
return getFeeds(Cluster.Pythnet);
};
Loading
Loading