123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258 |
- import { Commitment, Connection, ConnectionConfig, PublicKey } from "@solana/web3.js";
- // local imports for the ReadApi types
- import type {
- GetAssetProofRpcInput,
- GetAssetProofRpcResponse,
- GetAssetRpcInput,
- GetAssetsByGroupRpcInput,
- GetAssetsByOwnerRpcInput,
- ReadApiAsset,
- ReadApiAssetList,
- } from "@/ReadApi/types";
- import type { Metadata, Mint, NftOriginalEdition, SplTokenCurrency } from "@metaplex-foundation/js";
- // import from the `@metaplex-foundation/js`
- import { MetaplexError, Pda, amount, toBigNumber } from "@metaplex-foundation/js";
- import BN from "bn.js";
- import { PROGRAM_ID as BUBBLEGUM_PROGRAM_ID } from "@metaplex-foundation/mpl-bubblegum";
- import { TokenStandard } from "@metaplex-foundation/mpl-token-metadata";
- type JsonRpcParams<ReadApiMethodParams> = {
- method: string;
- id?: string;
- params: ReadApiMethodParams;
- };
- type JsonRpcOutput<ReadApiJsonOutput> = {
- result: ReadApiJsonOutput;
- };
- /** @group Errors */
- export class ReadApiError extends MetaplexError {
- readonly name: string = "ReadApiError";
- constructor(message: string, cause?: Error) {
- super(message, "rpc", undefined, cause);
- }
- }
- /**
- * Convert a ReadApi asset (e.g. compressed NFT) into an NftEdition
- */
- export const toNftEditionFromReadApiAsset = (input: ReadApiAsset): NftOriginalEdition => {
- return {
- model: "nftEdition",
- isOriginal: true,
- address: new PublicKey(input.id),
- supply: toBigNumber(input.supply.print_current_supply),
- maxSupply: toBigNumber(input.supply.print_max_supply),
- };
- };
- /**
- * Convert a ReadApi asset (e.g. compressed NFT) into an NFT mint
- */
- export const toMintFromReadApiAsset = (input: ReadApiAsset): Mint => {
- const currency: SplTokenCurrency = {
- symbol: "Token",
- decimals: 0,
- namespace: "spl-token",
- };
- return {
- model: "mint",
- address: new PublicKey(input.id),
- mintAuthorityAddress: new PublicKey(input.id),
- freezeAuthorityAddress: new PublicKey(input.id),
- decimals: 0,
- supply: amount(1, currency),
- isWrappedSol: false,
- currency,
- };
- };
- /**
- * Convert a ReadApi asset's data into standard Metaplex `Metadata`
- */
- export const toMetadataFromReadApiAsset = (input: ReadApiAsset): Metadata => {
- const updateAuthority = input.authorities?.find(authority => authority.scopes.includes("full"));
- const collection = input.grouping.find(({ group_key }) => group_key === "collection");
- return {
- model: "metadata",
- /**
- * We technically don't have a metadata address anymore.
- * So we are using the asset's id as the address
- */
- address: Pda.find(BUBBLEGUM_PROGRAM_ID, [
- Buffer.from("asset", "utf-8"),
- new PublicKey(input.compression.tree).toBuffer(),
- Uint8Array.from(new BN(input.compression.leaf_id).toArray("le", 8)),
- ]),
- mintAddress: new PublicKey(input.id),
- updateAuthorityAddress: new PublicKey(updateAuthority!.address),
- name: input.content.metadata?.name ?? "",
- symbol: input.content.metadata?.symbol ?? "",
- json: input.content.metadata,
- jsonLoaded: true,
- uri: input.content.json_uri,
- isMutable: input.mutable,
- primarySaleHappened: input.royalty.primary_sale_happened,
- sellerFeeBasisPoints: input.royalty.basis_points,
- creators: input.creators,
- editionNonce: input.supply.edition_nonce,
- tokenStandard: TokenStandard.NonFungible,
- collection: collection
- ? { address: new PublicKey(collection.group_value), verified: false }
- : null,
- // Current regular `Metadata` does not currently have a `compression` value
- // @ts-ignore
- compression: input.compression,
- // Read API doesn't return this info, yet
- collectionDetails: null,
- // Read API doesn't return this info, yet
- uses: null,
- // Read API doesn't return this info, yet
- programmableConfig: null,
- };
- };
- /**
- * Wrapper class to add additional methods on top the standard Connection from `@solana/web3.js`
- * Specifically, adding the RPC methods used by the Digital Asset Standards (DAS) ReadApi
- * for state compression and compressed NFTs
- */
- export class WrapperConnection extends Connection {
- constructor(endpoint: string, commitmentOrConfig?: Commitment | ConnectionConfig) {
- super(endpoint, commitmentOrConfig);
- }
- private callReadApi = async <ReadApiMethodParams, ReadApiJsonOutput>(
- jsonRpcParams: JsonRpcParams<ReadApiMethodParams>,
- ): Promise<JsonRpcOutput<ReadApiJsonOutput>> => {
- const response = await fetch(this.rpcEndpoint, {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- jsonrpc: "2.0",
- method: jsonRpcParams.method,
- id: jsonRpcParams.id ?? "rpd-op-123",
- params: jsonRpcParams.params,
- }),
- });
- return await response.json() as JsonRpcOutput<ReadApiJsonOutput>;
- };
- // Asset id can be calculated via Bubblegum#getLeafAssetId
- // It is a PDA with the following seeds: ["asset", tree, leafIndex]
- async getAsset(assetId: PublicKey): Promise<ReadApiAsset> {
- const { result: asset } = await this.callReadApi<GetAssetRpcInput, ReadApiAsset>({
- method: "getAsset",
- params: {
- id: assetId.toBase58(),
- },
- });
- if (!asset) throw new ReadApiError("No asset returned");
- return asset;
- }
- // Asset id can be calculated via Bubblegum#getLeafAssetId
- // It is a PDA with the following seeds: ["asset", tree, leafIndex]
- async getAssetProof(assetId: PublicKey): Promise<GetAssetProofRpcResponse> {
- const { result: proof } = await this.callReadApi<
- GetAssetProofRpcInput,
- GetAssetProofRpcResponse
- >({
- method: "getAssetProof",
- params: {
- id: assetId.toBase58(),
- },
- });
- if (!proof) throw new ReadApiError("No asset proof returned");
- return proof;
- }
- //
- async getAssetsByGroup({
- groupKey,
- groupValue,
- page,
- limit,
- sortBy,
- before,
- after,
- }: GetAssetsByGroupRpcInput): Promise<ReadApiAssetList> {
- // `page` cannot be supplied with `before` or `after`
- if (typeof page == "number" && (before || after))
- throw new ReadApiError(
- "Pagination Error. Only one pagination parameter supported per query.",
- );
- // a pagination method MUST be selected, but we are defaulting to using `page=0`
- const { result } = await this.callReadApi<GetAssetsByGroupRpcInput, ReadApiAssetList>({
- method: "getAssetsByGroup",
- params: {
- groupKey,
- groupValue,
- after: after ?? null,
- before: before ?? null,
- limit: limit ?? null,
- page: page ?? 1,
- sortBy: sortBy ?? null,
- },
- });
- if (!result) throw new ReadApiError("No results returned");
- return result;
- }
- //
- async getAssetsByOwner({
- ownerAddress,
- page,
- limit,
- sortBy,
- before,
- after,
- }: GetAssetsByOwnerRpcInput): Promise<ReadApiAssetList> {
- // `page` cannot be supplied with `before` or `after`
- if (typeof page == "number" && (before || after))
- throw new ReadApiError(
- "Pagination Error. Only one pagination parameter supported per query.",
- );
- // a pagination method MUST be selected, but we are defaulting to using `page=0`
- const { result } = await this.callReadApi<GetAssetsByOwnerRpcInput, ReadApiAssetList>({
- method: "getAssetsByOwner",
- params: {
- ownerAddress,
- after: after ?? null,
- before: before ?? null,
- limit: limit ?? null,
- page: page ?? 1,
- sortBy: sortBy ?? null,
- },
- });
- if (!result) throw new ReadApiError("No results returned");
- return result;
- }
- }
|