diff --git a/explorer/src/components/account/TokenAccountSection.tsx b/explorer/src/components/account/TokenAccountSection.tsx
index 1decf17709..7e9d23c4c8 100644
--- a/explorer/src/components/account/TokenAccountSection.tsx
+++ b/explorer/src/components/account/TokenAccountSection.tsx
@@ -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({
- {nftData?.editionData?.masterEdition?.maxSupply && (
+ {nftData.editionInfo.masterEdition?.maxSupply && (
Max Total Supply |
- {nftData.editionData.masterEdition.maxSupply.toNumber() === 0
+ {nftData.editionInfo.masterEdition.maxSupply.toNumber() === 0
? 1
- : nftData.editionData.masterEdition.maxSupply.toNumber()}
+ : nftData.editionInfo.masterEdition.maxSupply.toNumber()}
|
)}
- {nftData?.editionData?.masterEdition?.supply && (
+ {nftData?.editionInfo.masterEdition?.supply && (
Current Supply |
- {nftData.editionData.masterEdition.supply.toNumber() === 0
+ {nftData.editionInfo.masterEdition.supply.toNumber() === 0
? 1
- : nftData.editionData.masterEdition.supply.toNumber()}
+ : nftData.editionInfo.masterEdition.supply.toNumber()}
|
)}
@@ -346,7 +346,7 @@ function NonFungibleTokenMintAccountCard({
Update Authority |
diff --git a/explorer/src/metaplex/Art/Art.tsx b/explorer/src/components/common/NFTArt.tsx
similarity index 65%
rename from explorer/src/metaplex/Art/Art.tsx
rename to explorer/src/components/common/NFTArt.tsx
index ccc6e2a0a1..1f200f0750 100644
--- a/explorer/src/metaplex/Art/Art.tsx
+++ b/explorer/src/components/common/NFTArt.tsx
@@ -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(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 = ({
);
};
+
+enum ArtFetchStatus {
+ ReadyToFetch,
+ Fetching,
+ FetchFailed,
+ FetchSucceeded,
+}
+
+const cachedImages = new Map();
+export const useCachedImage = (uri: string) => {
+ const [cachedBlob, setCachedBlob] = useState(undefined);
+ const [fetchStatus, setFetchStatus] = useState(
+ 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();
+
+ 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 };
+};
diff --git a/explorer/src/metaplex/Art/useArt.ts b/explorer/src/metaplex/Art/useArt.ts
deleted file mode 100644
index 72367796b0..0000000000
--- a/explorer/src/metaplex/Art/useArt.ts
+++ /dev/null
@@ -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();
-export const useCachedImage = (uri: string) => {
- const [cachedBlob, setCachedBlob] = useState(undefined);
- const [fetchStatus, setFetchStatus] = useState(
- 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();
-
- 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 };
-};
diff --git a/explorer/src/metaplex/classes.ts b/explorer/src/metaplex/classes.ts
deleted file mode 100644
index f5f110d33a..0000000000
--- a/explorer/src/metaplex/classes.ts
+++ /dev/null
@@ -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([
- [
- 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;
- }
-}
diff --git a/explorer/src/metaplex/ids.ts b/explorer/src/metaplex/ids.ts
deleted file mode 100644
index 705158f34d..0000000000
--- a/explorer/src/metaplex/ids.ts
+++ /dev/null
@@ -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 {
- 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();
-
-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 {
- pubkey: string;
- account: AccountInfo;
-}
-
-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");
diff --git a/explorer/src/metaplex/types.ts b/explorer/src/metaplex/types.ts
deleted file mode 100644
index 00dd2e7b5a..0000000000
--- a/explorer/src/metaplex/types.ts
+++ /dev/null
@@ -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,
-}
diff --git a/explorer/src/metaplex/utils.ts b/explorer/src/metaplex/utils.ts
deleted file mode 100644
index 76454cad7d..0000000000
--- a/explorer/src/metaplex/utils.ts
+++ /dev/null
@@ -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 = (arr: T[]) => {
- if (arr.length <= 0) {
- return undefined;
- }
-
- return arr[arr.length - 1];
-};
diff --git a/explorer/src/providers/accounts/index.tsx b/explorer/src/providers/accounts/index.tsx
index de8da26e6d..4a8b47ac4c 100644
--- a/explorer/src/providers/accounts/index.tsx
+++ b/explorer/src/providers/accounts/index.tsx
@@ -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 = {
diff --git a/explorer/src/providers/accounts/utils/getEditionInfo.ts b/explorer/src/providers/accounts/utils/getEditionInfo.ts
new file mode 100644
index 0000000000..c671d32318
--- /dev/null
+++ b/explorer/src/providers/accounts/utils/getEditionInfo.ts
@@ -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 {
+ 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,
+ };
+}
diff --git a/explorer/src/providers/accounts/utils/metadataHelpers.ts b/explorer/src/providers/accounts/utils/metadataHelpers.ts
deleted file mode 100644
index e8c22b8c33..0000000000
--- a/explorer/src/providers/accounts/utils/metadataHelpers.ts
+++ /dev/null
@@ -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 {
- 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 {
- 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 {
- 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) =>
- account.owner.toBase58() === METADATA_PROGRAM_ID;
-
-const isMetadataV1Account = (account: AccountInfo) =>
- account.data[0] === MetadataKey.MetadataV1;
-
-const isEditionV1Account = (account: AccountInfo) =>
- account.data[0] === MetadataKey.EditionV1;
-
-const isMasterEditionAccount = (account: AccountInfo) =>
- 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();
diff --git a/explorer/src/utils/index.tsx b/explorer/src/utils/index.tsx
index 95fa3943c0..630c880998 100644
--- a/explorer/src/utils/index.tsx
+++ b/explorer/src/utils/index.tsx
@@ -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];
+};
|