123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228 |
- // 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 { type Commitment, Connection, type ConnectionConfig, PublicKey } from '@solana/web3.js';
- import { PROGRAM_ID as BUBBLEGUM_PROGRAM_ID } from '@metaplex-foundation/mpl-bubblegum';
- import { TokenStandard } from '@metaplex-foundation/mpl-token-metadata';
- import BN from 'bn.js';
- 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 {
- 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;
- }
- }
|