Kaynağa Gözat

Merge remote-tracking branch 'origin/main' into bduran/dual-publish

benduran 3 hafta önce
ebeveyn
işleme
07394044be

+ 14 - 14
Cargo.lock

@@ -5656,7 +5656,7 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-agent"
-version = "0.7.2"
+version = "0.8.0"
 dependencies = [
  "anyhow",
  "backoff",
@@ -5674,8 +5674,8 @@ dependencies = [
  "hyper 1.6.0",
  "hyper-util",
  "protobuf",
- "pyth-lazer-protocol 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "pyth-lazer-publisher-sdk 0.18.0",
+ "pyth-lazer-protocol 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pyth-lazer-publisher-sdk 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "reqwest 0.12.23",
  "serde",
  "serde_json",
@@ -5694,7 +5694,7 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-client"
-version = "8.5.0"
+version = "8.6.0"
 dependencies = [
  "alloy-primitives 0.8.25",
  "anyhow",
@@ -5712,7 +5712,7 @@ dependencies = [
  "hex",
  "humantime-serde",
  "libsecp256k1 0.7.2",
- "pyth-lazer-protocol 0.19.0",
+ "pyth-lazer-protocol 0.20.0",
  "reqwest 0.12.23",
  "serde",
  "serde_json",
@@ -5726,7 +5726,7 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-protocol"
-version = "0.19.0"
+version = "0.20.0"
 dependencies = [
  "alloy-primitives 0.8.25",
  "anyhow",
@@ -5752,9 +5752,9 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-protocol"
-version = "0.19.0"
+version = "0.20.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5b7d19a91e0d63e5003a409d51346abb4941bf55a3a9d52f65453c65057f0482"
+checksum = "2efd998c309b88c9f9790addb962cb20cb2528f3c1fe305160f998403306ba7d"
 dependencies = [
  "anyhow",
  "byteorder",
@@ -5773,27 +5773,27 @@ dependencies = [
 
 [[package]]
 name = "pyth-lazer-publisher-sdk"
-version = "0.18.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b910a3ca825bd1f4bbf75ef5fe01c76eec3e4f5f7570e8c38cbd465c81e21a4"
+version = "0.20.0"
 dependencies = [
  "anyhow",
  "fs-err",
  "protobuf",
  "protobuf-codegen",
- "pyth-lazer-protocol 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pyth-lazer-protocol 0.20.0",
  "serde_json",
 ]
 
 [[package]]
 name = "pyth-lazer-publisher-sdk"
-version = "0.19.0"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "988c6a2d6bc8d065a492d49915e803912b263e57ad5e300dc45c5a5471d609c8"
 dependencies = [
  "anyhow",
  "fs-err",
  "protobuf",
  "protobuf-codegen",
- "pyth-lazer-protocol 0.19.0",
+ "pyth-lazer-protocol 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json",
 ]
 

+ 50 - 47
apps/entropy-explorer/src/components/Home/request-drawer.tsx

@@ -9,6 +9,7 @@ import { StatCard } from "@pythnetwork/component-library/StatCard";
 import { Table } from "@pythnetwork/component-library/Table";
 import { Term } from "@pythnetwork/component-library/Term";
 import type { OpenDrawerArgs } from "@pythnetwork/component-library/useDrawer";
+import type { ComponentProps } from "react";
 import { useNumberFormatter } from "react-aria";
 import TimeAgo from "react-timeago";
 
@@ -75,7 +76,7 @@ const RequestDrawerBody = ({
         <CallbackErrorInfo request={request} />
       )}
       {request.status === Status.Failed && (
-        <RevealFailedInfo request={request} />
+        <FailureInfo header="Reveal failed!" request={request} />
       )}
       <Table
         label="Details"
@@ -253,28 +254,7 @@ const CallbackErrorInfo = ({ request }: { request: CallbackErrorRequest }) => {
 
   return (
     <>
-      <InfoBox
-        header="Callback failed!"
-        icon={<Warning />}
-        className={styles.message}
-        variant="warning"
-      >
-        <Button
-          hideText
-          beforeIcon={<Question />}
-          rounded
-          size="sm"
-          variant="ghost"
-          className={styles.helpButton ?? ""}
-          href={getHelpLink(request.returnValue)}
-          target="_blank"
-        >
-          Help
-        </Button>
-        <div className={styles.failureMessage}>
-          <FailureMessage reason={request.returnValue} />
-        </div>
-      </InfoBox>
+      <FailureInfo header="Callback failed!" request={request} />
       <InfoBox
         header="Retry the callback yourself"
         icon={<Code />}
@@ -311,12 +291,17 @@ const CallbackErrorInfo = ({ request }: { request: CallbackErrorRequest }) => {
   );
 };
 
-const RevealFailedInfo = ({ request }: { request: FailedRequest }) => (
+const FailureInfo = ({
+  request,
+  ...props
+}: ComponentProps<typeof InfoBox> & {
+  request: CallbackErrorRequest | FailedRequest;
+}) => (
   <InfoBox
-    header="Reveal failed!"
     icon={<Warning />}
     className={styles.message}
     variant="warning"
+    {...props}
   >
     <Button
       hideText
@@ -325,37 +310,55 @@ const RevealFailedInfo = ({ request }: { request: FailedRequest }) => (
       size="sm"
       variant="ghost"
       className={styles.helpButton ?? ""}
-      href={getHelpLink(request.reason)}
+      href={getHelpLink(request)}
       target="_blank"
     >
       Help
     </Button>
     <div className={styles.failureMessage}>
-      <FailureMessage reason={request.reason} />
+      <FailureMessage request={request} />
     </div>
   </InfoBox>
 );
 
-const getHelpLink = (reason: string) => {
-  const details = getErrorDetails(reason);
-  return (
-    details?.[2] ??
-    "https://docs.pyth.network/entropy/best-practices#handling-callback-failures"
-  );
+const getHelpLink = (request: CallbackErrorRequest | FailedRequest) => {
+  const details = getErrorDetails(request.reason);
+  if (details === undefined) {
+    return isGasLimitExceeded(request)
+      ? "https://docs.pyth.network/entropy/best-practices#limit-gas-usage-on-the-callback"
+      : "https://docs.pyth.network/entropy/best-practices#handling-callback-failures";
+  } else {
+    return details[2];
+  }
 };
 
-const FailureMessage = ({ reason }: { reason: string }) => {
-  const details = getErrorDetails(reason);
-  return details ? (
-    <>
-      <p>The callback encountered the following error:</p>
-      <p className={styles.details}>
-        <b>{details[0]}</b> (<code>{reason}</code>): {details[1]}
-      </p>
-    </>
-  ) : (
-    <>
-      <b>Error response:</b> {reason}
-    </>
-  );
+const FailureMessage = ({
+  request,
+}: {
+  request: CallbackErrorRequest | FailedRequest;
+}) => {
+  const details = getErrorDetails(request.reason);
+  if (details) {
+    return (
+      <>
+        <p>The callback encountered the following error:</p>
+        <p className={styles.details}>
+          <b>{details[0]}</b> (<code>{request.reason}</code>): {details[1]}
+        </p>
+      </>
+    );
+  } else if (isGasLimitExceeded(request)) {
+    return "The callback used more gas than the set gas limit";
+  } else {
+    return (
+      <>
+        <b>Error response:</b> {request.reason}
+      </>
+    );
+  }
 };
+
+const isGasLimitExceeded = (request: CallbackErrorRequest | FailedRequest) =>
+  request.status === Status.CallbackError &&
+  request.reason === "0x" &&
+  request.gasUsed > request.gasLimit;

+ 2 - 2
apps/entropy-explorer/src/requests.ts

@@ -86,7 +86,7 @@ export const getRequests = async ({
                     return request.state.callback_failed
                       ? Request.CallbackErrored({
                           ...completedCommon,
-                          returnValue: request.state.callback_return_value,
+                          reason: request.state.callback_return_value,
                         })
                       : Request.Complete({
                           ...completedCommon,
@@ -244,7 +244,7 @@ type CompletedArgs = BaseArgs & {
   callbackTxHash: `0x${string}`;
 };
 type CallbackErrorArgs = CompletedArgs & {
-  returnValue: string;
+  reason: string;
 };
 
 const Request = {

+ 1 - 2
apps/insights/src/components/Root/index.tsx

@@ -10,7 +10,6 @@ import {
   GOOGLE_ANALYTICS_ID,
 } from "../../config/server";
 import { getPublishersWithRankings } from "../../get-publishers-with-rankings";
-import { LivePriceDataProvider } from "../../hooks/use-live-price-data";
 import { Cluster } from "../../services/pyth";
 import { getFeeds } from "../../services/pyth/get-feeds";
 import { PriceFeedIcon } from "../PriceFeedIcon";
@@ -32,7 +31,7 @@ export const Root = ({ children }: Props) => (
     amplitudeApiKey={AMPLITUDE_API_KEY}
     googleAnalyticsId={GOOGLE_ANALYTICS_ID}
     enableAccessibilityReporting={ENABLE_ACCESSIBILITY_REPORTING}
-    providers={[NuqsAdapter, LivePriceDataProvider]}
+    providers={[NuqsAdapter]}
     tabs={TABS}
     extraCta={<SearchButton />}
   >

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

@@ -3,53 +3,34 @@
 import type { PriceData } from "@pythnetwork/client";
 import { useLogger } from "@pythnetwork/component-library/useLogger";
 import { PublicKey } from "@solana/web3.js";
-import type { ComponentProps } from "react";
-import {
-  use,
-  createContext,
-  useEffect,
-  useCallback,
-  useState,
-  useMemo,
-  useRef,
-} from "react";
+import { useEffect, useState, useMemo } from "react";
 
-import {
-  Cluster,
-  subscribe,
-  getAssetPricesFromAccounts,
-} from "../services/pyth";
-
-const LivePriceDataContext = createContext<
-  ReturnType<typeof usePriceData> | undefined
->(undefined);
-
-type LivePriceDataProviderProps = Omit<
-  ComponentProps<typeof LivePriceDataContext>,
-  "value"
->;
-
-export const LivePriceDataProvider = (props: LivePriceDataProviderProps) => {
-  const priceData = usePriceData();
-
-  return <LivePriceDataContext value={priceData} {...props} />;
-};
+import { Cluster, subscribe, unsubscribe } from "../services/pyth";
 
 export const useLivePriceData = (cluster: Cluster, feedKey: string) => {
-  const { addSubscription, removeSubscription } =
-    useLivePriceDataContext()[cluster];
-
+  const logger = useLogger();
   const [data, setData] = useState<{
     current: PriceData | undefined;
     prev: PriceData | undefined;
   }>({ current: undefined, prev: undefined });
 
   useEffect(() => {
-    addSubscription(feedKey, setData);
+    const subscriptionId = subscribe(
+      cluster,
+      new PublicKey(feedKey),
+      ({ data }) => {
+        setData((prev) => ({ current: data, prev: prev.current }));
+      },
+    );
     return () => {
-      removeSubscription(feedKey, setData);
+      unsubscribe(cluster, subscriptionId).catch((error: unknown) => {
+        logger.error(
+          `Failed to remove subscription for price feed ${feedKey}`,
+          error,
+        );
+      });
     };
-  }, [addSubscription, removeSubscription, feedKey]);
+  }, [cluster, feedKey, logger]);
 
   return data;
 };
@@ -75,130 +56,3 @@ export const useLivePriceComponent = (
     exponent: current?.exponent,
   };
 };
-
-const usePriceData = () => {
-  const pythnetPriceData = usePriceDataForCluster(Cluster.Pythnet);
-  const pythtestPriceData = usePriceDataForCluster(Cluster.PythtestConformance);
-
-  return {
-    [Cluster.Pythnet]: pythnetPriceData,
-    [Cluster.PythtestConformance]: pythtestPriceData,
-  };
-};
-
-type Subscription = (value: {
-  current: PriceData;
-  prev: PriceData | undefined;
-}) => void;
-
-const usePriceDataForCluster = (cluster: Cluster) => {
-  const [feedKeys, setFeedKeys] = useState<string[]>([]);
-  const feedSubscriptions = useRef<Map<string, Set<Subscription>>>(new Map());
-  const priceData = useRef<Map<string, PriceData>>(new Map());
-  const prevPriceData = useRef<Map<string, PriceData>>(new Map());
-  const logger = useLogger();
-
-  useEffect(() => {
-    // First, we initialize prices with the last available price.  This way, if
-    // there's any symbol that isn't currently publishing prices (e.g. the
-    // markets are closed), we will still display the last published price for
-    // that symbol.
-    const uninitializedFeedKeys = feedKeys.filter(
-      (key) => !priceData.current.has(key),
-    );
-    if (uninitializedFeedKeys.length > 0) {
-      getAssetPricesFromAccounts(
-        cluster,
-        uninitializedFeedKeys.map((key) => new PublicKey(key)),
-      )
-        .then((initialPrices) => {
-          for (const [i, price] of initialPrices.entries()) {
-            const key = uninitializedFeedKeys[i];
-            if (key && !priceData.current.has(key)) {
-              priceData.current.set(key, price);
-            }
-          }
-        })
-        .catch((error: unknown) => {
-          logger.error("Failed to fetch initial prices", error);
-        });
-    }
-
-    // Then, we create a subscription to update prices live.
-    const connection = subscribe(
-      cluster,
-      feedKeys.map((key) => new PublicKey(key)),
-      ({ price_account }, data) => {
-        if (price_account) {
-          const prevData = priceData.current.get(price_account);
-          if (prevData) {
-            prevPriceData.current.set(price_account, prevData);
-          }
-          priceData.current.set(price_account, data);
-          for (const subscription of feedSubscriptions.current.get(
-            price_account,
-          ) ?? []) {
-            subscription({ current: data, prev: prevData });
-          }
-        }
-      },
-    );
-
-    connection.start().catch((error: unknown) => {
-      logger.error("Failed to subscribe to prices", error);
-    });
-    return () => {
-      connection.stop().catch((error: unknown) => {
-        logger.error("Failed to unsubscribe from price updates", error);
-      });
-    };
-  }, [feedKeys, logger, cluster]);
-
-  const addSubscription = useCallback(
-    (key: string, subscription: Subscription) => {
-      const current = feedSubscriptions.current.get(key);
-      if (current === undefined) {
-        feedSubscriptions.current.set(key, new Set([subscription]));
-        setFeedKeys((prev) => [...new Set([...prev, key])]);
-      } else {
-        current.add(subscription);
-      }
-    },
-    [feedSubscriptions],
-  );
-
-  const removeSubscription = useCallback(
-    (key: string, subscription: Subscription) => {
-      const current = feedSubscriptions.current.get(key);
-      if (current) {
-        if (current.size === 0) {
-          feedSubscriptions.current.delete(key);
-          setFeedKeys((prev) => prev.filter((elem) => elem !== key));
-        } else {
-          current.delete(subscription);
-        }
-      }
-    },
-    [feedSubscriptions],
-  );
-
-  return {
-    addSubscription,
-    removeSubscription,
-  };
-};
-
-const useLivePriceDataContext = () => {
-  const prices = use(LivePriceDataContext);
-  if (prices === undefined) {
-    throw new LivePriceDataProviderNotInitializedError();
-  }
-  return prices;
-};
-
-class LivePriceDataProviderNotInitializedError extends Error {
-  constructor() {
-    super("This component must be a child of <LivePriceDataProvider>");
-    this.name = "LivePriceDataProviderNotInitializedError";
-  }
-}

+ 20 - 13
apps/insights/src/services/pyth/index.ts

@@ -1,9 +1,10 @@
+import type { PriceData } from "@pythnetwork/client";
 import {
   PythHttpClient,
-  PythConnection,
   getPythProgramKeyForCluster,
+  parsePriceData,
 } from "@pythnetwork/client";
-import type { PythPriceCallback } from "@pythnetwork/client/lib/PythConnection";
+import type { AccountInfo } from "@solana/web3.js";
 import { Connection, PublicKey } from "@solana/web3.js";
 
 import { PYTHNET_RPC, PYTHTEST_CONFORMANCE_RPC } from "../../config/isomorphic";
@@ -67,15 +68,21 @@ export const getAssetPricesFromAccounts = (
 
 export const subscribe = (
   cluster: Cluster,
-  feeds: PublicKey[],
-  cb: PythPriceCallback,
-) => {
-  const pythConn = new PythConnection(
-    connections[cluster],
-    getPythProgramKeyForCluster(ClusterToName[cluster]),
-    "confirmed",
-    feeds,
+  feed: PublicKey,
+  cb: (values: { accountInfo: AccountInfo<Buffer>; data: PriceData }) => void,
+) =>
+  connections[cluster].onAccountChange(
+    feed,
+    (accountInfo, context) => {
+      cb({
+        accountInfo,
+        data: parsePriceData(accountInfo.data, context.slot),
+      });
+    },
+    {
+      commitment: "confirmed",
+    },
   );
-  pythConn.onPriceChange(cb);
-  return pythConn;
-};
+
+export const unsubscribe = (cluster: Cluster, subscriptionId: number) =>
+  connections[cluster].removeAccountChangeListener(subscriptionId);

+ 3 - 3
apps/pyth-lazer-agent/Cargo.toml

@@ -1,14 +1,14 @@
 [package]
 name = "pyth-lazer-agent"
-version = "0.7.2"
+version = "0.8.0"
 edition = "2024"
 description = "Pyth Lazer Agent"
 license = "Apache-2.0"
 repository = "https://github.com/pyth-network/pyth-crosschain"
 
 [dependencies]
-pyth-lazer-publisher-sdk = "0.18.0"
-pyth-lazer-protocol = "0.19.0"
+pyth-lazer-publisher-sdk = "0.20.0"
+pyth-lazer-protocol = "0.20.0"
 
 anyhow = "1.0.98"
 backoff = "0.4.0"

+ 5 - 0
contract_manager/src/store/contracts/EvmLazerContracts.json

@@ -63,5 +63,10 @@
     "chain": "itsnotreal2",
     "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
     "type": "EvmLazerContract"
+  },
+  {
+    "chain": "arbitrum_sepolia",
+    "address": "0xACeA761c27A909d4D3895128EBe6370FDE2dF481",
+    "type": "EvmLazerContract"
   }
 ]

+ 1 - 1
lazer/contracts/solana/programs/pyth-lazer-solana-contract/Cargo.toml

@@ -19,7 +19,7 @@ no-log-ix-name = []
 idl-build = ["anchor-lang/idl-build"]
 
 [dependencies]
-pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.19.0" }
+pyth-lazer-protocol = { path = "../../../../sdk/rust/protocol", version = "0.20.0" }
 
 anchor-lang = "0.31.1"
 bytemuck = { version = "1.20.0", features = ["derive"] }

+ 2 - 2
lazer/publisher_sdk/rust/Cargo.toml

@@ -1,13 +1,13 @@
 [package]
 name = "pyth-lazer-publisher-sdk"
-version = "0.19.0"
+version = "0.20.0"
 edition = "2021"
 description = "Pyth Lazer Publisher SDK types."
 license = "Apache-2.0"
 repository = "https://github.com/pyth-network/pyth-crosschain"
 
 [dependencies]
-pyth-lazer-protocol = { version = "0.19.0", path = "../../sdk/rust/protocol" }
+pyth-lazer-protocol = { version = "0.20.0", path = "../../sdk/rust/protocol" }
 anyhow = "1.0.98"
 protobuf = "3.7.2"
 serde_json = "1.0.140"

+ 1 - 1
lazer/publisher_sdk/rust/src/lib.rs

@@ -56,7 +56,7 @@ impl From<UpdateParams> for Update {
                 best_bid_price,
                 best_ask_price,
             } => Update::PriceUpdate(PriceUpdate {
-                price: Some(price.mantissa_i64()),
+                price: price.map(|p| p.mantissa_i64()),
                 best_bid_price: best_bid_price.map(|p| p.mantissa_i64()),
                 best_ask_price: best_ask_price.map(|p| p.mantissa_i64()),
                 special_fields: Default::default(),

+ 2 - 2
lazer/sdk/rust/client/Cargo.toml

@@ -1,12 +1,12 @@
 [package]
 name = "pyth-lazer-client"
-version = "8.5.0"
+version = "8.6.0"
 edition = "2021"
 description = "A Rust client for Pyth Lazer"
 license = "Apache-2.0"
 
 [dependencies]
-pyth-lazer-protocol = { path = "../protocol", version = "0.19.0" }
+pyth-lazer-protocol = { path = "../protocol", version = "0.20.0" }
 tokio = { version = "1", features = ["full"] }
 tokio-tungstenite = { version = "0.20", features = ["native-tls"] }
 futures-util = "0.3"

+ 1 - 1
lazer/sdk/rust/protocol/Cargo.toml

@@ -1,6 +1,6 @@
 [package]
 name = "pyth-lazer-protocol"
-version = "0.19.0"
+version = "0.20.0"
 edition = "2021"
 description = "Pyth Lazer SDK - protocol types."
 license = "Apache-2.0"

+ 6 - 6
lazer/sdk/rust/protocol/src/jrpc.rs

@@ -45,7 +45,7 @@ pub struct FeedUpdateParams {
 pub enum UpdateParams {
     #[serde(rename = "price")]
     PriceUpdate {
-        price: Price,
+        price: Option<Price>,
         best_bid_price: Option<Price>,
         best_ask_price: Option<Price>,
     },
@@ -190,7 +190,7 @@ mod tests {
                 feed_id: PriceFeedId(1),
                 source_timestamp: TimestampUs::from_micros(124214124124),
                 update: UpdateParams::PriceUpdate {
-                    price: Price::from_integer(1234567890, 0).unwrap(),
+                    price: Some(Price::from_integer(1234567890, 0).unwrap()),
                     best_bid_price: Some(Price::from_integer(1234567891, 0).unwrap()),
                     best_ask_price: Some(Price::from_integer(1234567892, 0).unwrap()),
                 },
@@ -231,7 +231,7 @@ mod tests {
                 feed_id: PriceFeedId(1),
                 source_timestamp: TimestampUs::from_micros(124214124124),
                 update: UpdateParams::PriceUpdate {
-                    price: Price::from_integer(1234567890, 0).unwrap(),
+                    price: Some(Price::from_integer(1234567890, 0).unwrap()),
                     best_bid_price: Some(Price::from_integer(1234567891, 0).unwrap()),
                     best_ask_price: Some(Price::from_integer(1234567892, 0).unwrap()),
                 },
@@ -272,7 +272,7 @@ mod tests {
                 feed_id: PriceFeedId(1),
                 source_timestamp: TimestampUs::from_micros(124214124124),
                 update: UpdateParams::PriceUpdate {
-                    price: Price::from_integer(1234567890, 0).unwrap(),
+                    price: Some(Price::from_integer(1234567890, 0).unwrap()),
                     best_bid_price: Some(Price::from_integer(1234567891, 0).unwrap()),
                     best_ask_price: Some(Price::from_integer(1234567892, 0).unwrap()),
                 },
@@ -312,7 +312,7 @@ mod tests {
                 feed_id: PriceFeedId(1),
                 source_timestamp: TimestampUs::from_micros(745214124124),
                 update: UpdateParams::PriceUpdate {
-                    price: Price::from_integer(5432, 0).unwrap(),
+                    price: Some(Price::from_integer(5432, 0).unwrap()),
                     best_bid_price: Some(Price::from_integer(5432, 0).unwrap()),
                     best_ask_price: Some(Price::from_integer(5432, 0).unwrap()),
                 },
@@ -351,7 +351,7 @@ mod tests {
                 feed_id: PriceFeedId(1),
                 source_timestamp: TimestampUs::from_micros(124214124124),
                 update: UpdateParams::PriceUpdate {
-                    price: Price::from_integer(1234567890, 0).unwrap(),
+                    price: Some(Price::from_integer(1234567890, 0).unwrap()),
                     best_bid_price: None,
                     best_ask_price: None,
                 },

+ 4 - 0
packages/component-library/src/Table/index.module.scss

@@ -136,6 +136,10 @@
           right: theme.spacing(2);
           top: theme.spacing(2);
 
+          svg {
+            width: 1em;
+          }
+
           .ascending,
           .descending {
             opacity: 0.25;

+ 2 - 2
target_chains/sui/contracts/sources/price.move

@@ -6,9 +6,9 @@ module pyth::price {
     /// The confidence interval roughly corresponds to the standard error of a normal distribution.
     /// Both the price and confidence are stored in a fixed-point numeric representation,
     /// `x * (10^expo)`, where `expo` is the exponent.
-    //
+    ///
     /// Please refer to the documentation at https://docs.pyth.network/documentation/pythnet-price-feeds/best-practices for how
-    /// to how this price safely.
+    /// to use this price safely.
     struct Price has copy, drop, store {
         price: I64,
         /// Confidence interval around the price