Skip to content

Commit 331c14e

Browse files
authored
Merge pull request #3158 from pyth-network/cprussin/dont-use-pythconnection
fix(insights): use web3.js Connection instead of PythConnection
2 parents c1f723c + 4de4efc commit 331c14e

File tree

3 files changed

+38
-178
lines changed

3 files changed

+38
-178
lines changed

apps/insights/src/components/Root/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import {
1010
GOOGLE_ANALYTICS_ID,
1111
} from "../../config/server";
1212
import { getPublishersWithRankings } from "../../get-publishers-with-rankings";
13-
import { LivePriceDataProvider } from "../../hooks/use-live-price-data";
1413
import { Cluster } from "../../services/pyth";
1514
import { getFeeds } from "../../services/pyth/get-feeds";
1615
import { PriceFeedIcon } from "../PriceFeedIcon";
@@ -32,7 +31,7 @@ export const Root = ({ children }: Props) => (
3231
amplitudeApiKey={AMPLITUDE_API_KEY}
3332
googleAnalyticsId={GOOGLE_ANALYTICS_ID}
3433
enableAccessibilityReporting={ENABLE_ACCESSIBILITY_REPORTING}
35-
providers={[NuqsAdapter, LivePriceDataProvider]}
34+
providers={[NuqsAdapter]}
3635
tabs={TABS}
3736
extraCta={<SearchButton />}
3837
>

apps/insights/src/hooks/use-live-price-data.tsx

Lines changed: 17 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -3,53 +3,34 @@
33
import type { PriceData } from "@pythnetwork/client";
44
import { useLogger } from "@pythnetwork/component-library/useLogger";
55
import { PublicKey } from "@solana/web3.js";
6-
import type { ComponentProps } from "react";
7-
import {
8-
use,
9-
createContext,
10-
useEffect,
11-
useCallback,
12-
useState,
13-
useMemo,
14-
useRef,
15-
} from "react";
6+
import { useEffect, useState, useMemo } from "react";
167

17-
import {
18-
Cluster,
19-
subscribe,
20-
getAssetPricesFromAccounts,
21-
} from "../services/pyth";
22-
23-
const LivePriceDataContext = createContext<
24-
ReturnType<typeof usePriceData> | undefined
25-
>(undefined);
26-
27-
type LivePriceDataProviderProps = Omit<
28-
ComponentProps<typeof LivePriceDataContext>,
29-
"value"
30-
>;
31-
32-
export const LivePriceDataProvider = (props: LivePriceDataProviderProps) => {
33-
const priceData = usePriceData();
34-
35-
return <LivePriceDataContext value={priceData} {...props} />;
36-
};
8+
import { Cluster, subscribe, unsubscribe } from "../services/pyth";
379

3810
export const useLivePriceData = (cluster: Cluster, feedKey: string) => {
39-
const { addSubscription, removeSubscription } =
40-
useLivePriceDataContext()[cluster];
41-
11+
const logger = useLogger();
4212
const [data, setData] = useState<{
4313
current: PriceData | undefined;
4414
prev: PriceData | undefined;
4515
}>({ current: undefined, prev: undefined });
4616

4717
useEffect(() => {
48-
addSubscription(feedKey, setData);
18+
const subscriptionId = subscribe(
19+
cluster,
20+
new PublicKey(feedKey),
21+
({ data }) => {
22+
setData((prev) => ({ current: data, prev: prev.current }));
23+
},
24+
);
4925
return () => {
50-
removeSubscription(feedKey, setData);
26+
unsubscribe(cluster, subscriptionId).catch((error: unknown) => {
27+
logger.error(
28+
`Failed to remove subscription for price feed ${feedKey}`,
29+
error,
30+
);
31+
});
5132
};
52-
}, [addSubscription, removeSubscription, feedKey]);
33+
}, [cluster, feedKey, logger]);
5334

5435
return data;
5536
};
@@ -75,130 +56,3 @@ export const useLivePriceComponent = (
7556
exponent: current?.exponent,
7657
};
7758
};
78-
79-
const usePriceData = () => {
80-
const pythnetPriceData = usePriceDataForCluster(Cluster.Pythnet);
81-
const pythtestPriceData = usePriceDataForCluster(Cluster.PythtestConformance);
82-
83-
return {
84-
[Cluster.Pythnet]: pythnetPriceData,
85-
[Cluster.PythtestConformance]: pythtestPriceData,
86-
};
87-
};
88-
89-
type Subscription = (value: {
90-
current: PriceData;
91-
prev: PriceData | undefined;
92-
}) => void;
93-
94-
const usePriceDataForCluster = (cluster: Cluster) => {
95-
const [feedKeys, setFeedKeys] = useState<string[]>([]);
96-
const feedSubscriptions = useRef<Map<string, Set<Subscription>>>(new Map());
97-
const priceData = useRef<Map<string, PriceData>>(new Map());
98-
const prevPriceData = useRef<Map<string, PriceData>>(new Map());
99-
const logger = useLogger();
100-
101-
useEffect(() => {
102-
// First, we initialize prices with the last available price. This way, if
103-
// there's any symbol that isn't currently publishing prices (e.g. the
104-
// markets are closed), we will still display the last published price for
105-
// that symbol.
106-
const uninitializedFeedKeys = feedKeys.filter(
107-
(key) => !priceData.current.has(key),
108-
);
109-
if (uninitializedFeedKeys.length > 0) {
110-
getAssetPricesFromAccounts(
111-
cluster,
112-
uninitializedFeedKeys.map((key) => new PublicKey(key)),
113-
)
114-
.then((initialPrices) => {
115-
for (const [i, price] of initialPrices.entries()) {
116-
const key = uninitializedFeedKeys[i];
117-
if (key && !priceData.current.has(key)) {
118-
priceData.current.set(key, price);
119-
}
120-
}
121-
})
122-
.catch((error: unknown) => {
123-
logger.error("Failed to fetch initial prices", error);
124-
});
125-
}
126-
127-
// Then, we create a subscription to update prices live.
128-
const connection = subscribe(
129-
cluster,
130-
feedKeys.map((key) => new PublicKey(key)),
131-
({ price_account }, data) => {
132-
if (price_account) {
133-
const prevData = priceData.current.get(price_account);
134-
if (prevData) {
135-
prevPriceData.current.set(price_account, prevData);
136-
}
137-
priceData.current.set(price_account, data);
138-
for (const subscription of feedSubscriptions.current.get(
139-
price_account,
140-
) ?? []) {
141-
subscription({ current: data, prev: prevData });
142-
}
143-
}
144-
},
145-
);
146-
147-
connection.start().catch((error: unknown) => {
148-
logger.error("Failed to subscribe to prices", error);
149-
});
150-
return () => {
151-
connection.stop().catch((error: unknown) => {
152-
logger.error("Failed to unsubscribe from price updates", error);
153-
});
154-
};
155-
}, [feedKeys, logger, cluster]);
156-
157-
const addSubscription = useCallback(
158-
(key: string, subscription: Subscription) => {
159-
const current = feedSubscriptions.current.get(key);
160-
if (current === undefined) {
161-
feedSubscriptions.current.set(key, new Set([subscription]));
162-
setFeedKeys((prev) => [...new Set([...prev, key])]);
163-
} else {
164-
current.add(subscription);
165-
}
166-
},
167-
[feedSubscriptions],
168-
);
169-
170-
const removeSubscription = useCallback(
171-
(key: string, subscription: Subscription) => {
172-
const current = feedSubscriptions.current.get(key);
173-
if (current) {
174-
if (current.size === 0) {
175-
feedSubscriptions.current.delete(key);
176-
setFeedKeys((prev) => prev.filter((elem) => elem !== key));
177-
} else {
178-
current.delete(subscription);
179-
}
180-
}
181-
},
182-
[feedSubscriptions],
183-
);
184-
185-
return {
186-
addSubscription,
187-
removeSubscription,
188-
};
189-
};
190-
191-
const useLivePriceDataContext = () => {
192-
const prices = use(LivePriceDataContext);
193-
if (prices === undefined) {
194-
throw new LivePriceDataProviderNotInitializedError();
195-
}
196-
return prices;
197-
};
198-
199-
class LivePriceDataProviderNotInitializedError extends Error {
200-
constructor() {
201-
super("This component must be a child of <LivePriceDataProvider>");
202-
this.name = "LivePriceDataProviderNotInitializedError";
203-
}
204-
}

apps/insights/src/services/pyth/index.ts

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1+
import type { PriceData } from "@pythnetwork/client";
12
import {
23
PythHttpClient,
3-
PythConnection,
44
getPythProgramKeyForCluster,
5+
parsePriceData,
56
} from "@pythnetwork/client";
6-
import type { PythPriceCallback } from "@pythnetwork/client/lib/PythConnection";
7+
import type { AccountInfo } from "@solana/web3.js";
78
import { Connection, PublicKey } from "@solana/web3.js";
89

910
import { PYTHNET_RPC, PYTHTEST_CONFORMANCE_RPC } from "../../config/isomorphic";
@@ -67,15 +68,21 @@ export const getAssetPricesFromAccounts = (
6768

6869
export const subscribe = (
6970
cluster: Cluster,
70-
feeds: PublicKey[],
71-
cb: PythPriceCallback,
72-
) => {
73-
const pythConn = new PythConnection(
74-
connections[cluster],
75-
getPythProgramKeyForCluster(ClusterToName[cluster]),
76-
"confirmed",
77-
feeds,
71+
feed: PublicKey,
72+
cb: (values: { accountInfo: AccountInfo<Buffer>; data: PriceData }) => void,
73+
) =>
74+
connections[cluster].onAccountChange(
75+
feed,
76+
(accountInfo, context) => {
77+
cb({
78+
accountInfo,
79+
data: parsePriceData(accountInfo.data, context.slot),
80+
});
81+
},
82+
{
83+
commitment: "confirmed",
84+
},
7885
);
79-
pythConn.onPriceChange(cb);
80-
return pythConn;
81-
};
86+
87+
export const unsubscribe = (cluster: Cluster, subscriptionId: number) =>
88+
connections[cluster].removeAccountChangeListener(subscriptionId);

0 commit comments

Comments
 (0)