[Explorer] Change to consume @metaplex/js (#20637)
* Removed duplicated Metaplex logic/schemas and instead consume @metaplex/js * Bumped @metaplex/js version to 1.2.0 Co-authored-by: Will Roeder <roederw@wills-mbp.lan>
This commit is contained in:
		
							
								
								
									
										134
									
								
								explorer/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										134
									
								
								explorer/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -12,6 +12,7 @@
 | 
			
		||||
        "@bonfida/bot": "^0.5.3",
 | 
			
		||||
        "@cloudflare/stream-react": "^1.2.0",
 | 
			
		||||
        "@metamask/jazzicon": "^2.0.0",
 | 
			
		||||
        "@metaplex/js": "1.2.0",
 | 
			
		||||
        "@project-serum/serum": "^0.13.60",
 | 
			
		||||
        "@react-hook/debounce": "^4.0.0",
 | 
			
		||||
        "@sentry/react": "^6.13.3",
 | 
			
		||||
@@ -3499,6 +3500,83 @@
 | 
			
		||||
        "color-name": "^1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@metaplex/js": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@metaplex/js/-/js-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-qbfie0Zwish72Ry3UdA5YVgNzLYqLm86EOsnBos9UNkTRr+Yb2/Xh/l5cgws6lUNpnTLm9c8PaLRmtRvuLD3tQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@solana/spl-token": "^0.1.8",
 | 
			
		||||
        "@solana/web3.js": "^1.24.1",
 | 
			
		||||
        "@types/bs58": "^4.0.1",
 | 
			
		||||
        "axios": "^0.21.4",
 | 
			
		||||
        "bn.js": "^5.2.0",
 | 
			
		||||
        "borsh": "^0.4.0",
 | 
			
		||||
        "bs58": "^4.0.1",
 | 
			
		||||
        "buffer": "^6.0.3",
 | 
			
		||||
        "crypto-hash": "^1.3.0",
 | 
			
		||||
        "form-data": "^4.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@metaplex/js/node_modules/@solana/spl-token": {
 | 
			
		||||
      "version": "0.1.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.1.8.tgz",
 | 
			
		||||
      "integrity": "sha512-LZmYCKcPQDtJgecvWOgT/cnoIQPWjdH+QVyzPcFvyDUiT0DiRjZaam4aqNUyvchLFhzgunv3d9xOoyE34ofdoQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/runtime": "^7.10.5",
 | 
			
		||||
        "@solana/web3.js": "^1.21.0",
 | 
			
		||||
        "bn.js": "^5.1.0",
 | 
			
		||||
        "buffer": "6.0.3",
 | 
			
		||||
        "buffer-layout": "^1.2.0",
 | 
			
		||||
        "dotenv": "10.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@metaplex/js/node_modules/buffer": {
 | 
			
		||||
      "version": "6.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
 | 
			
		||||
      "funding": [
 | 
			
		||||
        {
 | 
			
		||||
          "type": "github",
 | 
			
		||||
          "url": "https://github.com/sponsors/feross"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "type": "patreon",
 | 
			
		||||
          "url": "https://www.patreon.com/feross"
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "type": "consulting",
 | 
			
		||||
          "url": "https://feross.org/support"
 | 
			
		||||
        }
 | 
			
		||||
      ],
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "base64-js": "^1.3.1",
 | 
			
		||||
        "ieee754": "^1.2.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@metaplex/js/node_modules/dotenv": {
 | 
			
		||||
      "version": "10.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=10"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@metaplex/js/node_modules/form-data": {
 | 
			
		||||
      "version": "4.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
 | 
			
		||||
      "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "asynckit": "^0.4.0",
 | 
			
		||||
        "combined-stream": "^1.0.8",
 | 
			
		||||
        "mime-types": "^2.1.12"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">= 6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@nodelib/fs.scandir": {
 | 
			
		||||
      "version": "2.1.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
 | 
			
		||||
@@ -28902,6 +28980,62 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@metaplex/js": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@metaplex/js/-/js-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-qbfie0Zwish72Ry3UdA5YVgNzLYqLm86EOsnBos9UNkTRr+Yb2/Xh/l5cgws6lUNpnTLm9c8PaLRmtRvuLD3tQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@solana/spl-token": "^0.1.8",
 | 
			
		||||
        "@solana/web3.js": "^1.24.1",
 | 
			
		||||
        "@types/bs58": "^4.0.1",
 | 
			
		||||
        "axios": "^0.21.4",
 | 
			
		||||
        "bn.js": "^5.2.0",
 | 
			
		||||
        "borsh": "^0.4.0",
 | 
			
		||||
        "bs58": "^4.0.1",
 | 
			
		||||
        "buffer": "^6.0.3",
 | 
			
		||||
        "crypto-hash": "^1.3.0",
 | 
			
		||||
        "form-data": "^4.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@solana/spl-token": {
 | 
			
		||||
          "version": "0.1.8",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/@solana/spl-token/-/spl-token-0.1.8.tgz",
 | 
			
		||||
          "integrity": "sha512-LZmYCKcPQDtJgecvWOgT/cnoIQPWjdH+QVyzPcFvyDUiT0DiRjZaam4aqNUyvchLFhzgunv3d9xOoyE34ofdoQ==",
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "@babel/runtime": "^7.10.5",
 | 
			
		||||
            "@solana/web3.js": "^1.21.0",
 | 
			
		||||
            "bn.js": "^5.1.0",
 | 
			
		||||
            "buffer": "6.0.3",
 | 
			
		||||
            "buffer-layout": "^1.2.0",
 | 
			
		||||
            "dotenv": "10.0.0"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "buffer": {
 | 
			
		||||
          "version": "6.0.3",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
 | 
			
		||||
          "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "base64-js": "^1.3.1",
 | 
			
		||||
            "ieee754": "^1.2.1"
 | 
			
		||||
          }
 | 
			
		||||
        },
 | 
			
		||||
        "dotenv": {
 | 
			
		||||
          "version": "10.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q=="
 | 
			
		||||
        },
 | 
			
		||||
        "form-data": {
 | 
			
		||||
          "version": "4.0.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
 | 
			
		||||
          "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "asynckit": "^0.4.0",
 | 
			
		||||
            "combined-stream": "^1.0.8",
 | 
			
		||||
            "mime-types": "^2.1.12"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@nodelib/fs.scandir": {
 | 
			
		||||
      "version": "2.1.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.4.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
    "@bonfida/bot": "^0.5.3",
 | 
			
		||||
    "@cloudflare/stream-react": "^1.2.0",
 | 
			
		||||
    "@metamask/jazzicon": "^2.0.0",
 | 
			
		||||
    "@metaplex/js": "1.2.0",
 | 
			
		||||
    "@project-serum/serum": "^0.13.60",
 | 
			
		||||
    "@react-hook/debounce": "^4.0.0",
 | 
			
		||||
    "@sentry/react": "^6.13.3",
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,11 @@
 | 
			
		||||
import "bootstrap/dist/js/bootstrap.min.js";
 | 
			
		||||
import { NFTData } from "providers/accounts";
 | 
			
		||||
import { Creator } from "metaplex/classes";
 | 
			
		||||
import { ArtContent } from "metaplex/Art/Art";
 | 
			
		||||
import { Creator } from "@metaplex/js";
 | 
			
		||||
import { ArtContent } from "components/common/NFTArt";
 | 
			
		||||
import { InfoTooltip } from "components/common/InfoTooltip";
 | 
			
		||||
import { EditionData } from "providers/accounts/utils/metadataHelpers";
 | 
			
		||||
import { clusterPath } from "utils/url";
 | 
			
		||||
import { Link } from "react-router-dom";
 | 
			
		||||
import { EditionInfo } from "providers/accounts/utils/getEditionInfo";
 | 
			
		||||
 | 
			
		||||
export function NFTHeader({
 | 
			
		||||
  nftData,
 | 
			
		||||
@@ -29,7 +29,7 @@ export function NFTHeader({
 | 
			
		||||
              ? metadata.data.name
 | 
			
		||||
              : "No NFT name was found"}
 | 
			
		||||
          </h2>
 | 
			
		||||
          {getEditionPill(nftData.editionData)}
 | 
			
		||||
          {getEditionPill(nftData.editionInfo)}
 | 
			
		||||
        </div>
 | 
			
		||||
        <h4 className="header-pretitle ml-1 mt-1">
 | 
			
		||||
          {metadata.data.symbol !== ""
 | 
			
		||||
@@ -127,9 +127,9 @@ function getCreatorDropdownItems(creators: Creator[] | null) {
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function getEditionPill(editionData?: EditionData) {
 | 
			
		||||
  const masterEdition = editionData?.masterEdition;
 | 
			
		||||
  const edition = editionData?.edition;
 | 
			
		||||
function getEditionPill(editionInfo: EditionInfo) {
 | 
			
		||||
  const masterEdition = editionInfo.masterEdition;
 | 
			
		||||
  const edition = editionInfo.edition;
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div className={"d-inline-flex ml-2"}>
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,7 @@ import { Copyable } from "components/common/Copyable";
 | 
			
		||||
import { CoingeckoStatus, useCoinGecko } from "utils/coingecko";
 | 
			
		||||
import { displayTimestampWithoutDate } from "utils/date";
 | 
			
		||||
import { LoadingCard } from "components/common/LoadingCard";
 | 
			
		||||
import { toPublicKey } from "metaplex/ids";
 | 
			
		||||
import { PublicKey } from "@solana/web3.js";
 | 
			
		||||
 | 
			
		||||
const getEthAddress = (link?: string) => {
 | 
			
		||||
  let address = "";
 | 
			
		||||
@@ -314,23 +314,23 @@ function NonFungibleTokenMintAccountCard({
 | 
			
		||||
            <Address pubkey={account.pubkey} alignRight raw />
 | 
			
		||||
          </td>
 | 
			
		||||
        </tr>
 | 
			
		||||
        {nftData?.editionData?.masterEdition?.maxSupply && (
 | 
			
		||||
        {nftData.editionInfo.masterEdition?.maxSupply && (
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td>Max Total Supply</td>
 | 
			
		||||
            <td className="text-lg-right">
 | 
			
		||||
              {nftData.editionData.masterEdition.maxSupply.toNumber() === 0
 | 
			
		||||
              {nftData.editionInfo.masterEdition.maxSupply.toNumber() === 0
 | 
			
		||||
                ? 1
 | 
			
		||||
                : nftData.editionData.masterEdition.maxSupply.toNumber()}
 | 
			
		||||
                : nftData.editionInfo.masterEdition.maxSupply.toNumber()}
 | 
			
		||||
            </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        )}
 | 
			
		||||
        {nftData?.editionData?.masterEdition?.supply && (
 | 
			
		||||
        {nftData?.editionInfo.masterEdition?.supply && (
 | 
			
		||||
          <tr>
 | 
			
		||||
            <td>Current Supply</td>
 | 
			
		||||
            <td className="text-lg-right">
 | 
			
		||||
              {nftData.editionData.masterEdition.supply.toNumber() === 0
 | 
			
		||||
              {nftData.editionInfo.masterEdition.supply.toNumber() === 0
 | 
			
		||||
                ? 1
 | 
			
		||||
                : nftData.editionData.masterEdition.supply.toNumber()}
 | 
			
		||||
                : nftData.editionInfo.masterEdition.supply.toNumber()}
 | 
			
		||||
            </td>
 | 
			
		||||
          </tr>
 | 
			
		||||
        )}
 | 
			
		||||
@@ -346,7 +346,7 @@ function NonFungibleTokenMintAccountCard({
 | 
			
		||||
          <td>Update Authority</td>
 | 
			
		||||
          <td className="text-lg-right">
 | 
			
		||||
            <Address
 | 
			
		||||
              pubkey={toPublicKey(nftData.metadata.updateAuthority)}
 | 
			
		||||
              pubkey={new PublicKey(nftData.metadata.updateAuthority)}
 | 
			
		||||
              alignRight
 | 
			
		||||
              link
 | 
			
		||||
            />
 | 
			
		||||
 
 | 
			
		||||
@@ -1,13 +1,15 @@
 | 
			
		||||
import { useCallback, useEffect, useState } from "react";
 | 
			
		||||
import { MetadataCategory, MetadataFile } from "../types";
 | 
			
		||||
import { pubkeyToString } from "../utils";
 | 
			
		||||
import { useCachedImage, useExtendedArt } from "./useArt";
 | 
			
		||||
import { Stream, StreamPlayerApi } from "@cloudflare/stream-react";
 | 
			
		||||
import { PublicKey } from "@solana/web3.js";
 | 
			
		||||
import { getLast } from "../utils";
 | 
			
		||||
import { Metadata } from "metaplex/classes";
 | 
			
		||||
import {
 | 
			
		||||
  MetadataData,
 | 
			
		||||
  MetadataJson,
 | 
			
		||||
  MetaDataJsonCategory,
 | 
			
		||||
  MetadataJsonFile,
 | 
			
		||||
} from "@metaplex/js";
 | 
			
		||||
import ContentLoader from "react-content-loader";
 | 
			
		||||
import ErrorLogo from "img/logos-solana/dark-solana-logo.svg";
 | 
			
		||||
import { getLast, pubkeyToString } from "utils";
 | 
			
		||||
 | 
			
		||||
const MAX_TIME_LOADING_IMAGE = 5000; /* 5 seconds */
 | 
			
		||||
 | 
			
		||||
@@ -90,7 +92,7 @@ const VideoArtContent = ({
 | 
			
		||||
  animationURL,
 | 
			
		||||
  active,
 | 
			
		||||
}: {
 | 
			
		||||
  files?: (MetadataFile | string)[];
 | 
			
		||||
  files?: (MetadataJsonFile | string)[];
 | 
			
		||||
  uri?: string;
 | 
			
		||||
  animationURL?: string;
 | 
			
		||||
  active?: boolean;
 | 
			
		||||
@@ -168,7 +170,7 @@ const HTMLContent = ({
 | 
			
		||||
  files,
 | 
			
		||||
}: {
 | 
			
		||||
  animationUrl?: string;
 | 
			
		||||
  files?: (MetadataFile | string)[];
 | 
			
		||||
  files?: (MetadataJsonFile | string)[];
 | 
			
		||||
}) => {
 | 
			
		||||
  const [loaded, setLoaded] = useState<boolean>(false);
 | 
			
		||||
  const htmlURL =
 | 
			
		||||
@@ -207,13 +209,13 @@ export const ArtContent = ({
 | 
			
		||||
  animationURL,
 | 
			
		||||
  files,
 | 
			
		||||
}: {
 | 
			
		||||
  metadata: Metadata;
 | 
			
		||||
  category?: MetadataCategory;
 | 
			
		||||
  metadata: MetadataData;
 | 
			
		||||
  category?: MetaDataJsonCategory;
 | 
			
		||||
  active?: boolean;
 | 
			
		||||
  pubkey?: PublicKey | string;
 | 
			
		||||
  uri?: string;
 | 
			
		||||
  animationURL?: string;
 | 
			
		||||
  files?: (MetadataFile | string)[];
 | 
			
		||||
  files?: (MetadataJsonFile | string)[];
 | 
			
		||||
}) => {
 | 
			
		||||
  const id = pubkeyToString(pubkey);
 | 
			
		||||
 | 
			
		||||
@@ -261,3 +263,113 @@ export const ArtContent = ({
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
enum ArtFetchStatus {
 | 
			
		||||
  ReadyToFetch,
 | 
			
		||||
  Fetching,
 | 
			
		||||
  FetchFailed,
 | 
			
		||||
  FetchSucceeded,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const cachedImages = new Map<string, string>();
 | 
			
		||||
export const useCachedImage = (uri: string) => {
 | 
			
		||||
  const [cachedBlob, setCachedBlob] = useState<string | undefined>(undefined);
 | 
			
		||||
  const [fetchStatus, setFetchStatus] = useState<ArtFetchStatus>(
 | 
			
		||||
    ArtFetchStatus.ReadyToFetch
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!uri) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (fetchStatus === ArtFetchStatus.FetchFailed) {
 | 
			
		||||
      setCachedBlob(uri);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = cachedImages.get(uri);
 | 
			
		||||
    if (result) {
 | 
			
		||||
      setCachedBlob(result);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (fetchStatus === ArtFetchStatus.ReadyToFetch) {
 | 
			
		||||
      (async () => {
 | 
			
		||||
        setFetchStatus(ArtFetchStatus.Fetching);
 | 
			
		||||
        let response: Response;
 | 
			
		||||
        try {
 | 
			
		||||
          response = await fetch(uri, { cache: "force-cache" });
 | 
			
		||||
        } catch {
 | 
			
		||||
          try {
 | 
			
		||||
            response = await fetch(uri, { cache: "reload" });
 | 
			
		||||
          } catch {
 | 
			
		||||
            if (uri?.startsWith("http")) {
 | 
			
		||||
              setCachedBlob(uri);
 | 
			
		||||
            }
 | 
			
		||||
            setFetchStatus(ArtFetchStatus.FetchFailed);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const blob = await response.blob();
 | 
			
		||||
        const blobURI = URL.createObjectURL(blob);
 | 
			
		||||
        cachedImages.set(uri, blobURI);
 | 
			
		||||
        setCachedBlob(blobURI);
 | 
			
		||||
        setFetchStatus(ArtFetchStatus.FetchSucceeded);
 | 
			
		||||
      })();
 | 
			
		||||
    }
 | 
			
		||||
  }, [uri, setCachedBlob, fetchStatus, setFetchStatus]);
 | 
			
		||||
 | 
			
		||||
  return { cachedBlob };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useExtendedArt = (id: string, metadata: MetadataData) => {
 | 
			
		||||
  const [data, setData] = useState<MetadataJson>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (id && !data) {
 | 
			
		||||
      if (metadata.data.uri) {
 | 
			
		||||
        const uri = metadata.data.uri;
 | 
			
		||||
 | 
			
		||||
        const processJson = (extended: any) => {
 | 
			
		||||
          if (!extended || extended?.properties?.files?.length === 0) {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (extended?.image) {
 | 
			
		||||
            extended.image = extended.image.startsWith("http")
 | 
			
		||||
              ? extended.image
 | 
			
		||||
              : `${metadata.data.uri}/${extended.image}`;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return extended;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
          fetch(uri)
 | 
			
		||||
            .then(async (_) => {
 | 
			
		||||
              try {
 | 
			
		||||
                const data = await _.json();
 | 
			
		||||
                try {
 | 
			
		||||
                  localStorage.setItem(uri, JSON.stringify(data));
 | 
			
		||||
                } catch {
 | 
			
		||||
                  // ignore
 | 
			
		||||
                }
 | 
			
		||||
                setData(processJson(data));
 | 
			
		||||
              } catch {
 | 
			
		||||
                return undefined;
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
              return undefined;
 | 
			
		||||
            });
 | 
			
		||||
        } catch (ex) {
 | 
			
		||||
          console.error(ex);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [id, data, setData, metadata.data.uri]);
 | 
			
		||||
 | 
			
		||||
  return { data };
 | 
			
		||||
};
 | 
			
		||||
@@ -1,113 +0,0 @@
 | 
			
		||||
import { IMetadataExtension, Metadata } from "metaplex/classes";
 | 
			
		||||
import { StringPublicKey } from "metaplex/types";
 | 
			
		||||
import { useEffect, useState } from "react";
 | 
			
		||||
 | 
			
		||||
enum ArtFetchStatus {
 | 
			
		||||
  ReadyToFetch,
 | 
			
		||||
  Fetching,
 | 
			
		||||
  FetchFailed,
 | 
			
		||||
  FetchSucceeded,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const cachedImages = new Map<string, string>();
 | 
			
		||||
export const useCachedImage = (uri: string) => {
 | 
			
		||||
  const [cachedBlob, setCachedBlob] = useState<string | undefined>(undefined);
 | 
			
		||||
  const [fetchStatus, setFetchStatus] = useState<ArtFetchStatus>(
 | 
			
		||||
    ArtFetchStatus.ReadyToFetch
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (!uri) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (fetchStatus === ArtFetchStatus.FetchFailed) {
 | 
			
		||||
      setCachedBlob(uri);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const result = cachedImages.get(uri);
 | 
			
		||||
    if (result) {
 | 
			
		||||
      setCachedBlob(result);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (fetchStatus === ArtFetchStatus.ReadyToFetch) {
 | 
			
		||||
      (async () => {
 | 
			
		||||
        setFetchStatus(ArtFetchStatus.Fetching);
 | 
			
		||||
        let response: Response;
 | 
			
		||||
        try {
 | 
			
		||||
          response = await fetch(uri, { cache: "force-cache" });
 | 
			
		||||
        } catch {
 | 
			
		||||
          try {
 | 
			
		||||
            response = await fetch(uri, { cache: "reload" });
 | 
			
		||||
          } catch {
 | 
			
		||||
            if (uri?.startsWith("http")) {
 | 
			
		||||
              setCachedBlob(uri);
 | 
			
		||||
            }
 | 
			
		||||
            setFetchStatus(ArtFetchStatus.FetchFailed);
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const blob = await response.blob();
 | 
			
		||||
        const blobURI = URL.createObjectURL(blob);
 | 
			
		||||
        cachedImages.set(uri, blobURI);
 | 
			
		||||
        setCachedBlob(blobURI);
 | 
			
		||||
        setFetchStatus(ArtFetchStatus.FetchSucceeded);
 | 
			
		||||
      })();
 | 
			
		||||
    }
 | 
			
		||||
  }, [uri, setCachedBlob, fetchStatus, setFetchStatus]);
 | 
			
		||||
 | 
			
		||||
  return { cachedBlob };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const useExtendedArt = (id: StringPublicKey, metadata: Metadata) => {
 | 
			
		||||
  const [data, setData] = useState<IMetadataExtension>();
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (id && !data) {
 | 
			
		||||
      if (metadata.data.uri) {
 | 
			
		||||
        const uri = metadata.data.uri;
 | 
			
		||||
 | 
			
		||||
        const processJson = (extended: any) => {
 | 
			
		||||
          if (!extended || extended?.properties?.files?.length === 0) {
 | 
			
		||||
            return;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          if (extended?.image) {
 | 
			
		||||
            extended.image = extended.image.startsWith("http")
 | 
			
		||||
              ? extended.image
 | 
			
		||||
              : `${metadata.data.uri}/${extended.image}`;
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          return extended;
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
          fetch(uri)
 | 
			
		||||
            .then(async (_) => {
 | 
			
		||||
              try {
 | 
			
		||||
                const data = await _.json();
 | 
			
		||||
                try {
 | 
			
		||||
                  localStorage.setItem(uri, JSON.stringify(data));
 | 
			
		||||
                } catch {
 | 
			
		||||
                  // ignore
 | 
			
		||||
                }
 | 
			
		||||
                setData(processJson(data));
 | 
			
		||||
              } catch {
 | 
			
		||||
                return undefined;
 | 
			
		||||
              }
 | 
			
		||||
            })
 | 
			
		||||
            .catch(() => {
 | 
			
		||||
              return undefined;
 | 
			
		||||
            });
 | 
			
		||||
        } catch (ex) {
 | 
			
		||||
          console.error(ex);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [id, data, setData, metadata.data.uri]);
 | 
			
		||||
 | 
			
		||||
  return { data };
 | 
			
		||||
};
 | 
			
		||||
@@ -1,296 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
    Taken from: https://github.com/metaplex-foundation/metaplex/blob/master/js/packages/common/src/actions/metadata.ts
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import BN from "bn.js";
 | 
			
		||||
import {
 | 
			
		||||
  StringPublicKey,
 | 
			
		||||
  EDITION_MARKER_BIT_SIZE,
 | 
			
		||||
  MetadataKey,
 | 
			
		||||
  FileOrString,
 | 
			
		||||
  MetadataCategory,
 | 
			
		||||
  MetaplexKey,
 | 
			
		||||
} from "./types";
 | 
			
		||||
 | 
			
		||||
export class MasterEditionV1 {
 | 
			
		||||
  key: MetadataKey;
 | 
			
		||||
  supply: BN;
 | 
			
		||||
  maxSupply?: BN;
 | 
			
		||||
  /// Can be used to mint tokens that give one-time permission to mint a single limited edition.
 | 
			
		||||
  printingMint: StringPublicKey;
 | 
			
		||||
  /// If you don't know how many printing tokens you are going to need, but you do know
 | 
			
		||||
  /// you are going to need some amount in the future, you can use a token from this mint.
 | 
			
		||||
  /// Coming back to token metadata with one of these tokens allows you to mint (one time)
 | 
			
		||||
  /// any number of printing tokens you want. This is used for instance by Auction Manager
 | 
			
		||||
  /// with participation NFTs, where we dont know how many people will bid and need participation
 | 
			
		||||
  /// printing tokens to redeem, so we give it ONE of these tokens to use after the auction is over,
 | 
			
		||||
  /// because when the auction begins we just dont know how many printing tokens we will need,
 | 
			
		||||
  /// but at the end we will. At the end it then burns this token with token-metadata to
 | 
			
		||||
  /// get the printing tokens it needs to give to bidders. Each bidder then redeems a printing token
 | 
			
		||||
  /// to get their limited editions.
 | 
			
		||||
  oneTimePrintingAuthorizationMint: StringPublicKey;
 | 
			
		||||
 | 
			
		||||
  constructor(args: {
 | 
			
		||||
    key: MetadataKey;
 | 
			
		||||
    supply: BN;
 | 
			
		||||
    maxSupply?: BN;
 | 
			
		||||
    printingMint: StringPublicKey;
 | 
			
		||||
    oneTimePrintingAuthorizationMint: StringPublicKey;
 | 
			
		||||
  }) {
 | 
			
		||||
    this.key = MetadataKey.MasterEditionV1;
 | 
			
		||||
    this.supply = args.supply;
 | 
			
		||||
    this.maxSupply = args.maxSupply;
 | 
			
		||||
    this.printingMint = args.printingMint;
 | 
			
		||||
    this.oneTimePrintingAuthorizationMint =
 | 
			
		||||
      args.oneTimePrintingAuthorizationMint;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class MasterEditionV2 {
 | 
			
		||||
  key: MetadataKey;
 | 
			
		||||
  supply: BN;
 | 
			
		||||
  maxSupply?: BN;
 | 
			
		||||
 | 
			
		||||
  constructor(args: { key: MetadataKey; supply: BN; maxSupply?: BN }) {
 | 
			
		||||
    this.key = MetadataKey.MasterEditionV2;
 | 
			
		||||
    this.supply = args.supply;
 | 
			
		||||
    this.maxSupply = args.maxSupply;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class EditionMarker {
 | 
			
		||||
  key: MetadataKey;
 | 
			
		||||
  ledger: number[];
 | 
			
		||||
 | 
			
		||||
  constructor(args: { key: MetadataKey; ledger: number[] }) {
 | 
			
		||||
    this.key = MetadataKey.EditionMarker;
 | 
			
		||||
    this.ledger = args.ledger;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  editionTaken(edition: number) {
 | 
			
		||||
    const editionOffset = edition % EDITION_MARKER_BIT_SIZE;
 | 
			
		||||
    const indexOffset = Math.floor(editionOffset / 8);
 | 
			
		||||
 | 
			
		||||
    if (indexOffset > 30) {
 | 
			
		||||
      throw Error("bad index for edition");
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const positionInBitsetFromRight = 7 - (editionOffset % 8);
 | 
			
		||||
 | 
			
		||||
    const mask = Math.pow(2, positionInBitsetFromRight);
 | 
			
		||||
 | 
			
		||||
    const appliedMask = this.ledger[indexOffset] & mask;
 | 
			
		||||
 | 
			
		||||
    return appliedMask !== 0;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Edition {
 | 
			
		||||
  key: MetadataKey;
 | 
			
		||||
  /// Points at MasterEdition struct
 | 
			
		||||
  parent: StringPublicKey;
 | 
			
		||||
  /// Starting at 0 for master record, this is incremented for each edition minted.
 | 
			
		||||
  edition: BN;
 | 
			
		||||
 | 
			
		||||
  constructor(args: {
 | 
			
		||||
    key: MetadataKey;
 | 
			
		||||
    parent: StringPublicKey;
 | 
			
		||||
    edition: BN;
 | 
			
		||||
  }) {
 | 
			
		||||
    this.key = MetadataKey.EditionV1;
 | 
			
		||||
    this.parent = args.parent;
 | 
			
		||||
    this.edition = args.edition;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
export class Creator {
 | 
			
		||||
  address: StringPublicKey;
 | 
			
		||||
  verified: boolean;
 | 
			
		||||
  share: number;
 | 
			
		||||
 | 
			
		||||
  constructor(args: {
 | 
			
		||||
    address: StringPublicKey;
 | 
			
		||||
    verified: boolean;
 | 
			
		||||
    share: number;
 | 
			
		||||
  }) {
 | 
			
		||||
    this.address = args.address;
 | 
			
		||||
    this.verified = args.verified;
 | 
			
		||||
    this.share = args.share;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Data {
 | 
			
		||||
  name: string;
 | 
			
		||||
  symbol: string;
 | 
			
		||||
  uri: string;
 | 
			
		||||
  sellerFeeBasisPoints: number;
 | 
			
		||||
  creators: Creator[] | null;
 | 
			
		||||
  constructor(args: {
 | 
			
		||||
    name: string;
 | 
			
		||||
    symbol: string;
 | 
			
		||||
    uri: string;
 | 
			
		||||
    sellerFeeBasisPoints: number;
 | 
			
		||||
    creators: Creator[] | null;
 | 
			
		||||
  }) {
 | 
			
		||||
    this.name = args.name;
 | 
			
		||||
    this.symbol = args.symbol;
 | 
			
		||||
    this.uri = args.uri;
 | 
			
		||||
    this.sellerFeeBasisPoints = args.sellerFeeBasisPoints;
 | 
			
		||||
    this.creators = args.creators;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class Metadata {
 | 
			
		||||
  key: MetadataKey;
 | 
			
		||||
  updateAuthority: StringPublicKey;
 | 
			
		||||
  mint: StringPublicKey;
 | 
			
		||||
  data: Data;
 | 
			
		||||
  primarySaleHappened: boolean;
 | 
			
		||||
  isMutable: boolean;
 | 
			
		||||
  editionNonce: number | null;
 | 
			
		||||
 | 
			
		||||
  constructor(args: {
 | 
			
		||||
    updateAuthority: StringPublicKey;
 | 
			
		||||
    mint: StringPublicKey;
 | 
			
		||||
    data: Data;
 | 
			
		||||
    primarySaleHappened: boolean;
 | 
			
		||||
    isMutable: boolean;
 | 
			
		||||
    editionNonce: number | null;
 | 
			
		||||
  }) {
 | 
			
		||||
    this.key = MetadataKey.MetadataV1;
 | 
			
		||||
    this.updateAuthority = args.updateAuthority;
 | 
			
		||||
    this.mint = args.mint;
 | 
			
		||||
    this.data = args.data;
 | 
			
		||||
    this.primarySaleHappened = args.primarySaleHappened;
 | 
			
		||||
    this.isMutable = args.isMutable;
 | 
			
		||||
    this.editionNonce = args.editionNonce;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IMetadataExtension {
 | 
			
		||||
  name: string;
 | 
			
		||||
  symbol: string;
 | 
			
		||||
 | 
			
		||||
  creators: Creator[] | null;
 | 
			
		||||
  description: string;
 | 
			
		||||
  // preview image absolute URI
 | 
			
		||||
  image: string;
 | 
			
		||||
  animation_url?: string;
 | 
			
		||||
 | 
			
		||||
  // stores link to item on meta
 | 
			
		||||
  external_url: string;
 | 
			
		||||
 | 
			
		||||
  seller_fee_basis_points: number;
 | 
			
		||||
 | 
			
		||||
  properties: {
 | 
			
		||||
    files?: FileOrString[];
 | 
			
		||||
    category: MetadataCategory;
 | 
			
		||||
    maxSupply?: number;
 | 
			
		||||
    creators?: {
 | 
			
		||||
      address: string;
 | 
			
		||||
      shares: number;
 | 
			
		||||
    }[];
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const METADATA_SCHEMA = new Map<any, any>([
 | 
			
		||||
  [
 | 
			
		||||
    MasterEditionV1,
 | 
			
		||||
    {
 | 
			
		||||
      kind: "struct",
 | 
			
		||||
      fields: [
 | 
			
		||||
        ["key", "u8"],
 | 
			
		||||
        ["supply", "u64"],
 | 
			
		||||
        ["maxSupply", { kind: "option", type: "u64" }],
 | 
			
		||||
        ["printingMint", "pubkeyAsString"],
 | 
			
		||||
        ["oneTimePrintingAuthorizationMint", "pubkeyAsString"],
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    MasterEditionV2,
 | 
			
		||||
    {
 | 
			
		||||
      kind: "struct",
 | 
			
		||||
      fields: [
 | 
			
		||||
        ["key", "u8"],
 | 
			
		||||
        ["supply", "u64"],
 | 
			
		||||
        ["maxSupply", { kind: "option", type: "u64" }],
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    Edition,
 | 
			
		||||
    {
 | 
			
		||||
      kind: "struct",
 | 
			
		||||
      fields: [
 | 
			
		||||
        ["key", "u8"],
 | 
			
		||||
        ["parent", "pubkeyAsString"],
 | 
			
		||||
        ["edition", "u64"],
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    Data,
 | 
			
		||||
    {
 | 
			
		||||
      kind: "struct",
 | 
			
		||||
      fields: [
 | 
			
		||||
        ["name", "string"],
 | 
			
		||||
        ["symbol", "string"],
 | 
			
		||||
        ["uri", "string"],
 | 
			
		||||
        ["sellerFeeBasisPoints", "u16"],
 | 
			
		||||
        ["creators", { kind: "option", type: [Creator] }],
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    Creator,
 | 
			
		||||
    {
 | 
			
		||||
      kind: "struct",
 | 
			
		||||
      fields: [
 | 
			
		||||
        ["address", "pubkeyAsString"],
 | 
			
		||||
        ["verified", "u8"],
 | 
			
		||||
        ["share", "u8"],
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    Metadata,
 | 
			
		||||
    {
 | 
			
		||||
      kind: "struct",
 | 
			
		||||
      fields: [
 | 
			
		||||
        ["key", "u8"],
 | 
			
		||||
        ["updateAuthority", "pubkeyAsString"],
 | 
			
		||||
        ["mint", "pubkeyAsString"],
 | 
			
		||||
        ["data", Data],
 | 
			
		||||
        ["primarySaleHappened", "u8"], // bool
 | 
			
		||||
        ["isMutable", "u8"], // bool
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
  [
 | 
			
		||||
    EditionMarker,
 | 
			
		||||
    {
 | 
			
		||||
      kind: "struct",
 | 
			
		||||
      fields: [
 | 
			
		||||
        ["key", "u8"],
 | 
			
		||||
        ["ledger", [31]],
 | 
			
		||||
      ],
 | 
			
		||||
    },
 | 
			
		||||
  ],
 | 
			
		||||
]);
 | 
			
		||||
 | 
			
		||||
export class WhitelistedCreator {
 | 
			
		||||
  key: MetaplexKey = MetaplexKey.WhitelistedCreatorV1;
 | 
			
		||||
  address: StringPublicKey;
 | 
			
		||||
  activated: boolean = true;
 | 
			
		||||
 | 
			
		||||
  // Populated from name service
 | 
			
		||||
  twitter?: string;
 | 
			
		||||
  name?: string;
 | 
			
		||||
  image?: string;
 | 
			
		||||
  description?: string;
 | 
			
		||||
 | 
			
		||||
  constructor(args: { address: string; activated: boolean }) {
 | 
			
		||||
    this.address = args.address;
 | 
			
		||||
    this.activated = args.activated;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,72 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
    Taken from: https://github.com/metaplex-foundation/metaplex/blob/master/js/packages/common/src/utils/ids.ts
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import { PublicKey, AccountInfo } from "@solana/web3.js";
 | 
			
		||||
 | 
			
		||||
export type StringPublicKey = string;
 | 
			
		||||
 | 
			
		||||
export class LazyAccountInfoProxy<T> {
 | 
			
		||||
  executable: boolean = false;
 | 
			
		||||
  owner: StringPublicKey = "";
 | 
			
		||||
  lamports: number = 0;
 | 
			
		||||
 | 
			
		||||
  get data() {
 | 
			
		||||
    //
 | 
			
		||||
    return undefined as unknown as T;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface LazyAccountInfo {
 | 
			
		||||
  executable: boolean;
 | 
			
		||||
  owner: StringPublicKey;
 | 
			
		||||
  lamports: number;
 | 
			
		||||
  data: [string, string];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const PubKeysInternedMap = new Map<string, PublicKey>();
 | 
			
		||||
 | 
			
		||||
export const toPublicKey = (key: string | PublicKey) => {
 | 
			
		||||
  if (typeof key !== "string") {
 | 
			
		||||
    return key;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let result = PubKeysInternedMap.get(key);
 | 
			
		||||
  if (!result) {
 | 
			
		||||
    result = new PublicKey(key);
 | 
			
		||||
    PubKeysInternedMap.set(key, result);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return result;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface PublicKeyStringAndAccount<T> {
 | 
			
		||||
  pubkey: string;
 | 
			
		||||
  account: AccountInfo<T>;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
 | 
			
		||||
  "ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL"
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const BPF_UPGRADE_LOADER_ID = new PublicKey(
 | 
			
		||||
  "BPFLoaderUpgradeab1e11111111111111111111111"
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const MEMO_ID = new PublicKey(
 | 
			
		||||
  "MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr"
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
export const METADATA_PROGRAM_ID =
 | 
			
		||||
  "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s" as StringPublicKey;
 | 
			
		||||
 | 
			
		||||
export const VAULT_ID =
 | 
			
		||||
  "vau1zxA2LbssAUEF7Gpw91zMM1LvXrvpzJtmZ58rPsn" as StringPublicKey;
 | 
			
		||||
 | 
			
		||||
export const AUCTION_ID =
 | 
			
		||||
  "auctxRXPeJoc4817jDhf4HbjnhEcr1cCXenosMhK5R8" as StringPublicKey;
 | 
			
		||||
 | 
			
		||||
export const METAPLEX_ID =
 | 
			
		||||
  "p1exdMJcjVao65QdewkaZRUnU6VPSXhus9n2GzWfh98" as StringPublicKey;
 | 
			
		||||
 | 
			
		||||
export const SYSTEM = new PublicKey("11111111111111111111111111111111");
 | 
			
		||||
@@ -1,101 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
    Taken from: https://github.com/metaplex-foundation/metaplex/blob/master/js/packages/common/src/actions/metadata.ts
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
export type StringPublicKey = string;
 | 
			
		||||
 | 
			
		||||
export const EDITION = "edition";
 | 
			
		||||
export const METADATA_PREFIX = "metadata";
 | 
			
		||||
 | 
			
		||||
export const MAX_AUCTION_DATA_EXTENDED_SIZE = 8 + 9 + 2 + 200;
 | 
			
		||||
 | 
			
		||||
export const MAX_NAME_LENGTH = 32;
 | 
			
		||||
export const MAX_SYMBOL_LENGTH = 10;
 | 
			
		||||
export const MAX_URI_LENGTH = 200;
 | 
			
		||||
export const MAX_CREATOR_LIMIT = 5;
 | 
			
		||||
export const EDITION_MARKER_BIT_SIZE = 248;
 | 
			
		||||
export const MAX_CREATOR_LEN = 32 + 1 + 1;
 | 
			
		||||
export const MAX_METADATA_LEN =
 | 
			
		||||
  1 +
 | 
			
		||||
  32 +
 | 
			
		||||
  32 +
 | 
			
		||||
  MAX_NAME_LENGTH +
 | 
			
		||||
  MAX_SYMBOL_LENGTH +
 | 
			
		||||
  MAX_URI_LENGTH +
 | 
			
		||||
  MAX_CREATOR_LIMIT * MAX_CREATOR_LEN +
 | 
			
		||||
  2 +
 | 
			
		||||
  1 +
 | 
			
		||||
  1 +
 | 
			
		||||
  198;
 | 
			
		||||
 | 
			
		||||
export enum MetadataKey {
 | 
			
		||||
  Uninitialized = 0,
 | 
			
		||||
  MetadataV1 = 4,
 | 
			
		||||
  EditionV1 = 1,
 | 
			
		||||
  MasterEditionV1 = 2,
 | 
			
		||||
  MasterEditionV2 = 6,
 | 
			
		||||
  EditionMarker = 7,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum MetadataCategory {
 | 
			
		||||
  Audio = "audio",
 | 
			
		||||
  Video = "video",
 | 
			
		||||
  Image = "image",
 | 
			
		||||
  VR = "vr",
 | 
			
		||||
  HTML = "html",
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type MetadataFile = {
 | 
			
		||||
  uri: string;
 | 
			
		||||
  type: string;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type FileOrString = MetadataFile | string;
 | 
			
		||||
 | 
			
		||||
export interface Auction {
 | 
			
		||||
  name: string;
 | 
			
		||||
  auctionerName: string;
 | 
			
		||||
  auctionerLink: string;
 | 
			
		||||
  highestBid: number;
 | 
			
		||||
  solAmt: number;
 | 
			
		||||
  link: string;
 | 
			
		||||
  image: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface Artist {
 | 
			
		||||
  address?: string;
 | 
			
		||||
  name: string;
 | 
			
		||||
  link: string;
 | 
			
		||||
  image: string;
 | 
			
		||||
  itemsAvailable?: number;
 | 
			
		||||
  itemsSold?: number;
 | 
			
		||||
  about?: string;
 | 
			
		||||
  verified?: boolean;
 | 
			
		||||
 | 
			
		||||
  share?: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum ArtType {
 | 
			
		||||
  Master,
 | 
			
		||||
  Print,
 | 
			
		||||
  NFT,
 | 
			
		||||
}
 | 
			
		||||
export interface Art {
 | 
			
		||||
  url: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export enum MetaplexKey {
 | 
			
		||||
  Uninitialized = 0,
 | 
			
		||||
  OriginalAuthorityLookupV1 = 1,
 | 
			
		||||
  BidRedemptionTicketV1 = 2,
 | 
			
		||||
  StoreV1 = 3,
 | 
			
		||||
  WhitelistedCreatorV1 = 4,
 | 
			
		||||
  PayoutTicketV1 = 5,
 | 
			
		||||
  SafetyDepositValidationTicketV1 = 6,
 | 
			
		||||
  AuctionManagerV1 = 7,
 | 
			
		||||
  PrizeTrackingTicketV1 = 8,
 | 
			
		||||
  SafetyDepositConfigV1 = 9,
 | 
			
		||||
  AuctionManagerV2 = 10,
 | 
			
		||||
  BidRedemptionTicketV2 = 11,
 | 
			
		||||
  AuctionWinnerTokenTypeTrackerV1 = 12,
 | 
			
		||||
}
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
    Taken from: https://github.com/metaplex-foundation/metaplex/blob/master/js/packages/common/src/utils/utils.ts
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
import { PublicKey } from "@solana/web3.js";
 | 
			
		||||
 | 
			
		||||
export const pubkeyToString = (key: PublicKey | string = "") => {
 | 
			
		||||
  return typeof key === "string" ? key : key?.toBase58() || "";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getLast = <T>(arr: T[]) => {
 | 
			
		||||
  if (arr.length <= 0) {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return arr[arr.length - 1];
 | 
			
		||||
};
 | 
			
		||||
@@ -25,12 +25,8 @@ import {
 | 
			
		||||
  UpgradeableLoaderAccount,
 | 
			
		||||
} from "validators/accounts/upgradeable-program";
 | 
			
		||||
import { RewardsProvider } from "./rewards";
 | 
			
		||||
import { Metadata } from "metaplex/classes";
 | 
			
		||||
import {
 | 
			
		||||
  EditionData,
 | 
			
		||||
  getEditionData,
 | 
			
		||||
  getMetadata,
 | 
			
		||||
} from "./utils/metadataHelpers";
 | 
			
		||||
import { Metadata, MetadataData } from "@metaplex/js";
 | 
			
		||||
import getEditionInfo, { EditionInfo } from "./utils/getEditionInfo";
 | 
			
		||||
export { useAccountHistory } from "./history";
 | 
			
		||||
 | 
			
		||||
export type StakeProgramData = {
 | 
			
		||||
@@ -46,8 +42,8 @@ export type UpgradeableLoaderAccountData = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type NFTData = {
 | 
			
		||||
  metadata: Metadata;
 | 
			
		||||
  editionData?: EditionData;
 | 
			
		||||
  metadata: MetadataData;
 | 
			
		||||
  editionInfo: EditionInfo;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type TokenProgramData = {
 | 
			
		||||
@@ -243,11 +239,17 @@ async function fetchAccountInfo(
 | 
			
		||||
 | 
			
		||||
              // Generate a PDA and check for a Metadata Account
 | 
			
		||||
              if (parsed.type === "mint") {
 | 
			
		||||
                const metadata = await getMetadata(pubkey, url);
 | 
			
		||||
                const metadata = await Metadata.load(
 | 
			
		||||
                  connection,
 | 
			
		||||
                  await Metadata.getPDA(pubkey)
 | 
			
		||||
                );
 | 
			
		||||
                if (metadata) {
 | 
			
		||||
                  // We have a valid Metadata account. Try and pull edition data.
 | 
			
		||||
                  const editionData = await getEditionData(pubkey, url);
 | 
			
		||||
                  nftData = { metadata, editionData };
 | 
			
		||||
                  const editionInfo = await getEditionInfo(
 | 
			
		||||
                    metadata,
 | 
			
		||||
                    connection
 | 
			
		||||
                  );
 | 
			
		||||
                  nftData = { metadata: metadata.data, editionInfo };
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
              data = {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										52
									
								
								explorer/src/providers/accounts/utils/getEditionInfo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								explorer/src/providers/accounts/utils/getEditionInfo.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
			
		||||
import {
 | 
			
		||||
  EditionData,
 | 
			
		||||
  MasterEdition,
 | 
			
		||||
  MasterEditionData,
 | 
			
		||||
  Metadata,
 | 
			
		||||
  MetadataKey,
 | 
			
		||||
} from "@metaplex/js";
 | 
			
		||||
import { Connection } from "@solana/web3.js";
 | 
			
		||||
 | 
			
		||||
export type EditionInfo = {
 | 
			
		||||
  masterEdition?: MasterEditionData;
 | 
			
		||||
  edition?: EditionData;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default async function getEditionInfo(
 | 
			
		||||
  metadata: Metadata,
 | 
			
		||||
  connection: Connection
 | 
			
		||||
): Promise<EditionInfo> {
 | 
			
		||||
  try {
 | 
			
		||||
    const edition = (await metadata.getEdition(connection)).data;
 | 
			
		||||
 | 
			
		||||
    if (edition) {
 | 
			
		||||
      if (
 | 
			
		||||
        edition.key === MetadataKey.MasterEditionV1 ||
 | 
			
		||||
        edition.key === MetadataKey.MasterEditionV2
 | 
			
		||||
      ) {
 | 
			
		||||
        return {
 | 
			
		||||
          masterEdition: edition as MasterEditionData,
 | 
			
		||||
          edition: undefined,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // This is an Edition NFT. Pull the Parent (MasterEdition)
 | 
			
		||||
      const masterEdition = (
 | 
			
		||||
        await MasterEdition.load(connection, (edition as EditionData).parent)
 | 
			
		||||
      ).data;
 | 
			
		||||
      if (masterEdition) {
 | 
			
		||||
        return {
 | 
			
		||||
          masterEdition,
 | 
			
		||||
          edition: edition as EditionData,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } catch {
 | 
			
		||||
    /* ignore */
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return {
 | 
			
		||||
    masterEdition: undefined,
 | 
			
		||||
    edition: undefined,
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
@@ -1,219 +0,0 @@
 | 
			
		||||
import { AccountInfo, Connection, PublicKey } from "@solana/web3.js";
 | 
			
		||||
import {
 | 
			
		||||
  Edition,
 | 
			
		||||
  MasterEditionV1,
 | 
			
		||||
  MasterEditionV2,
 | 
			
		||||
  Metadata,
 | 
			
		||||
  METADATA_SCHEMA,
 | 
			
		||||
} from "metaplex/classes";
 | 
			
		||||
import { MetadataKey, METADATA_PREFIX, StringPublicKey } from "metaplex/types";
 | 
			
		||||
import { deserializeUnchecked, BinaryReader, BinaryWriter } from "borsh";
 | 
			
		||||
import base58 from "bs58";
 | 
			
		||||
import {
 | 
			
		||||
  METADATA_PROGRAM_ID,
 | 
			
		||||
  SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
 | 
			
		||||
  METAPLEX_ID,
 | 
			
		||||
  BPF_UPGRADE_LOADER_ID,
 | 
			
		||||
  SYSTEM,
 | 
			
		||||
  MEMO_ID,
 | 
			
		||||
  VAULT_ID,
 | 
			
		||||
  AUCTION_ID,
 | 
			
		||||
  toPublicKey,
 | 
			
		||||
} from "metaplex/ids";
 | 
			
		||||
import { TOKEN_PROGRAM_ID } from "providers/accounts/tokens";
 | 
			
		||||
 | 
			
		||||
let STORE: PublicKey | undefined;
 | 
			
		||||
 | 
			
		||||
export type EditionData = {
 | 
			
		||||
  masterEdition?: MasterEditionV1 | MasterEditionV2;
 | 
			
		||||
  edition?: Edition;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const programIds = () => {
 | 
			
		||||
  return {
 | 
			
		||||
    token: TOKEN_PROGRAM_ID,
 | 
			
		||||
    associatedToken: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
 | 
			
		||||
    bpf_upgrade_loader: BPF_UPGRADE_LOADER_ID,
 | 
			
		||||
    system: SYSTEM,
 | 
			
		||||
    metadata: METADATA_PROGRAM_ID,
 | 
			
		||||
    memo: MEMO_ID,
 | 
			
		||||
    vault: VAULT_ID,
 | 
			
		||||
    auction: AUCTION_ID,
 | 
			
		||||
    metaplex: METAPLEX_ID,
 | 
			
		||||
    store: STORE,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export async function getMetadata(
 | 
			
		||||
  pubkey: PublicKey,
 | 
			
		||||
  url: string
 | 
			
		||||
): Promise<Metadata | undefined> {
 | 
			
		||||
  const connection = new Connection(url, "confirmed");
 | 
			
		||||
  const metadataKey = await generatePDA(pubkey);
 | 
			
		||||
  const accountInfo = await connection.getAccountInfo(toPublicKey(metadataKey));
 | 
			
		||||
 | 
			
		||||
  if (accountInfo && accountInfo.data.length > 0) {
 | 
			
		||||
    if (!isMetadataAccount(accountInfo)) return;
 | 
			
		||||
 | 
			
		||||
    if (isMetadataV1Account(accountInfo)) {
 | 
			
		||||
      const metadata = decodeMetadata(accountInfo.data);
 | 
			
		||||
 | 
			
		||||
      if (isValidHttpUrl(metadata.data.uri)) {
 | 
			
		||||
        return metadata;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getEditionData(
 | 
			
		||||
  pubkey: PublicKey,
 | 
			
		||||
  url: string
 | 
			
		||||
): Promise<EditionData | undefined> {
 | 
			
		||||
  const connection = new Connection(url, "confirmed");
 | 
			
		||||
  const editionKey = await generatePDA(pubkey, true /* addEditionToSeeds */);
 | 
			
		||||
  const accountInfo = await connection.getAccountInfo(toPublicKey(editionKey));
 | 
			
		||||
 | 
			
		||||
  if (accountInfo && accountInfo.data.length > 0) {
 | 
			
		||||
    if (!isMetadataAccount(accountInfo)) return;
 | 
			
		||||
 | 
			
		||||
    if (isMasterEditionAccount(accountInfo)) {
 | 
			
		||||
      return {
 | 
			
		||||
        masterEdition: decodeMasterEdition(accountInfo.data),
 | 
			
		||||
        edition: undefined,
 | 
			
		||||
      };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // This is an Edition NFT. Pull the Parent (MasterEdition)
 | 
			
		||||
    if (isEditionV1Account(accountInfo)) {
 | 
			
		||||
      const edition = decodeEdition(accountInfo.data);
 | 
			
		||||
      const masterEditionAccountInfo = await connection.getAccountInfo(
 | 
			
		||||
        toPublicKey(edition.parent)
 | 
			
		||||
      );
 | 
			
		||||
 | 
			
		||||
      if (
 | 
			
		||||
        masterEditionAccountInfo &&
 | 
			
		||||
        masterEditionAccountInfo.data.length > 0 &&
 | 
			
		||||
        isMasterEditionAccount(masterEditionAccountInfo)
 | 
			
		||||
      ) {
 | 
			
		||||
        return {
 | 
			
		||||
          masterEdition: decodeMasterEdition(masterEditionAccountInfo.data),
 | 
			
		||||
          edition,
 | 
			
		||||
        };
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function generatePDA(
 | 
			
		||||
  tokenMint: PublicKey,
 | 
			
		||||
  addEditionToSeeds: boolean = false
 | 
			
		||||
): Promise<PublicKey> {
 | 
			
		||||
  const PROGRAM_IDS = programIds();
 | 
			
		||||
 | 
			
		||||
  const metadataSeeds = [
 | 
			
		||||
    Buffer.from(METADATA_PREFIX),
 | 
			
		||||
    toPublicKey(PROGRAM_IDS.metadata).toBuffer(),
 | 
			
		||||
    tokenMint.toBuffer(),
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  if (addEditionToSeeds) {
 | 
			
		||||
    metadataSeeds.push(Buffer.from("edition"));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    await PublicKey.findProgramAddress(
 | 
			
		||||
      metadataSeeds,
 | 
			
		||||
      toPublicKey(PROGRAM_IDS.metadata)
 | 
			
		||||
    )
 | 
			
		||||
  )[0];
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const decodeMetadata = (buffer: Buffer): Metadata => {
 | 
			
		||||
  const metadata = deserializeUnchecked(
 | 
			
		||||
    METADATA_SCHEMA,
 | 
			
		||||
    Metadata,
 | 
			
		||||
    buffer
 | 
			
		||||
  ) as Metadata;
 | 
			
		||||
 | 
			
		||||
  // Remove any trailing null characters from the deserialized strings
 | 
			
		||||
  metadata.data.name = metadata.data.name.replace(/\0/g, "");
 | 
			
		||||
  metadata.data.symbol = metadata.data.symbol.replace(/\0/g, "");
 | 
			
		||||
  metadata.data.uri = metadata.data.uri.replace(/\0/g, "");
 | 
			
		||||
  metadata.data.name = metadata.data.name.replace(/\0/g, "");
 | 
			
		||||
  return metadata;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const decodeMasterEdition = (
 | 
			
		||||
  buffer: Buffer
 | 
			
		||||
): MasterEditionV1 | MasterEditionV2 => {
 | 
			
		||||
  if (buffer[0] === MetadataKey.MasterEditionV1) {
 | 
			
		||||
    return deserializeUnchecked(
 | 
			
		||||
      METADATA_SCHEMA,
 | 
			
		||||
      MasterEditionV1,
 | 
			
		||||
      buffer
 | 
			
		||||
    ) as MasterEditionV1;
 | 
			
		||||
  } else {
 | 
			
		||||
    return deserializeUnchecked(
 | 
			
		||||
      METADATA_SCHEMA,
 | 
			
		||||
      MasterEditionV2,
 | 
			
		||||
      buffer
 | 
			
		||||
    ) as MasterEditionV2;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const decodeEdition = (buffer: Buffer) => {
 | 
			
		||||
  return deserializeUnchecked(METADATA_SCHEMA, Edition, buffer) as Edition;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const isMetadataAccount = (account: AccountInfo<Buffer>) =>
 | 
			
		||||
  account.owner.toBase58() === METADATA_PROGRAM_ID;
 | 
			
		||||
 | 
			
		||||
const isMetadataV1Account = (account: AccountInfo<Buffer>) =>
 | 
			
		||||
  account.data[0] === MetadataKey.MetadataV1;
 | 
			
		||||
 | 
			
		||||
const isEditionV1Account = (account: AccountInfo<Buffer>) =>
 | 
			
		||||
  account.data[0] === MetadataKey.EditionV1;
 | 
			
		||||
 | 
			
		||||
const isMasterEditionAccount = (account: AccountInfo<Buffer>) =>
 | 
			
		||||
  account.data[0] === MetadataKey.MasterEditionV1 ||
 | 
			
		||||
  account.data[0] === MetadataKey.MasterEditionV2;
 | 
			
		||||
 | 
			
		||||
function isValidHttpUrl(text: string) {
 | 
			
		||||
  try {
 | 
			
		||||
    const url = new URL(text);
 | 
			
		||||
    return url.protocol === "http:" || url.protocol === "https:";
 | 
			
		||||
  } catch (_) {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Required to properly serialize and deserialize pubKeyAsString types
 | 
			
		||||
const extendBorsh = () => {
 | 
			
		||||
  (BinaryReader.prototype as any).readPubkey = function () {
 | 
			
		||||
    const reader = this as unknown as BinaryReader;
 | 
			
		||||
    const array = reader.readFixedArray(32);
 | 
			
		||||
    return new PublicKey(array);
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  (BinaryWriter.prototype as any).writePubkey = function (value: any) {
 | 
			
		||||
    const writer = this as unknown as BinaryWriter;
 | 
			
		||||
    writer.writeFixedArray(value.toBuffer());
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  (BinaryReader.prototype as any).readPubkeyAsString = function () {
 | 
			
		||||
    const reader = this as unknown as BinaryReader;
 | 
			
		||||
    const array = reader.readFixedArray(32);
 | 
			
		||||
    return base58.encode(array) as StringPublicKey;
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  (BinaryWriter.prototype as any).writePubkeyAsString = function (
 | 
			
		||||
    value: StringPublicKey
 | 
			
		||||
  ) {
 | 
			
		||||
    const writer = this as unknown as BinaryWriter;
 | 
			
		||||
    writer.writeFixedArray(base58.decode(value));
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
extendBorsh();
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import BN from "bn.js";
 | 
			
		||||
import {
 | 
			
		||||
  HumanizeDuration,
 | 
			
		||||
  HumanizeDurationLanguage,
 | 
			
		||||
} from "humanize-duration-ts";
 | 
			
		||||
import { PublicKey } from "@solana/web3.js";
 | 
			
		||||
 | 
			
		||||
// Switch to web3 constant when web3 updates superstruct
 | 
			
		||||
export const LAMPORTS_PER_SOL = 1000000000;
 | 
			
		||||
@@ -133,3 +133,15 @@ export function abbreviatedNumber(value: number, fixed = 1) {
 | 
			
		||||
  if (value >= 1e9 && value < 1e12) return +(value / 1e9).toFixed(fixed) + "B";
 | 
			
		||||
  if (value >= 1e12) return +(value / 1e12).toFixed(fixed) + "T";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const pubkeyToString = (key: PublicKey | string = "") => {
 | 
			
		||||
  return typeof key === "string" ? key : key?.toBase58() || "";
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getLast = (arr: string[]) => {
 | 
			
		||||
  if (arr.length <= 0) {
 | 
			
		||||
    return undefined;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return arr[arr.length - 1];
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user