solana/explorer/src/components/account/nftoken/nftoken.ts

144 lines
3.6 KiB
TypeScript

import axios from "axios";
import pLimit from "p-limit";
import { Connection, PublicKey } from "@solana/web3.js";
import bs58 from "bs58";
import { NftokenTypes } from "./nftoken-types";
export const NFTOKEN_ADDRESS = "nftokf9qcHSYkVSP3P2gUMmV6d4AwjMueXgUu43HyLL";
const nftokenAccountDiscInHex = "21b45b35ec0f3f61";
export namespace NftokenFetcher {
export const getNftsInCollection = async ({
collection,
rpcUrl,
}: {
collection: string;
rpcUrl: string;
}): Promise<NftokenTypes.NftInfo[]> => {
const connection = new Connection(rpcUrl);
const accounts = await connection.getProgramAccounts(
new PublicKey(NFTOKEN_ADDRESS),
{
filters: [
{
memcmp: {
offset: 0,
bytes: bs58.encode(Buffer.from(nftokenAccountDiscInHex, "hex")),
},
},
{
memcmp: {
offset:
8 + // discriminator
1 + // version
32 + // holder
32 + // authority
1, // authority_can_update
bytes: collection,
},
},
],
}
);
const parsed_accounts: NftokenTypes.NftAccount[] = accounts.flatMap(
(account) => {
const parsed = NftokenTypes.nftAccountLayout.decode(
account.account.data
);
if (!parsed) {
return [];
}
return {
address: account.pubkey.toBase58(),
holder: parsed.holder,
authority: parsed.authority,
authority_can_update: Boolean(parsed.authority_can_update),
collection: parsed.collection,
delegate: parsed.delegate,
metadata_url: parsed.metadata_url,
};
}
);
const metadata_urls = parsed_accounts.map((a) => a.metadata_url);
const metadataMap = await getMetadataMap({ urls: metadata_urls });
const nfts = parsed_accounts.map((account) => ({
...account,
...metadataMap.get(account.metadata_url),
}));
nfts.sort();
return nfts.sort((a, b) => {
if (a.name && b.name) {
return a.name < b.name ? -1 : 1;
}
if (a.name) {
return 1;
}
if (b.name) {
return -1;
}
return a.address < b.address ? 1 : -1;
});
};
export const getMetadata = async ({
url,
}: {
url: string | null | undefined;
}): Promise<NftokenTypes.Metadata | null> => {
if (!url) {
return null;
}
const metadataMap = await getMetadataMap({
urls: [url],
});
return metadataMap.get(url) ?? null;
};
export const getMetadataMap = async ({
urls: _urls,
}: {
urls: Array<string | null | undefined>;
}): Promise<Map<string, NftokenTypes.Metadata | null>> => {
const urls = Array.from(
new Set(_urls.filter((url): url is string => Boolean(url)))
);
const metadataMap = new Map<string, NftokenTypes.Metadata | null>();
const limit = pLimit(5);
const promises = urls.map((url) =>
limit(async () => {
try {
const { data } = await axios.get(url, {
timeout: 5_000,
});
metadataMap.set(url, {
name: data.name ?? "",
description: data.description ?? null,
image: data.image ?? "",
traits: data.traits ?? [],
animation_url: data.animation_url ?? null,
external_url: data.external_url ?? null,
});
} catch {
metadataMap.set(url, null);
}
})
);
await Promise.all(promises);
return metadataMap;
};
}