From dece98cd0e4ef79d3e62bf759a34964ab7119832 Mon Sep 17 00:00:00 2001 From: KidSysco Date: Tue, 17 Dec 2024 22:41:55 -0600 Subject: [PATCH 1/8] add getMetaDataCollection --- src/composables/useEvmNft.js | 46 ++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/composables/useEvmNft.js b/src/composables/useEvmNft.js index b781e4f..147a380 100644 --- a/src/composables/useEvmNft.js +++ b/src/composables/useEvmNft.js @@ -382,11 +382,57 @@ export async function useEvmNft( } } + /** + * Retrieves and paginates NFT metadata based on the contract's token balance and configuration. This method optimizes performance by avoiding direct blockchain queries for token IDs, instead calculating them directly. If a chain ID is specified, this function will be extra fast because it can use Dig-A-Hash predictable meta data. + * + * @param {number} page - The page number for pagination. Defaults to 1 if not provided. + * @param {boolean} isAscending - If true, sorts tokens in ascending order by token ID; if false, descending order. + * @returns {Promise} An object containing: {Array} tokens - An array of objects where each object includes token metadata, and token ID. + * - {number} pageSize - The size of each page (number of items per page). + * - {number} count - The total number of tokens or NFTs for the specified contract. + * @throws {Error} If the contract instance has not been initialized. + */ + async function getMetaDataCollection(page, isAscending) { + _contractRequired(); + loadingMessage.value = 'Connecting to Blockchain...'; + + const startTokenId = await _getStartTokenId(); + const balance = await _getBalance(holderPublicKey); + const { startIndex, endIndex, lastPage } = _calculatePageIndexes( + page, + balance, + pageSize, + isAscending, + startTokenId + ); + + const tokenIds = Array.from( + // Create an array with 'balance' items + { length: balance }, + // For each item, give it a value starting from 'startTokenId' + (_, index) => startTokenId + index + ) + // Only keep the items from 'startIndex' to 'endIndex' + .slice(startIndex, endIndex); + + const tokens = await getTokenMetaData(tokenIds); + + // Ensure we sort + if (isAscending) { + tokens.sort((a, b) => a.tokenId - b.tokenId); + } else { + tokens.sort((a, b) => b.tokenId - a.tokenId); + } + + return { tokens, pageSize, count: balance }; + } + return { getNfts, getTokenOwner, getMetaDataBatch, getTokenMetaData, loadingMessage, + getMetaDataCollection, }; } From 03c081db25db176b8ceaea95fc115e0de01091ca Mon Sep 17 00:00:00 2001 From: KidSysco Date: Wed, 18 Dec 2024 22:47:43 -0600 Subject: [PATCH 2/8] add useEvnMetaDataCollection with better performance --- package.json | 2 +- src/composables/useEvmMetaDataGallery.js | 174 +++++++++++++++++++++++ src/composables/useEvmNft.js | 10 +- src/index.js | 1 + src/types/index.d.ts | 1 + src/types/useEvmMetaDataGallery.d.ts | 26 ++++ src/types/useNftStore.d.ts | 8 +- 7 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 src/composables/useEvmMetaDataGallery.js create mode 100644 src/types/useEvmMetaDataGallery.d.ts diff --git a/package.json b/package.json index 2ae1156..0e57388 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-evm-nft", - "version": "1.1.3", + "version": "1.1.4", "main": "src/index.js", "types": "src/types/index.d.ts", "scripts": { diff --git a/src/composables/useEvmMetaDataGallery.js b/src/composables/useEvmMetaDataGallery.js new file mode 100644 index 0000000..9a8e760 --- /dev/null +++ b/src/composables/useEvmMetaDataGallery.js @@ -0,0 +1,174 @@ +import { ref, onMounted, watch } from 'vue'; +import { useEvmNft } from './useEvmNft'; +import { useNftStore } from '../stores/nftStore'; +import { ethers } from 'ethers'; + +/** + * Initializes the NFT Gallery composable exposing several variables and + * functions needed to sort and page through EVM based NFT Contracts. This + * component is dependant on the useEvmNft composable, and Pinia nftStore. + * @param {string} contractPublicKey - The public key of the wallet holding the contract. + * @param {string} contractAddress - The contract address. + * @param {array} abi - The contract ABI. + * @param {number} chainId - The EVM Chain ID, pass null to use Dig-A-Hash + * meta-data for improved Meta Data fetching performance. + * @param {string} holderPublicKey - Gets NFTs on contract held by this wallet only. + * If null, all NFTs on contract will return. + * @param {string} ethersProviderUrl - The Ethers provider for the Chain ID. + * @param {number} itemsPerPage - The number of items to get per page. + * @param {string} nftStoreItemCollectionName - The NFT Store collection name. + * @param {boolean} isAscendingSort - Sorting by Token ID direction. + * @returns page, numberOfPages, nfts, isAscending, toggleSortOrder, + * isLoading loadingMessage, getNftPage, getTokenOwner, getTokenMetaData, getMetaDataBatch + */ +export function useEvmMetaDataGallery( + contractPublicKey, + contractAddress, + abi, + chainId, + holderPublicKey, + ethersProviderUrl, + itemsPerPage, + nftStoreItemCollectionName, + isAscendingSort +) { + const nftStore = useNftStore(); + + const page = ref(1); + const numberOfPages = ref(0); + const nfts = ref([]); + const isAscending = ref(isAscendingSort); + const isLoading = ref(false); + const loadingMessage = ref(''); + + // Proxy functions from useEvmNft. + let _getMyNfts = null; + let _getTokenOwner = null; + let _getTokenMetaData = null; + let _getMetaDataBatch = null; + + onMounted(async () => { + nftStore.addCollection(nftStoreItemCollectionName); + + const evmNft = await useEvmNft( + parseInt(itemsPerPage), + new ethers.JsonRpcProvider(ethersProviderUrl), + holderPublicKey, + contractPublicKey, + contractAddress, + abi, + chainId + ); + + loadingMessage.value = evmNft.loadingMessage; // bind ref to loadingMessage + + // Set the function pointer for calling later, after mount. + _getMyNfts = evmNft.getMetaDataCollection; + _getTokenOwner = evmNft.getTokenOwner; + _getTokenMetaData = evmNft.getTokenMetaData; + _getMetaDataBatch = evmNft.getMetaDataBatch; + + await getNftPage(page.value); + }); + + // Get NFTs if page changes. + watch(page, async (newPage, oldPage) => { + if (newPage !== oldPage) { + await getNftPage(newPage); + } + }); + + /** + * Toggles the sort order of the NFTs between ascending and descending. + * It also clears the current collection and resets pagination to the first page. + * @returns {Promise} - A promise for a page of NFTs + */ + async function toggleSortOrder() { + isAscending.value = !isAscending.value; + nftStore.itemCollections[nftStoreItemCollectionName].items = []; + nftStore.itemCollections[nftStoreItemCollectionName].page = 1; + page.value = 1; + await getNftPage(page.value); + } + + /** + * Fetches a specific page of NFTs and associated metadata. + * This function updates the local state with NFTs and their pagination details. + * @param {number} iPage - The page number to retrieve. + * @returns - A promise that resolves once the NFTs are fetched. + */ + async function getNftPage(iPage) { + try { + isLoading.value = true; + // Skip fetching NFTs if we already have them. + if ( + nftStore.itemCollections[nftStoreItemCollectionName].items[iPage - 1] + ) { + nfts.value = + nftStore.itemCollections[nftStoreItemCollectionName].items[iPage - 1]; + numberOfPages.value = + nftStore.itemCollections[nftStoreItemCollectionName].page; + return; + } + + const { tokens, pageSize, count } = await _getMyNfts( + iPage, + isAscending.value + ); + nfts.value = tokens; + nftStore.setCollectionItems(iPage, tokens, nftStoreItemCollectionName); + nftStore.itemCollections[nftStoreItemCollectionName].page = Math.ceil( + count / pageSize + ); + numberOfPages.value = + nftStore.itemCollections[nftStoreItemCollectionName].page; + nftStore.itemCollections[nftStoreItemCollectionName].itemCount = count; + } catch (error) { + console.error('Error in getNftPage:', error); + throw error; + } finally { + isLoading.value = false; + } + } + + /** + * Fetches the owner of a specific token by its ID. Exposing a proxy function for evmNft. + * @param {number} tokenId - The ID of the token to look up the owner for. + * @returns {Promise} - A promise that resolves with the owner’s address. + */ + async function getTokenOwner(tokenId) { + return await _getTokenOwner(tokenId); + } + + /** + * Retrieves metadata for a given set of token IDs. Exposing a proxy function for evmNft. + * @param {array} tokenIds - An array of token IDs to retrieve metadata for. + * @returns {Promise} - A promise that resolves with the metadata for the tokens. + */ + async function getTokenMetaData(tokenIds) { + return await _getTokenMetaData(tokenIds); + } + + /** + * Retrieves metadata for a batch of tokens. Exposing a proxy function for evmNft. + * @param {array} batchObjects - Array of batch objects, each containing details for multiple tokens. + * @returns {Promise} - A promise that resolves with the metadata for the batch of tokens. + */ + async function getMetaDataBatch(batchObjects) { + return await _getMetaDataBatch(batchObjects); + } + + return { + page, + numberOfPages, + nfts, + isAscending, + isLoading, + loadingMessage, + toggleSortOrder, + getNftPage, + getTokenOwner, + getTokenMetaData, + getMetaDataBatch, + }; +} diff --git a/src/composables/useEvmNft.js b/src/composables/useEvmNft.js index 147a380..fbe3dac 100644 --- a/src/composables/useEvmNft.js +++ b/src/composables/useEvmNft.js @@ -110,13 +110,7 @@ export async function useEvmNft( * - `endIndex` (number): The index at which to end retrieval (inclusive). * - `lastPage` (number): The total number of pages based on the balance and page size. */ - function _calculatePageIndexes( - page, - balance, - pageSize, - isAscending, - startTokenId - ) { + function _calculatePageIndexes(page, balance, isAscending, startTokenId) { const lastPage = Math.ceil(balance / pageSize); page = page || 1; @@ -242,7 +236,6 @@ export async function useEvmNft( const { startIndex, endIndex, lastPage } = _calculatePageIndexes( page, balance, - pageSize, isAscending, startTokenId ); @@ -401,7 +394,6 @@ export async function useEvmNft( const { startIndex, endIndex, lastPage } = _calculatePageIndexes( page, balance, - pageSize, isAscending, startTokenId ); diff --git a/src/index.js b/src/index.js index ccbedbc..cacb0e5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,7 @@ export * from './stores/nftStore.js'; export * from './composables/useEvmNft.js'; export * from './composables/useEvmNftGallery.js'; +export * from './composables/useEvmMetaDataGallery.js'; export * from './modules/blockchains.js'; export * from './modules/dahDemoV1Abi.js'; export * from './modules/dahNftV2Abi.js'; diff --git a/src/types/index.d.ts b/src/types/index.d.ts index b275ca1..e9a0c57 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -1,5 +1,6 @@ export * from './useEvmNft'; export * from './useEvmNftGallery'; +export * from './useEvmMetaDataGallery'; export * from './useNftStore'; export * from './blockchains'; diff --git a/src/types/useEvmMetaDataGallery.d.ts b/src/types/useEvmMetaDataGallery.d.ts new file mode 100644 index 0000000..72df890 --- /dev/null +++ b/src/types/useEvmMetaDataGallery.d.ts @@ -0,0 +1,26 @@ +import { type ref, Ref } from 'vue'; +import { NftMetaData } from './useNftStore'; + +export declare function useEvmMetaDataGallery( + contractPublicKey: string, + contractAddress: string, + abi: any[], + chainId: number | null, + holderPublicKey: string | null, + ethersProviderUrl: string, + itemsPerPage: number, + nftStoreItemCollectionName: string, + isAscendingSort: boolean +): { + page: ref; + numberOfPages: ref; + nfts: ref; + isAscending: ref; + isLoading: ref; + loadingMessage: ref; + toggleSortOrder: () => Promise; + getNftPage: (iPage: number) => Promise; + getTokenOwner: (tokenId: number) => Promise; + getTokenMetaData: (tokenIds: number[]) => Promise; + getMetaDataBatch: (batchObjects: any[]) => Promise; +}; diff --git a/src/types/useNftStore.d.ts b/src/types/useNftStore.d.ts index 1433251..d081bfa 100644 --- a/src/types/useNftStore.d.ts +++ b/src/types/useNftStore.d.ts @@ -1,13 +1,15 @@ import { StoreDefinition } from 'pinia'; interface MetaDataAttribute { - trait_type?: string; - value?: string; + trait_type: string; + value: string; } export interface NftMetaData { - image?: string; + image: string; name: string; + tokenId?: number; + description: string; attributes?: MetaDataAttribute[]; } From 128fd6fa1d4f8907741b15948d49fcceabcfc92c Mon Sep 17 00:00:00 2001 From: KidSysco Date: Fri, 20 Dec 2024 00:21:49 -0600 Subject: [PATCH 3/8] Optimize performance --- src/composables/useEvmNft.js | 87 ++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 44 deletions(-) diff --git a/src/composables/useEvmNft.js b/src/composables/useEvmNft.js index fbe3dac..7758a28 100644 --- a/src/composables/useEvmNft.js +++ b/src/composables/useEvmNft.js @@ -34,6 +34,8 @@ export async function useEvmNft( chainId ) { let contract = null; + const _balance = ref(-1); + const _startTokenId = ref(-1); // Only initialize the contract if both contractAddress and contractABI are provided if (contractAddress && contractABI && provider) { @@ -51,27 +53,33 @@ export async function useEvmNft( */ function _contractRequired() { if (!contract) { - throw new Error('Contract is required for getStartTokenId.'); + throw new Error('Contract is required for useEvmNft.'); } } /** - * Gets the starting Token ID on the contract. + * Sets the starting Token ID on the contract. * This is used to tell if the first Token ID is 0 or 1. * @private * @returns Either a 0 or a 1. */ - async function _getStartTokenId() { + async function _setStartTokenId() { try { + if (_startTokenId.value > -1) { + return; + } + await contract.ownerOf(0); - return 0; + _startTokenId.value = 0; + // return 0; } catch { - return 1; + _startTokenId.value = 1; + // return 1; } } /** - * Retrieves the balance of NFTs for a given holder or the total supply. + * Sets the balance of NFTs for a given holder or the total supply. * If a holder's public key is provided, it fetches the balance of NFTs * owned by that specific holder. If no public key is provided, it * returns the total supply of NFTs. @@ -81,14 +89,20 @@ export async function useEvmNft( * @returns {Promise} - A Promise that resolves to the balance of * NFTs owned by the holder or the total supply as a number. */ - async function _getBalance(holderPublicKey) { - if (holderPublicKey) { - holderPublicKey = holderPublicKey.toLowerCase(); - const balance = await contract.balanceOf(holderPublicKey); - return Number(balance); + async function _setBalance(holderPublicKey) { + if (_balance.value > -1) { + return; } else { - const balance = await contract.totalSupply(); - return Number(balance); + if (holderPublicKey) { + holderPublicKey = holderPublicKey.toLowerCase(); + const balance = await contract.balanceOf(holderPublicKey); + // return Number(balance); + _balance.value = Number(balance); + } else { + const balance = await contract.totalSupply(); + _balance.value = Number(balance); + // return Number(balance); + } } } @@ -103,14 +117,12 @@ export async function useEvmNft( * @param {boolean} isAscending - Determines the order of retrieval: * - `true`: Retrieves items in ascending order. * - `false`: Retrieves items in descending order. - * @param {number} startTokenId - The starting token ID of the contract, - * which could be 0 or 1 depending on the contract. * @returns {Object} - An object containing: * - `startIndex` (number): The index at which to start retrieving tokens. * - `endIndex` (number): The index at which to end retrieval (inclusive). * - `lastPage` (number): The total number of pages based on the balance and page size. */ - function _calculatePageIndexes(page, balance, isAscending, startTokenId) { + function _calculatePageIndexes(page, balance, isAscending) { const lastPage = Math.ceil(balance / pageSize); page = page || 1; @@ -123,7 +135,7 @@ export async function useEvmNft( endIndex = balance - pageSize * (page - 1); } - if (startTokenId === 0) { + if (_startTokenId.value === 0) { endIndex--; } @@ -186,23 +198,16 @@ export async function useEvmNft( * @param {number} startIndex - The starting index for fetching tokens (inclusive). * @param {number} endIndex - The ending index for fetching tokens (inclusive). * @param {string} holderPublicKey - The public key (address) of the NFT holder. - * @param {number} startTokenId - The starting token ID of the contract, which - * could be 0 or 1 depending on the contract. * @returns {Promise} - A Promise that resolves to an array of objects, * each containing: * - `tokenId` (number): The ID of the fetched token. * - `owner` (string): The holder's public key. */ - async function _fetchUserTokens( - startIndex, - endIndex, - holderPublicKey, - startTokenId - ) { + async function _fetchUserTokens(startIndex, endIndex, holderPublicKey) { const batchedTokenIdPromises = []; // Adjust indexes for fetching user's tokens - for (let i = endIndex - startTokenId; i >= startIndex; i--) { + for (let i = endIndex - _startTokenId.value; i >= startIndex; i--) { batchedTokenIdPromises.push( contract .tokenOfOwnerByIndex(holderPublicKey, i) @@ -231,28 +236,23 @@ export async function useEvmNft( _contractRequired(); loadingMessage.value = 'Connecting to Blockchain...'; - const startTokenId = await _getStartTokenId(); - const balance = await _getBalance(holderPublicKey); + await _setStartTokenId(); + await _setBalance(holderPublicKey); const { startIndex, endIndex, lastPage } = _calculatePageIndexes( page, - balance, + _balance.value, isAscending, startTokenId ); // Fetch tokens based on whether a specific wallet is provided or not const batchedTokenIds = holderPublicKey - ? await _fetchUserTokens( - startIndex, - endIndex, - holderPublicKey, - startTokenId - ) + ? await _fetchUserTokens(startIndex, endIndex, holderPublicKey) : await _fetchAllTokens(startIndex, endIndex, startTokenId); const tokens = await getMetaDataBatch(batchedTokenIds, isAscending); - return { tokens, pageSize, count: balance }; + return { tokens, pageSize, count: _balance.value }; } /** @@ -389,20 +389,19 @@ export async function useEvmNft( _contractRequired(); loadingMessage.value = 'Connecting to Blockchain...'; - const startTokenId = await _getStartTokenId(); - const balance = await _getBalance(holderPublicKey); + await _setStartTokenId(); + await _setBalance(holderPublicKey); const { startIndex, endIndex, lastPage } = _calculatePageIndexes( page, - balance, - isAscending, - startTokenId + _balance.value, + isAscending ); const tokenIds = Array.from( // Create an array with 'balance' items - { length: balance }, + { length: _balance.value }, // For each item, give it a value starting from 'startTokenId' - (_, index) => startTokenId + index + (_, index) => _startTokenId.value + index ) // Only keep the items from 'startIndex' to 'endIndex' .slice(startIndex, endIndex); @@ -416,7 +415,7 @@ export async function useEvmNft( tokens.sort((a, b) => b.tokenId - a.tokenId); } - return { tokens, pageSize, count: balance }; + return { tokens, pageSize, count: _balance.value }; } return { From 0671c69e5060c91948d2da99edd463992148723c Mon Sep 17 00:00:00 2001 From: KidSysco Date: Fri, 20 Dec 2024 00:22:13 -0600 Subject: [PATCH 4/8] add new param for performance --- src/composables/useEvmMetaDataGallery.js | 43 +++++++++++++++++++++--- src/types/useEvmMetaDataGallery.d.ts | 3 +- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/src/composables/useEvmMetaDataGallery.js b/src/composables/useEvmMetaDataGallery.js index 9a8e760..a04974c 100644 --- a/src/composables/useEvmMetaDataGallery.js +++ b/src/composables/useEvmMetaDataGallery.js @@ -18,6 +18,7 @@ import { ethers } from 'ethers'; * @param {number} itemsPerPage - The number of items to get per page. * @param {string} nftStoreItemCollectionName - The NFT Store collection name. * @param {boolean} isAscendingSort - Sorting by Token ID direction. + * @param {boolean} isGetAllNftQuery - If true, fetches all NFTs in one query. * @returns page, numberOfPages, nfts, isAscending, toggleSortOrder, * isLoading loadingMessage, getNftPage, getTokenOwner, getTokenMetaData, getMetaDataBatch */ @@ -30,7 +31,8 @@ export function useEvmMetaDataGallery( ethersProviderUrl, itemsPerPage, nftStoreItemCollectionName, - isAscendingSort + isAscendingSort, + isGetAllNftQuery ) { const nftStore = useNftStore(); @@ -68,7 +70,11 @@ export function useEvmMetaDataGallery( _getTokenMetaData = evmNft.getTokenMetaData; _getMetaDataBatch = evmNft.getMetaDataBatch; - await getNftPage(page.value); + if (isGetAllNftQuery) { + await getAllNfts(); + } else { + await getNftPage(page.value); + } }); // Get NFTs if page changes. @@ -115,8 +121,18 @@ export function useEvmMetaDataGallery( iPage, isAscending.value ); - nfts.value = tokens; - nftStore.setCollectionItems(iPage, tokens, nftStoreItemCollectionName); + // append tokens if isGetAllNftQuery is true + if (isGetAllNftQuery) { + nfts.value = nfts.value.concat(tokens); + } else { + nfts.value = tokens; + } + + nftStore.setCollectionItems( + iPage, + nfts.value, // used to be tokens, just need the appended items + nftStoreItemCollectionName + ); nftStore.itemCollections[nftStoreItemCollectionName].page = Math.ceil( count / pageSize ); @@ -131,6 +147,25 @@ export function useEvmMetaDataGallery( } } + /** + * Fetches all NFTs and associated metadata for a given contract in a loop + * with no paging. + */ + async function getAllNfts() { + try { + isLoading.value = true; + await getNftPage(1); + for (let i = 2; i <= numberOfPages.value; i++) { + await getNftPage(i); + } + } catch (error) { + console.error('Error in getAllNfts:', error); + throw error; + } finally { + isLoading.value = false; + } + } + /** * Fetches the owner of a specific token by its ID. Exposing a proxy function for evmNft. * @param {number} tokenId - The ID of the token to look up the owner for. diff --git a/src/types/useEvmMetaDataGallery.d.ts b/src/types/useEvmMetaDataGallery.d.ts index 72df890..b63520c 100644 --- a/src/types/useEvmMetaDataGallery.d.ts +++ b/src/types/useEvmMetaDataGallery.d.ts @@ -10,7 +10,8 @@ export declare function useEvmMetaDataGallery( ethersProviderUrl: string, itemsPerPage: number, nftStoreItemCollectionName: string, - isAscendingSort: boolean + isAscendingSort: boolean, + isGetAllNftQuery: boolean ): { page: ref; numberOfPages: ref; From f475237cd1234d3505812cf5d25c37bad3a7d44c Mon Sep 17 00:00:00 2001 From: KidSysco Date: Sat, 21 Dec 2024 00:35:40 -0600 Subject: [PATCH 5/8] adding config objects --- package.json | 2 +- src/composables/useEvmMetaDataGallery.js | 64 +++++-------- src/composables/useEvmNft.js | 115 +++++++++++------------ src/composables/useEvmNftGallery.js | 65 +++++-------- src/modules/blockchains.js | 8 +- src/types/useEvmMetaDataGallery.d.ts | 40 +++++--- src/types/useEvmNftGallery.d.ts | 42 ++++++--- 7 files changed, 165 insertions(+), 171 deletions(-) diff --git a/package.json b/package.json index 0e57388..9158aab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vue-evm-nft", - "version": "1.1.4", + "version": "1.2.1", "main": "src/index.js", "types": "src/types/index.d.ts", "scripts": { diff --git a/src/composables/useEvmMetaDataGallery.js b/src/composables/useEvmMetaDataGallery.js index a04974c..6e77c16 100644 --- a/src/composables/useEvmMetaDataGallery.js +++ b/src/composables/useEvmMetaDataGallery.js @@ -4,36 +4,30 @@ import { useNftStore } from '../stores/nftStore'; import { ethers } from 'ethers'; /** - * Initializes the NFT Gallery composable exposing several variables and - * functions needed to sort and page through EVM based NFT Contracts. This - * component is dependant on the useEvmNft composable, and Pinia nftStore. - * @param {string} contractPublicKey - The public key of the wallet holding the contract. - * @param {string} contractAddress - The contract address. - * @param {array} abi - The contract ABI. - * @param {number} chainId - The EVM Chain ID, pass null to use Dig-A-Hash - * meta-data for improved Meta Data fetching performance. - * @param {string} holderPublicKey - Gets NFTs on contract held by this wallet only. - * If null, all NFTs on contract will return. - * @param {string} ethersProviderUrl - The Ethers provider for the Chain ID. - * @param {number} itemsPerPage - The number of items to get per page. - * @param {string} nftStoreItemCollectionName - The NFT Store collection name. - * @param {boolean} isAscendingSort - Sorting by Token ID direction. - * @param {boolean} isGetAllNftQuery - If true, fetches all NFTs in one query. + * Similar to the useEvmNftGallery but this composable is designed to fetch + * NFT meta data with much less on-chain validation. This allows for faster + * fetching, and larger page sizes, including the ability to fetch all NFTs + * in one query. This composable cannot fetch NFTs from a specific wallet, + * and is designed only to fetch all NFTs on a contract. + * @param {object} config - The EvmMetaDataOptions configuration object for + * the useEvmMetaDataGallery. * @returns page, numberOfPages, nfts, isAscending, toggleSortOrder, - * isLoading loadingMessage, getNftPage, getTokenOwner, getTokenMetaData, getMetaDataBatch + * isLoading loadingMessage, getNftPage, getTokenOwner, getTokenMetaData. */ -export function useEvmMetaDataGallery( - contractPublicKey, - contractAddress, - abi, - chainId, - holderPublicKey, - ethersProviderUrl, - itemsPerPage, - nftStoreItemCollectionName, - isAscendingSort, - isGetAllNftQuery -) { +export function useEvmMetaDataGallery(config) { + const { + contractPublicKey, + contractAddress, + abi, + chainId, + rpc, + itemsPerPage, + nftStoreItemCollectionName, + isAscendingSort, + isGetAllNftQuery, + } = config; + + const holderPublicKey = null; const nftStore = useNftStore(); const page = ref(1); @@ -47,14 +41,13 @@ export function useEvmMetaDataGallery( let _getMyNfts = null; let _getTokenOwner = null; let _getTokenMetaData = null; - let _getMetaDataBatch = null; onMounted(async () => { nftStore.addCollection(nftStoreItemCollectionName); const evmNft = await useEvmNft( parseInt(itemsPerPage), - new ethers.JsonRpcProvider(ethersProviderUrl), + new ethers.JsonRpcProvider(rpc), holderPublicKey, contractPublicKey, contractAddress, @@ -68,7 +61,6 @@ export function useEvmMetaDataGallery( _getMyNfts = evmNft.getMetaDataCollection; _getTokenOwner = evmNft.getTokenOwner; _getTokenMetaData = evmNft.getTokenMetaData; - _getMetaDataBatch = evmNft.getMetaDataBatch; if (isGetAllNftQuery) { await getAllNfts(); @@ -184,15 +176,6 @@ export function useEvmMetaDataGallery( return await _getTokenMetaData(tokenIds); } - /** - * Retrieves metadata for a batch of tokens. Exposing a proxy function for evmNft. - * @param {array} batchObjects - Array of batch objects, each containing details for multiple tokens. - * @returns {Promise} - A promise that resolves with the metadata for the batch of tokens. - */ - async function getMetaDataBatch(batchObjects) { - return await _getMetaDataBatch(batchObjects); - } - return { page, numberOfPages, @@ -204,6 +187,5 @@ export function useEvmMetaDataGallery( getNftPage, getTokenOwner, getTokenMetaData, - getMetaDataBatch, }; } diff --git a/src/composables/useEvmNft.js b/src/composables/useEvmNft.js index 7758a28..c634c5d 100644 --- a/src/composables/useEvmNft.js +++ b/src/composables/useEvmNft.js @@ -58,10 +58,11 @@ export async function useEvmNft( } /** - * Sets the starting Token ID on the contract. - * This is used to tell if the first Token ID is 0 or 1. + * Sets the starting Token ID on the contract. This is used to tell if + * the first Token ID is 0 or 1. This can miscalculate if the first + * token(s) is/are burned but it doesn't matter because they technically + * don't exist. * @private - * @returns Either a 0 or a 1. */ async function _setStartTokenId() { try { @@ -71,10 +72,8 @@ export async function useEvmNft( await contract.ownerOf(0); _startTokenId.value = 0; - // return 0; } catch { _startTokenId.value = 1; - // return 1; } } @@ -86,8 +85,6 @@ export async function useEvmNft( * @private * @param {string} holderPublicKey - The public key (address) of the NFT * holder. If null, the total supply of NFTs is retrieved. - * @returns {Promise} - A Promise that resolves to the balance of - * NFTs owned by the holder or the total supply as a number. */ async function _setBalance(holderPublicKey) { if (_balance.value > -1) { @@ -96,12 +93,10 @@ export async function useEvmNft( if (holderPublicKey) { holderPublicKey = holderPublicKey.toLowerCase(); const balance = await contract.balanceOf(holderPublicKey); - // return Number(balance); _balance.value = Number(balance); } else { const balance = await contract.totalSupply(); _balance.value = Number(balance); - // return Number(balance); } } } @@ -112,7 +107,6 @@ export async function useEvmNft( * for determining which subset of tokens to fetch on a specific page. * @private * @param {number} page - The current page number. Defaults to 1 if not provided. - * @param {number} balance - The total number of tokens or items available. * @param {number} pageSize - The number of tokens or items to display per page. * @param {boolean} isAscending - Determines the order of retrieval: * - `true`: Retrieves items in ascending order. @@ -122,17 +116,17 @@ export async function useEvmNft( * - `endIndex` (number): The index at which to end retrieval (inclusive). * - `lastPage` (number): The total number of pages based on the balance and page size. */ - function _calculatePageIndexes(page, balance, isAscending) { - const lastPage = Math.ceil(balance / pageSize); + function _calculatePageIndexes(page, isAscending) { + const lastPage = Math.ceil(_balance.value / pageSize); page = page || 1; let startIndex, endIndex; if (isAscending) { startIndex = pageSize * (page - 1); - endIndex = Math.min(balance, pageSize * page); + endIndex = Math.min(_balance.value, pageSize * page); } else { - startIndex = Math.max(0, balance - pageSize * page); - endIndex = balance - pageSize * (page - 1); + startIndex = Math.max(0, _balance.value - pageSize * page); + endIndex = _balance.value - pageSize * (page - 1); } if (_startTokenId.value === 0) { @@ -145,27 +139,26 @@ export async function useEvmNft( /** * Fetches a batch of NFT tokens from the contract, including their owners, * within a specified range. This function does not work well with contracts - * that have burned tokens. Use fetchUserTokens instead. + * that have burned tokens. Use fetchUserTokens instead. Used only for getNfts + * with on-chain validation. * @private * @param {number} startIndex - The starting index for fetching tokens (adjusted * to the token IDs). * @param {number} endIndex - The ending index for fetching tokens. - * @param {number} startTokenId - The starting token ID of the contract, which - * could be 0 or 1 depending on the contract. * @returns {Promise} - A Promise that resolves to an array of objects, * each containing: * - `tokenId` (number): The ID of the fetched token. * - `owner` (string): The address of the token's owner. */ - async function _fetchAllTokens(startIndex, endIndex, startTokenId) { + async function _fetchAllTokens(startIndex, endIndex) { const batchedTokenIdPromises = []; // Adjust startIndex to match token IDs for contracts starting at 0 or 1 - startIndex += startTokenId; + startIndex += _startTokenId.value; for ( let tokenId = endIndex; - tokenId >= startIndex && tokenId >= startTokenId; + tokenId >= startIndex && tokenId >= _startTokenId.value; tokenId-- ) { batchedTokenIdPromises.push( @@ -189,11 +182,12 @@ export async function useEvmNft( } /** - * Fetches a batch of NFT tokens owned by a specific user from the + * Fetches a batch of NFTs on-chain owned by a specific user from the * contract, excluding metadata. This function generates promises * to retrieve token IDs for a user's NFTs, based on a range of indices. * The tokens are fetched using `tokenOfOwnerByIndex`, which is - * specific to the holder's address. + * specific to the holder's address. Used only for getNfts with on-chain + * validation. * @private * @param {number} startIndex - The starting index for fetching tokens (inclusive). * @param {number} endIndex - The ending index for fetching tokens (inclusive). @@ -225,45 +219,15 @@ export async function useEvmNft( return Promise.all(batchedTokenIdPromises); } - /** - * Gets NFTs and their Meta Data, with support for paging, and sorting by Token ID. - * @public - * @param {number} page - The page. - * @param {boolean} isAscending - The sort direction. - * @returns - */ - async function getNfts(page, isAscending) { - _contractRequired(); - loadingMessage.value = 'Connecting to Blockchain...'; - - await _setStartTokenId(); - await _setBalance(holderPublicKey); - const { startIndex, endIndex, lastPage } = _calculatePageIndexes( - page, - _balance.value, - isAscending, - startTokenId - ); - - // Fetch tokens based on whether a specific wallet is provided or not - const batchedTokenIds = holderPublicKey - ? await _fetchUserTokens(startIndex, endIndex, holderPublicKey) - : await _fetchAllTokens(startIndex, endIndex, startTokenId); - - const tokens = await getMetaDataBatch(batchedTokenIds, isAscending); - - return { tokens, pageSize, count: _balance.value }; - } - /** * Prepares and post processes the batched token IDs to assemble meta-data and - * on chain token IDs and owners. + * on chain token IDs and owners. Used only for getNfts with on-chain validation. * @public * @param {*} batchedTokenIds - An array of objects with tokenId, and owner props. * @param {*} isAscending - true if is ascending sort order, false for desc. * @returns An array of objects containing the tokens and their meta-data. */ - async function getMetaDataBatch(batchedTokenIds, isAscending) { + async function _getMetaDataBatch(batchedTokenIds, isAscending) { // Create an array to store the valid token IDs const validTokenIds = []; for (const batchedToken of batchedTokenIds) { @@ -302,7 +266,7 @@ export async function useEvmNft( * @public * @param {array} tokenIds - An array of token IDs. * @returns An array of objects containing the token ID, meta-data URL, - * meta-data, and private data. + * meta-data, and null private data. */ async function getTokenMetaData(tokenIds) { loadingMessage.value = 'Fetching Meta Data...'; @@ -376,8 +340,41 @@ export async function useEvmNft( } /** - * Retrieves and paginates NFT metadata based on the contract's token balance and configuration. This method optimizes performance by avoiding direct blockchain queries for token IDs, instead calculating them directly. If a chain ID is specified, this function will be extra fast because it can use Dig-A-Hash predictable meta data. - * + * Gets NFTs and their Meta Data using on-chain validation, with support + * for paging, and sorting by Token ID. this function is a little slower + * than getMetaDataCollection because it validates each token on-chain. + * @public + * @param {number} page - The page. + * @param {boolean} isAscending - The sort direction. + * @returns + */ + async function getNfts(page, isAscending) { + _contractRequired(); + loadingMessage.value = 'Connecting to Blockchain...'; + + await _setStartTokenId(); + await _setBalance(holderPublicKey); + const { startIndex, endIndex, lastPage } = _calculatePageIndexes( + page, + isAscending + ); + + // Fetch tokens based on whether a specific wallet is provided or not + const batchedTokenIds = holderPublicKey + ? await _fetchUserTokens(startIndex, endIndex, holderPublicKey) + : await _fetchAllTokens(startIndex, endIndex); + + const tokens = await _getMetaDataBatch(batchedTokenIds, isAscending); + + return { tokens, pageSize, count: _balance.value }; + } + + /** + * Retrieves and paginates NFT metadata based on the contract's token balance + * and configuration. This method optimizes performance by avoiding direct + * blockchain queries for token IDs, instead calculating them directly. If + * a chain ID is specified, this function will be extra fast because it can + * use Dig-A-Hash predictable meta data. * @param {number} page - The page number for pagination. Defaults to 1 if not provided. * @param {boolean} isAscending - If true, sorts tokens in ascending order by token ID; if false, descending order. * @returns {Promise} An object containing: {Array} tokens - An array of objects where each object includes token metadata, and token ID. @@ -393,7 +390,6 @@ export async function useEvmNft( await _setBalance(holderPublicKey); const { startIndex, endIndex, lastPage } = _calculatePageIndexes( page, - _balance.value, isAscending ); @@ -421,7 +417,6 @@ export async function useEvmNft( return { getNfts, getTokenOwner, - getMetaDataBatch, getTokenMetaData, loadingMessage, getMetaDataCollection, diff --git a/src/composables/useEvmNftGallery.js b/src/composables/useEvmNftGallery.js index 3ad39c5..0760598 100644 --- a/src/composables/useEvmNftGallery.js +++ b/src/composables/useEvmNftGallery.js @@ -4,36 +4,33 @@ import { useNftStore } from '../stores/nftStore'; import { ethers } from 'ethers'; /** - * Initializes the NFT Gallery composable exposing several variables and - * functions needed to sort and page through EVM based NFT Contracts. This - * component is dependant on the useEvmNft composable, and Pinia nftStore. - * @param {string} contractPublicKey - The public key of the wallet holding the contract. - * @param {string} contractAddress - The contract address. - * @param {array} abi - The contract ABI. - * @param {number} chainId - The EVM Chain ID, pass null to use Dig-A-Hash - * meta-data for improved Meta Data fetching performance. - * @param {string} holderPublicKey - Gets NFTs on contract held by this wallet only. - * If null, all NFTs on contract will return. - * @param {string} ethersProviderUrl - The Ethers provider for the Chain ID. - * @param {number} itemsPerPage - The number of items to get per page. - * @param {string} nftStoreItemCollectionName - The NFT Store collection name. - * @param {boolean} isAscendingSort - Sorting by Token ID direction. + * Initializes the NFT Gallery composable exposing several variables and functions + * needed to sort and page through EVM based NFT Contracts. This will be a little + * slower than useEvmMetaDataGallery, and public PRCs will enforce smaller page + * sizes because this composable will verify every NFT on-chain, which results in a + * Blockchain RPC call for every NFT. That also means this composable cannot fetch + * all NFTs on a contract at once, blockchain RPCs will not serve that purpose + * well. However, such blockchain verification allows this composable to fetch + * NFTs from a specific wallet, or all NFTs on contract. + * @param {object} config - The EvmNftOptions configuration object for + * the useEvmNftGallery. * @returns page, numberOfPages, nfts, isAscending, toggleSortOrder, - * isLoading loadingMessage, getNftPage, getTokenOwner, getTokenMetaData, getMetaDataBatch + * isLoading loadingMessage, getNftPage, getTokenOwner, getTokenMetaData. */ -export function useEvmNftGallery( - contractPublicKey, - contractAddress, - abi, - chainId, - holderPublicKey, - ethersProviderUrl, - itemsPerPage, - nftStoreItemCollectionName, - isAscendingSort -) { - const nftStore = useNftStore(); +export function useEvmNftGallery(config) { + const { + contractPublicKey, + contractAddress, + abi, + chainId, + holderPublicKey, + rpc, + itemsPerPage, + nftStoreItemCollectionName, + isAscendingSort, + } = config; + const nftStore = useNftStore(); const page = ref(1); const numberOfPages = ref(0); const nfts = ref([]); @@ -45,14 +42,13 @@ export function useEvmNftGallery( let _getMyNfts = null; let _getTokenOwner = null; let _getTokenMetaData = null; - let _getMetaDataBatch = null; onMounted(async () => { nftStore.addCollection(nftStoreItemCollectionName); const evmNft = await useEvmNft( parseInt(itemsPerPage), - new ethers.JsonRpcProvider(ethersProviderUrl), + new ethers.JsonRpcProvider(rpc), holderPublicKey, contractPublicKey, contractAddress, @@ -66,7 +62,6 @@ export function useEvmNftGallery( _getMyNfts = evmNft.getNfts; _getTokenOwner = evmNft.getTokenOwner; _getTokenMetaData = evmNft.getTokenMetaData; - _getMetaDataBatch = evmNft.getMetaDataBatch; await getNftPage(page.value); }); @@ -149,15 +144,6 @@ export function useEvmNftGallery( return await _getTokenMetaData(tokenIds); } - /** - * Retrieves metadata for a batch of tokens. Exposing a proxy function for evmNft. - * @param {array} batchObjects - Array of batch objects, each containing details for multiple tokens. - * @returns {Promise} - A promise that resolves with the metadata for the batch of tokens. - */ - async function getMetaDataBatch(batchObjects) { - return await _getMetaDataBatch(batchObjects); - } - return { page, numberOfPages, @@ -169,6 +155,5 @@ export function useEvmNftGallery( getNftPage, getTokenOwner, getTokenMetaData, - getMetaDataBatch, }; } diff --git a/src/modules/blockchains.js b/src/modules/blockchains.js index ee61907..190c17a 100644 --- a/src/modules/blockchains.js +++ b/src/modules/blockchains.js @@ -3,8 +3,8 @@ export const blockchains = { chainId: 43114, name: 'Avalanche', enabled: true, - explorerName: 'SnowTrace Explorer', - explorerUrl: 'https://snowtrace.io/', + explorerName: 'Avascan Explorer', + explorerUrl: 'https://avascan.info/', nativeCurrency: { name: 'AVAX', symbol: 'AVAX', @@ -50,11 +50,11 @@ export const blockchains = { symbol: 'POL', decimals: 18, }, - publicRpc: 'https://polygon.llamarpc.com', // Batch size reasonable. + publicRpc: 'https://polygon-rpc.com', // Batch size limit 8. altPublicRpc: [ - 'https://polygon-rpc.com', // Batch size limit 8. 'https://rpc.ankr.com/polygon', // Batch size limit 8. 'https://1rpc.io/matic', // Batch size limit 8. + 'https://polygon.llamarpc.com', // Batch size 28. ], }, }; diff --git a/src/types/useEvmMetaDataGallery.d.ts b/src/types/useEvmMetaDataGallery.d.ts index b63520c..38c7b44 100644 --- a/src/types/useEvmMetaDataGallery.d.ts +++ b/src/types/useEvmMetaDataGallery.d.ts @@ -1,18 +1,33 @@ import { type ref, Ref } from 'vue'; import { NftMetaData } from './useNftStore'; -export declare function useEvmMetaDataGallery( - contractPublicKey: string, - contractAddress: string, - abi: any[], - chainId: number | null, - holderPublicKey: string | null, - ethersProviderUrl: string, - itemsPerPage: number, - nftStoreItemCollectionName: string, - isAscendingSort: boolean, - isGetAllNftQuery: boolean -): { +/** + * The EvmMetaDataOptions configuration object for the useEvmMetaDataGallery composable. + */ +export interface EvmMetaDataOptions { + contractPublicKey: string; + contractAddress: string; + abi: any[]; + chainId: number | null; + rpc: string; + itemsPerPage: number; + nftStoreItemCollectionName: string; + isAscendingSort: boolean; + isGetAllNftQuery: boolean; +} + +/** + * Similar to the useEvmNftGallery but this composable is designed to fetch + * NFT meta data with much less on-chain validation. This allows for faster + * fetching, and larger page sizes, including the ability to fetch all NFTs + * in one query. This composable cannot fetch NFTs from a specific wallet, + * and is designed only to fetch all NFTs on a contract. + * @param {object} config - The EvmMetaDataOptions configuration object for + * the useEvmMetaDataGallery. + * @returns page, numberOfPages, nfts, isAscending, toggleSortOrder, + * isLoading loadingMessage, getNftPage, getTokenOwner, getTokenMetaData. + */ +export declare function useEvmMetaDataGallery(config: EvmMetaDataOptions): { page: ref; numberOfPages: ref; nfts: ref; @@ -23,5 +38,4 @@ export declare function useEvmMetaDataGallery( getNftPage: (iPage: number) => Promise; getTokenOwner: (tokenId: number) => Promise; getTokenMetaData: (tokenIds: number[]) => Promise; - getMetaDataBatch: (batchObjects: any[]) => Promise; }; diff --git a/src/types/useEvmNftGallery.d.ts b/src/types/useEvmNftGallery.d.ts index 0804978..0b447bf 100644 --- a/src/types/useEvmNftGallery.d.ts +++ b/src/types/useEvmNftGallery.d.ts @@ -1,17 +1,36 @@ import { type ref, Ref } from 'vue'; import { NftMetaData } from './useNftStore'; -export declare function useEvmNftGallery( - contractPublicKey: string, - contractAddress: string, - abi: any[], - chainId: number | null, - holderPublicKey: string | null, - ethersProviderUrl: string, - itemsPerPage: number, - nftStoreItemCollectionName: string, - isAscendingSort: boolean -): { +/** + * The EvmNftOptions configuration object for the useEvmNftGallery composable. + */ +export interface EvmNftOptions { + contractPublicKey: string; + contractAddress: string; + abi: any[]; + chainId: number | null; + holderPublicKey: string | null; + rpc: string; + itemsPerPage: number; + nftStoreItemCollectionName: string; + isAscendingSort: boolean; +} + +/** + * Initializes the NFT Gallery composable exposing several variables and functions + * needed to sort and page through EVM based NFT Contracts. This will be a little + * slower than useEvmMetaDataGallery, and public PRCs will enforce smaller page + * sizes because this composable will verify every NFT on-chain, which results in a + * Blockchain RPC call for every NFT. That also means this composable cannot fetch + * all NFTs on a contract at once, blockchain RPCs will not serve that purpose + * well. However, such blockchain verification allows this composable to fetch + * NFTs from a specific wallet, or all NFTs on contract. + * @param {object} config - The EvmNftOptions configuration object for + * the useEvmNftGallery. + * @returns page, numberOfPages, nfts, isAscending, toggleSortOrder, + * isLoading loadingMessage, getNftPage, getTokenOwner, getTokenMetaData. + */ +export declare function useEvmNftGallery(config: EvmNftOptions): { page: ref; numberOfPages: ref; nfts: ref; @@ -22,5 +41,4 @@ export declare function useEvmNftGallery( getNftPage: (iPage: number) => Promise; getTokenOwner: (tokenId: number) => Promise; getTokenMetaData: (tokenIds: number[]) => Promise; - getMetaDataBatch: (batchObjects: any[]) => Promise; }; From 40edbace6a69844092acd844a0d909508c44ebeb Mon Sep 17 00:00:00 2001 From: KidSysco Date: Sat, 21 Dec 2024 00:35:45 -0600 Subject: [PATCH 6/8] update tests --- .../useEvmNftGallery.history.test.js | 74 +++++++++---------- .../dahDemoV1/useEvmNftGallery.holder.test.js | 48 ++++++------ .../dahNftV2/useEvmNftGallery.history.test.js | 30 ++++---- .../dahNftV2/useEvmNftGallery.holder.test.js | 44 +++++------ 4 files changed, 98 insertions(+), 98 deletions(-) diff --git a/tests/dahDemoV1/useEvmNftGallery.history.test.js b/tests/dahDemoV1/useEvmNftGallery.history.test.js index c0b151f..6e3387f 100644 --- a/tests/dahDemoV1/useEvmNftGallery.history.test.js +++ b/tests/dahDemoV1/useEvmNftGallery.history.test.js @@ -9,7 +9,7 @@ let contractPublicKey = '0xcbb2a9868d73f24c056893131b97a69ffd36eba9'; // DAH let contractAddress = '0x33f1cdD52e7ec6F65Ab93dD518c1e2EdB3a8Dd63'; // DAH - Roadmap let chainId = blockchains.avalanche.chainId; let itemsPerPage = 5; -let nftStoreCollectionName = 'nftSmartContract1'; +let nftStoreItemCollectionName = 'nftSmartContract1'; const suspenseTemplate = '
'; test.beforeEach(() => { @@ -20,17 +20,17 @@ test('should fetch page 1 of all NFTs on contract', async (t) => { const wrapper = mount( { setup() { - const { nfts } = useEvmNftGallery( + const { nfts } = useEvmNftGallery({ contractPublicKey, contractAddress, - dahDemoV1Abi, + abi: dahDemoV1Abi, chainId, - null, - blockchains.avalanche.publicRpc, + holderPublicKey: null, + rpc: blockchains.avalanche.publicRpc, itemsPerPage, - nftStoreCollectionName, - true - ); + nftStoreItemCollectionName, + isAscendingSort: true, + }); return { nfts }; }, template: suspenseTemplate, @@ -61,17 +61,17 @@ test('should fetch page 2 of all NFTs on contract', async (t) => { const wrapper = mount( { setup() { - const { nfts, getNftPage } = useEvmNftGallery( + const { nfts, getNftPage } = useEvmNftGallery({ contractPublicKey, contractAddress, - dahDemoV1Abi, + abi: dahDemoV1Abi, chainId, - null, - blockchains.avalanche.publicRpc, + holderPublicKey: null, + rpc: blockchains.avalanche.publicRpc, itemsPerPage, - nftStoreCollectionName, - true - ); + nftStoreItemCollectionName, + isAscendingSort: true, + }); return { nfts, getNftPage }; }, @@ -118,17 +118,17 @@ test('should fetch page 1 of all NFTs on contract in desc order', async (t) => { const wrapper = mount( { setup() { - const { nfts } = useEvmNftGallery( - '0x18582f2CA048ac5f22E5a64F92E8a7d7b1F806a4', // Dog plex - '0x9c870E5B8724Db43E58Cd62C424E3071A3FB66E9', // Dog plex - dog show - dahDemoV1Abi, - blockchains.fantom.chainId, - null, - blockchains.fantom.publicRpc, - 24, - 'a1', - false - ); + const { nfts } = useEvmNftGallery({ + contractPublicKey: '0x18582f2CA048ac5f22E5a64F92E8a7d7b1F806a4', // Dog plex + contractAddress: '0x9c870E5B8724Db43E58Cd62C424E3071A3FB66E9', // Dog plex - dog show + abi: dahDemoV1Abi, + chainId: blockchains.fantom.chainId, + holderPublicKey: null, + chainId: blockchains.fantom.publicRpc, + itemsPerPage: 24, + nftStoreItemCollectionName: 'a1', + isAscendingSort: false, + }); return { nfts }; }, template: suspenseTemplate, @@ -160,17 +160,17 @@ test.only('should fetch page 1 of all NFTs on contract with burned NFTs', async const wrapper = mount( { setup() { - const { nfts } = useEvmNftGallery( - '0x5e44cEFFBeCaeCC0D75b3Be756d40726CE310608', // Urbanhomestead - '0x186EE2C8D81183b6bB06368413bc03ed5aa8eF21', // Urbanhomestead - Products - dahDemoV1Abi, - blockchains.avalanche.chainId, - null, - blockchains.avalanche.publicRpc, - 24, - 'a1', - false - ); + const { nfts } = useEvmNftGallery({ + contractPublicKey: '0x5e44cEFFBeCaeCC0D75b3Be756d40726CE310608', // Urbanhomestead + contractAddress: '0x186EE2C8D81183b6bB06368413bc03ed5aa8eF21', // Urbanhomestead - Products + abi: dahDemoV1Abi, + chainId: blockchains.avalanche.chainId, + holderPublicKey: null, + rpc: blockchains.avalanche.publicRpc, + itemsPerPage: 24, + nftStoreItemCollectionName: 'a1', + isAscendingSort: false, + }); return { nfts }; }, template: suspenseTemplate, diff --git a/tests/dahDemoV1/useEvmNftGallery.holder.test.js b/tests/dahDemoV1/useEvmNftGallery.holder.test.js index 8a3a864..fa95f78 100644 --- a/tests/dahDemoV1/useEvmNftGallery.holder.test.js +++ b/tests/dahDemoV1/useEvmNftGallery.holder.test.js @@ -9,7 +9,7 @@ let contractPublicKey = '0xcbb2a9868d73f24c056893131b97a69ffd36eba9'; let contractAddress = '0x33f1cdD52e7ec6F65Ab93dD518c1e2EdB3a8Dd63'; let chainId = blockchains.avalanche.chainId; let itemsPerPage = 5; -let nftStoreCollectionName = 'nftSmartContract1'; +let nftStoreItemCollectionName = 'nftSmartContract1'; const suspenseTemplate = '
'; test.beforeEach(() => { @@ -20,17 +20,17 @@ test('should fetch page 1 of NFTs by holder', async (t) => { const wrapper = mount( { setup() { - const { nfts } = useEvmNftGallery( + const { nfts } = useEvmNftGallery({ contractPublicKey, contractAddress, - dahDemoV1Abi, + abi: dahDemoV1Abi, chainId, contractPublicKey, - blockchains.avalanche.publicRpc, + rpc: blockchains.avalanche.publicRpc, itemsPerPage, - nftStoreCollectionName, - true - ); + nftStoreItemCollectionName, + isAscendingSort: true, + }); return { nfts }; }, template: suspenseTemplate, @@ -60,17 +60,17 @@ test('should fetch page 2 of NFTs by holder', async (t) => { const wrapper = mount( { setup() { - const { nfts, getNftPage } = useEvmNftGallery( + const { nfts, getNftPage } = useEvmNftGallery({ contractPublicKey, contractAddress, - dahDemoV1Abi, + abi: dahDemoV1Abi, chainId, contractPublicKey, - blockchains.avalanche.publicRpc, + rpc: blockchains.avalanche.publicRpc, itemsPerPage, - nftStoreCollectionName, - true - ); + nftStoreItemCollectionName, + isAscendingSort: true, + }); return { nfts, getNftPage }; }, @@ -113,17 +113,17 @@ test('should fetch page 1 of all NFTs on contract in desc order', async (t) => { const wrapper = mount( { setup() { - const { nfts } = useEvmNftGallery( - '0x18582f2CA048ac5f22E5a64F92E8a7d7b1F806a4', - '0x9c870E5B8724Db43E58Cd62C424E3071A3FB66E9', - dahDemoV1Abi, - blockchains.fantom.chainId, - '0x18582f2CA048ac5f22E5a64F92E8a7d7b1F806a4', - blockchains.fantom.publicRpc, - 24, - 'a1', - false - ); + const { nfts } = useEvmNftGallery({ + contractPublicKey: '0x18582f2CA048ac5f22E5a64F92E8a7d7b1F806a4', + contractAddress: '0x9c870E5B8724Db43E58Cd62C424E3071A3FB66E9', + abi: dahDemoV1Abi, + chainId: blockchains.fantom.chainId, + holderPublicKey: '0x18582f2CA048ac5f22E5a64F92E8a7d7b1F806a4', + rpc: blockchains.fantom.publicRpc, + itemsPerPage: 24, + nftStoreItemCollectionName: 'a1', + isAscendingSort: false, + }); return { nfts }; }, template: suspenseTemplate, diff --git a/tests/dahNftV2/useEvmNftGallery.history.test.js b/tests/dahNftV2/useEvmNftGallery.history.test.js index 8fedddb..d57b685 100644 --- a/tests/dahNftV2/useEvmNftGallery.history.test.js +++ b/tests/dahNftV2/useEvmNftGallery.history.test.js @@ -9,7 +9,7 @@ let contractPublicKey = '0x18582f2CA048ac5f22E5a64F92E8a7d7b1F806a4'; let contractAddress = '0x9c870E5B8724Db43E58Cd62C424E3071A3FB66E9'; let chainId = blockchains.polygon.chainId; let itemsPerPage = 5; -let nftStoreCollectionName = 'nftSmartContract1'; +let nftStoreItemCollectionName = 'nftSmartContract1'; const suspenseTemplate = '
'; test.beforeEach(() => { @@ -20,17 +20,17 @@ test('should fetch page 1 of all NFTs on contract', async (t) => { const wrapper = mount( { setup() { - const { nfts } = useEvmNftGallery( + const { nfts } = useEvmNftGallery({ contractPublicKey, contractAddress, - dahNftV2Abi, + abi: dahNftV2Abi, chainId, - null, - blockchains.polygon.publicRpc, + holderPublicKey: null, + rpc: blockchains.polygon.publicRpc, itemsPerPage, - nftStoreCollectionName, - true - ); + nftStoreItemCollectionName, + isAscendingSort: true, + }); return { nfts }; }, template: suspenseTemplate, @@ -62,17 +62,17 @@ test('should fetch page 2 of all NFTs on contract', async (t) => { const wrapper = mount( { setup() { - const { nfts, getNftPage } = useEvmNftGallery( + const { nfts, getNftPage } = useEvmNftGallery({ contractPublicKey, contractAddress, - dahNftV2Abi, + abi: dahNftV2Abi, chainId, - null, - blockchains.polygon.publicRpc, // otherwise batch too large + holderPublicKey: null, + rpc: blockchains.polygon.publicRpc, // otherwise batch too large itemsPerPage, - nftStoreCollectionName, - true - ); + nftStoreItemCollectionName, + isAscendingSort: true, + }); return { nfts, getNftPage }; }, diff --git a/tests/dahNftV2/useEvmNftGallery.holder.test.js b/tests/dahNftV2/useEvmNftGallery.holder.test.js index 60d590b..7e03cf7 100644 --- a/tests/dahNftV2/useEvmNftGallery.holder.test.js +++ b/tests/dahNftV2/useEvmNftGallery.holder.test.js @@ -9,7 +9,7 @@ let contractPublicKey = '0x18582f2CA048ac5f22E5a64F92E8a7d7b1F806a4'; let contractAddress = '0x9c870E5B8724Db43E58Cd62C424E3071A3FB66E9'; let chainId = blockchains.polygon.chainId; let itemsPerPage = 5; -let nftStoreCollectionName = 'nftSmartContract1'; +let nftStoreItemCollectionName = 'nftSmartContract1'; const suspenseTemplate = '
'; test.beforeEach(() => { @@ -20,17 +20,17 @@ test('should fetch page 1 of NFTs by holder', async (t) => { const wrapper = mount( { setup() { - const { nfts } = useEvmNftGallery( + const { nfts } = useEvmNftGallery({ contractPublicKey, contractAddress, - dahNftV2Abi, + abi: dahNftV2Abi, chainId, contractPublicKey, - blockchains.polygon.publicRpc, + rpc: blockchains.polygon.publicRpc, itemsPerPage, - nftStoreCollectionName, - true - ); + nftStoreItemCollectionName, + isAscendingSort: true, + }); return { nfts }; }, template: suspenseTemplate, @@ -60,17 +60,17 @@ test('should fetch page 2 of NFTs by holder', async (t) => { const wrapper = mount( { setup() { - const { nfts, getNftPage } = useEvmNftGallery( + const { nfts, getNftPage } = useEvmNftGallery({ contractPublicKey, contractAddress, - dahNftV2Abi, + abi: dahNftV2Abi, chainId, contractPublicKey, - blockchains.polygon.publicRpc, // otherwise batch too large + rpc: blockchains.polygon.publicRpc, // otherwise batch too large itemsPerPage, - nftStoreCollectionName, - true - ); + nftStoreItemCollectionName, + isAscendingSort: true, + }); return { nfts, getNftPage }; }, @@ -115,17 +115,17 @@ test('should fetch page 1 of all NFTs by holder in desc order', async (t) => { const wrapper = mount( { setup() { - const { nfts } = useEvmNftGallery( + const { nfts } = useEvmNftGallery({ contractPublicKey, contractAddress, - dahNftV2Abi, - blockchains.polygon.chainId, - contractPublicKey, - blockchains.polygon.publicRpc, - 6, - 'a1', - false - ); + abi: dahNftV2Abi, + chainId: blockchains.polygon.chainId, + holderPublicKey: contractPublicKey, + rpc: blockchains.polygon.publicRpc, + itemsPerPage: 6, + nftStoreItemCollectionName: 'a1', + isAscendingSort: false, + }); return { nfts }; }, template: suspenseTemplate, From b317f76d6e328d6b1263ead98d25b7b33009b1f9 Mon Sep 17 00:00:00 2001 From: KidSysco Date: Sat, 21 Dec 2024 23:28:11 -0600 Subject: [PATCH 7/8] update docs --- README.md | 140 ++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 119 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index ab38653..87c260e 100644 --- a/README.md +++ b/README.md @@ -17,16 +17,16 @@ No API is needed, the blockchain is the API! - Sorting by Token ID. - Local caching with Pinia. - More efficient RPC Batch Call support. -- Utilities for working with Meta Data structures. +- Utilities for working with NFT Meta Data structures. ## Dependencies This package depends on other packages such as `axios`, `ethers.js`, `pinia`, and `vue`. Your project should already have these installed, this package has been tested with... -- axios 1.7.7 -- ethers.js 6.13.3 -- pinia 2.2.4 -- vue 3.5.11 +- axios 1.7.7+ +- ethers.js 6.13.3+ +- pinia 2.2.4+ +- vue 3.5.11+ ## Installation @@ -37,9 +37,30 @@ npm install vue-evm-nft ``` # Docs -This project is free and open source, although it was originally built to work with [Dig-A-Hash Dynamic Meta Data Services](https://www.dig-a-hash.com). We build lots of websites that display NFTs, so this component was super useful to us. However, Dig-A-Hash specific settings have been removed from this package so that these components can be used for any NFT project, on any EVM-compatible blockchain. +This project is free and open source, although it was originally built to work with [Dig-A-Hash Dynamic Meta Data Services](https://www.dig-a-hash.com). We build lots of websites that display NFTs, so these components are super useful to us. Some of these components can be used for any NFT project, on any EVM-compatible blockchain, while others will be exclusively for use with NFT collections using Dig-A-Hash Dynamic Meta Data. -This package ships with 2 composables, 2 Pinia stores, and modules containing the ABI arrays for the various Dig-A-Hash NFT Smart Contracts. +This package ships with 3 composables, 2 Pinia stores, and modules containing the ABI arrays for the various Dig-A-Hash NFT Smart Contracts. + +Web 3 developers are expected to provide params for their own contracts, wallets, ABIs, and RPCs. Web 3 developers should have a solid working knowledge of the [Ethereum Virtual Machine (EVM)](https://ethereum.org/en/developers/docs/evm/), [Open Zeppelin](https://docs.openzeppelin.com/), [Vuetify](https://vuetifyjs.com/en/), [Vue](https://vuejs.org/), and [Pinia](https://pinia.vuejs.org/). + +There are 2 main composables to use... + +```useEvmNftGallery``` - Can be used with any EVM-based NFT project. + +```useEvmMetaDataGallery``` - Can only be used with [Dig-A-Hash Dynamic Meta Data Services](https://www.dig-a-hash.com). + +There are some other small differences below... + +|Composable|Fully Verify NFTs On-Chain|List Tokens in a Specific Wallet|List All Tokens on Contract|Get All Tokens in 1 Call|Large Page Sizes| +|----------|----------|----------|----------|----------|----------| +|```useEvmNftGallery``` |X|X|X||| +|```useEvmMetaDataGallery``` |||X|X|X| + +## useEvmNftGallery + +The `useEvmNftGallery` composable is designed to work with any NFT project displaying NFTs stored in EVM-based contracts. It allows sorting and pagination through NFTs while leveraging other composables (`useEvmNft` and `useEvmNftStore`) for local caching and utility functions. + +This composable will get the Meta Data from the URL of every NFT as it is listed on-chain. So this composable can be used even if your NFTs do not use Dig-A-Hash dynamic Meta Data. This composable will be a little slower than useEvmMetaDataGallery, and public PRCs will enforce smaller page sizes. This composable cannot fetch all NFTs on a contract at once, blockchain RPCs simply do not serve that purpose well. However, the blockchain verification allows this composable to fetch bNFTs from a specific wallet, or all NFTs on contract. ## Usage @@ -51,33 +72,88 @@ import { } from 'vue-evm-nft'; const { page, numberOfPages, nfts, isAscending, toggleSortOrder, getNftPage, getTokenOwner, getTokenMetaData } = - useEvmNftGallery( + useEvmNftGallery({ contractPublicKey, contractAddress, - abi, + abi: dahDemoV1Abi, chainId, - null, - blockchains.fantom.publicRpc, + holderPublicKey: null, + rpc: blockchains.avalanche.publicRpc, itemsPerPage, - nftStoreCollectionName, - false - ); + nftStoreItemCollectionName, + isAscendingSort: true, + }); ``` -## useEvmNftGallery +#### Configuration Object (config: EvmNftOptions) +- **`contractPublicKey`** (`string`): The public key of the wallet holding the contract. +- **`contractAddress`** (`string`): The address of the NFT contract. +- **`abi`** (`array`): The contract's ABI (Application Binary Interface). +- **`chainId`** (`number`): The EVM Chain ID. Pass `null` if you are not using Dig-A-Hash meta data, then the composable will get the Meta Dat from the token by making a call to the contract. Pass a chain ID if using Dig-A-Hash hosted meta data for improved performance when fetching meta data. +- **`holderPublicKey`** (`string`): (Optional) If provided, fetches NFTs owned by this wallet. If `null`, it will return all NFTs associated with the contract. Warning: If the contract has burned tokens, then passing null here will result in inaccurate counts and listings, the only option for this case is to pass a public Key instead (not null). +- **`rpc`** (`string`): The URL of the Ethers provider corresponding to the specified chain. +- **`itemsPerPage`** (`number`): The number of NFTs to display per page. +- **`nftStoreItemCollectionName`** (`string`): The name of the NFT store collection, used to reference the cache in Pinia. Use a unique name to be referenced later, whenever you need to access the cached NFTs in the store by page. +- **`isAscendingSort`** (`boolean`): Determines the starting sorting order of the NFTs. Set to `true` for ascending order, `false` for descending. Use onToggleSortOrder() to change the sort direction. -The `useEvmNftGallery` composable is designed to manage and display NFTs stored in EVM-based contracts. It allows sorting and pagination through NFTs while leveraging other composables (`useEvmNft` and `useEvmNftStore`) for local caching and enhanced functionality. +#### Returns an object containing: +- **`page`**: The current page of NFTs being displayed. Changing page will cause the component to fetch and store the new page of NFTs in Pinia. If the page has already been fetched, it will not fetch again until page refresh. +- **`numberOfPages`**: The total number of pages based on the number of items and the `itemsPerPage`specified in the params above. +- **`nfts`**: The current page of NFTs to be displayed. +- **`isAscending`**: The current sorting order of NFTs. +- **`toggleSortOrder`**: A function to toggle or change the sorting order of NFTs. +- **`isLoading`**: A boolean that will indicate whether or not the component is fetching. Create a vue watcher to track changes. +- **`loadingMessage`**: A string that will indicate exactly what the component is doing while isLoading is true. Create a vue watcher to track changes. +- **`getNftPage`**: An async function that takes a param indicating which page of NFTs to fetch from the contract. +- **`getTokenOwner`**: An async function to get the current token holder wallet address. +- **`getTokenMetaData`**: An async function to get meta data for an array of Token Ids. If chain ID is not null, then we get the meta data without needing to access the blockchain at all because we use Dig-A-Hash predictable storage paths based on that chain ID. -#### Parameters +## useEvmMetaDataGallery + +The `useEvmMetaDataGallery` composable is designed to work with NFT contracts using [Dig-A-Hash Dynamic Meta Data Services](https://www.dig-a-hash.com). It allows sorting and pagination through NFTs while leveraging other composables (`useEvmNft` and `useEvmNftStore`) for local caching and enhanced functionality. + +This composable is similar to the useEvmNftGallery but this composable is designed to fetch NFT meta data with much less on-chain validation. This allows for faster fetching, and larger page sizes, including the ability to fetch all NFTs in one query. This composable cannot fetch NFTs from a specific wallet, and is designed only to fetch all NFTs on a contract. + +## Usage + +```javascript +import { + useEvmMetaDataGallery, + blockchains, + dahDemoV1Abi as abi, +} from 'vue-evm-nft'; + +const { + page, + numberOfPages, + nfts, + isLoading, + loadingMessage, + isAscending, + toggleSortOrder, +} = useEvmMetaDataGallery({ + contractPublicKey, + contractAddress, + abi, + chainId: blockchains.fantom.chainId, + rpc: blockchains.fantom.publicRpc, + itemsPerPage, + nftStoreItemCollectionName, + isAscendingSort: false, + isGetAllNftQuery: false, +}); +``` + +#### Configuration Object (config: EvmMetaDataOptions) - **`contractPublicKey`** (`string`): The public key of the wallet holding the contract. - **`contractAddress`** (`string`): The address of the NFT contract. - **`abi`** (`array`): The contract's ABI (Application Binary Interface). - **`chainId`** (`number`): The EVM Chain ID. Pass `null` if you are not using Dig-A-Hash meta data, then the composable will get the Meta Dat from the token by making a call to the contract. Pass a chain ID if using Dig-A-Hash hosted meta data for improved performance when fetching meta data. -- **`holderPublicKey`** (`string`): (Optional) If provided, fetches NFTs owned by this wallet. If `null`, it will return all NFTs associated with the contract. Warning: If the contract has burned tokens, then passing null here will result in inaccurate counts and listings, the only option for this case is to pass a public Key instead (not null). -- **`ethersProviderUrl`** (`string`): The URL of the Ethers provider corresponding to the specified chain. +- **`rpc`** (`string`): The URL of the Ethers provider corresponding to the specified chain. - **`itemsPerPage`** (`number`): The number of NFTs to display per page. -- **`nftStoreItemCollectionName`** (`string`): The name of the NFT store collection. +- **`nftStoreItemCollectionName`** (`string`): The name of the NFT store collection, used to reference the cache in Pinia. Use a unique name to be referenced later, whenever you need to access the cached NFTs in the store by page. - **`isAscendingSort`** (`boolean`): Determines the starting sorting order of the NFTs. Set to `true` for ascending order, `false` for descending. Use onToggleSortOrder() to change the sort direction. +- **`isGetAllNftQuery`** (`boolean`): Set to `true` to get all NFTs back in the entire collection, this will be done in batches of `itemsPerPage`. `false` to get back pages of data with `itemsPerPage` items in each page. #### Returns an object containing: - **`page`**: The current page of NFTs being displayed. Changing page will cause the component to fetch and store the new page of NFTs in Pinia. If the page has already been fetched, it will not fetch again until page refresh. @@ -92,7 +168,9 @@ The `useEvmNftGallery` composable is designed to manage and display NFTs stored - **`getTokenMetaData`**: An async function to get meta data for an array of Token Ids. If chain ID is not null, then we get the meta data without needing to access the blockchain at all because we use Dig-A-Hash predictable storage paths based on that chain ID. ## useEvmNft -This is used internally by useEvmNftGallery but it can still be used directly. The useEvmNft composable fetches NFT data from an ERC721 contract and retrieves metadata either directly from the blockchain or via the Dig-A-Hash storage pattern. It supports pagination, sorting, and retrieving the current owner of a token. +The useEvmNft composable fetches NFT data from an ERC721 contract and retrieves metadata either directly from the blockchain or via the Dig-A-Hash storage pattern. It supports pagination, sorting, and retrieving the current owner of a token. + +This is used internally by useEvmNftGallery, and useEvmMetaDataGallery. This composable can still be used directly but the best way to use this composable is through useEvmNftGallery or useEvmMetaDataGallery as both composables will pass the public functions from useEvmNft through to the host component. For example, the functions getNftPage(), getTokenOwner(), and getTokenMetaData() returned from this useEvmNftGallery, and useEvmMetaDataGallery are passed through from useEvmNft. - **`pageSize`** (`number`): The number of NFTs to display per page. - **`provider`** (`object`): The `ethers.js` provider instance. @@ -134,12 +212,32 @@ const loadNFTs = async () => { ## useNftStore This Pinia store used internally by useEvmNftGallery but it can still be used directly. The `useNftStore` is a Pinia store used to manage collections of NFTs, their metadata, and associated image URLs. It allows for efficient retrieval and management of NFT attributes, including safe handling of different image sizes. +Refer to the various NFT collections in the store using nftStoreItemCollectionName. + #### State - **`itemCollections`** (`object`): Stores NFT collections. Each collection is keyed by its name and contains the following properties: - **`items`** (`array`): An array of NFTs for each page. - **`itemCount`** (`number`): The total number of items in the collection. - **`page`** (`number`): The current page of items being viewed. +``` +import { + useEvmNftGallery, + useEvmMetaDataGallery, + blockchains, + dahDemoV1Abi as abi, + useNftStore, +} from 'vue-evm-nft'; + +const nftStore = useNftStore(); + +// page 1 +nftStore.$state.itemCollections[nftStoreItemCollectionName].items[0]; + +// current page +nftStore.$state.itemCollections[nftStoreItemCollectionName].page; +``` + #### Getters - **`getNftUrl`** (`function`): Generates the URL for viewing an NFT on the website. - **Parameters**: From b7f578136e6d994f37d1bbe2711fece4049ac670 Mon Sep 17 00:00:00 2001 From: KidSysco Date: Sun, 22 Dec 2024 23:32:53 -0600 Subject: [PATCH 8/8] update types --- src/stores/nftStore.js | 54 +++++++++++++++++++++++++++++--------- src/types/useNftStore.d.ts | 13 +++++---- 2 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/stores/nftStore.js b/src/stores/nftStore.js index 4a69d97..df7feb2 100644 --- a/src/stores/nftStore.js +++ b/src/stores/nftStore.js @@ -40,23 +40,53 @@ export const useNftStore = defineStore('nftStore', { // NFT Meta Data Attributes /** - * Gets a large version of the image if specified, otherwise the default NFT image. - * This is a safe, and efficient way to call for images. - * @returns An image URL. + * Converts an imgur original image to a smaller image. + * @returns */ - getImageLarge: () => { - return (metaData) => { - return metaDataAttributeValueOrImage(metaData, 'url-large'); + getImageMedium: () => { + return (url) => { + try { + const lastDotIndex = url.lastIndexOf('.'); + + // If there's no extension, just add "m" to the file name + if (lastDotIndex === -1) { + return url + 'm'; + } + + // Split the name and extension + const namePart = url.substring(0, lastDotIndex); + const extensionPart = url.substring(lastDotIndex); + + // Add "m" to the name part, keep the extension as is + return `${namePart}m${extensionPart}`; + } catch (error) { + return url; + } }; }, /** - * Gets a medium version of the image if specified, otherwise the default NFT image. - * This is a safe, and efficient way to call for images. - * @returns An image URL. + * Converts an imgur original image to a large image. Still smaller than the original. + * @returns */ - getImageMedium: () => { - return (metaData) => { - return metaDataAttributeValueOrImage(metaData, 'url-medium'); + getImageLarge: () => { + return (url) => { + try { + const lastDotIndex = url.lastIndexOf('.'); + + // If there's no extension, just add "l" to the file name + if (lastDotIndex === -1) { + return url + 'l'; + } + + // Split the name and extension + const namePart = url.substring(0, lastDotIndex); + const extensionPart = url.substring(lastDotIndex); + + // Add "l" to the name part, keep the extension as is + return `${namePart}l${extensionPart}`; + } catch (error) { + return url; + } }; }, /** diff --git a/src/types/useNftStore.d.ts b/src/types/useNftStore.d.ts index d081bfa..82c9ebe 100644 --- a/src/types/useNftStore.d.ts +++ b/src/types/useNftStore.d.ts @@ -27,17 +27,20 @@ export interface NftStoreGetters { /** * Gets the URL for an NFT based on its token ID and path. */ - getNftUrl: (tokenId: string | number, path: string) => string; + getNftUrl: ( + tokenId: string | number, + path: string + ) => (tokenId: string | number, path: string) => string; /** - * Gets a large image URL for an NFT if available, otherwise the default NFT image. + * Appends an 'l' to the end of an image to get the large version from imgurl. */ - getImageLarge: (metaData: NftMetaData) => string; + getImageLarge: (url: string) => (url: string) => string; /** - * Gets a medium image URL for an NFT if available, otherwise the default NFT image. + * Appends an 'm' to the end of an image to get the large version from imgurl. */ - getImageMedium: (metaData: NftMetaData) => string; + getImageMedium: (url: string) => (url: string) => string; /** * Gets a public attribute value from the NFT metadata.