Skip to content

Instantly share code, notes, and snippets.

@robertjchristian
Created December 6, 2023 19:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save robertjchristian/c98362735ad8132050ab04b095d79d2f to your computer and use it in GitHub Desktop.
Save robertjchristian/c98362735ad8132050ab04b095d79d2f to your computer and use it in GitHub Desktop.
Alchemy
"use strict";
import AlchemyInitializer from "./AlchemyInitializer";
import { Alchemy } from "alchemy-sdk";
/**
* Class for fetching NFTs minted under a given contract address.
*/
class NFTFetcher {
private alchemy: Alchemy;
private contractAddress: string;
/**
* Creates an instance of NFTFetcher.
*/
constructor(contractAddress: string) {
this.contractAddress = contractAddress;
// Initialize Alchemy
this.alchemy = new AlchemyInitializer().initMatic();
}
/**
* Private method to parse NFT data from a response.
*
*/
private parseNfts(res: any) {
const nfts: any[] = [];
for (let x = 0; x < res.nfts.length; x++) {
const nft = res.nfts[x];
const n: any = nft.raw.metadata;
// Add additional properties to the NFT object
n["tokenId"] = nft.tokenId;
n["uri"] = nft.tokenUri;
n["media"] = nft.media;
nfts.push(n);
}
return nfts;
}
/**
* Private method to fetch NFTs for a contract with pagination.
*
*/
private async fetchNFTsForContractPaged(
contractAddress: string,
pageKey: string | null,
omitMetadata = true
) {
let options: any = { limit: 100, omitMetadata };
if (pageKey) {
options["pageKey"] = pageKey;
}
// Fetch NFTs for the specified contract
const nftsForContractResponse = await this.alchemy.nft.getNftsForContract(
contractAddress,
options
);
return {
nfts: nftsForContractResponse.nfts,
pageKey: nftsForContractResponse.pageKey
? nftsForContractResponse.pageKey
: null,
};
}
/**
* Private method to fetch all NFTs for a contract with pagination.
*
*/
private async fetchAllNFTsForContractWithPaging(
contractAddress: string,
omitMetadata: boolean
) {
let nfts: any[] = [];
let i = 0;
let pageKey = null;
while (true) {
const res = await this.fetchNFTsForContractPaged(
contractAddress,
pageKey,
omitMetadata
);
if (omitMetadata) {
// console.log(res.nfts);
const tokenIds = res.nfts.map((nft) => nft.tokenId);
nfts = nfts.concat(tokenIds);
// filter out the contract address from the fetched NFTs
const filteredNFTs = nfts.filter((nft: any) => nft !== contractAddress);
// return NFT id only
nfts = filteredNFTs;
// sleep to keep under rate limit (throttle)
// reduces "Retrying after error: 429" for large collections
await new Promise((resolve) => setTimeout(resolve, 35)); // TODO make this configurable, depends on key
} else {
// Concatenate the parsed NFTs to the existing array
nfts = nfts.concat(this.parseNfts(res));
}
// console.log(nfts);
// Output pagination information
console.log("Page", i++, res.pageKey, res.nfts.length, nfts.length);
pageKey = res.pageKey;
if (!pageKey) {
break;
}
}
return nfts;
}
/**
* Public method to fetch all NFTs for a contract.
*
*/
async fetchNftsForContract(includeMetadata: boolean = false) {
const omitMetadata = !includeMetadata;
let nfts = await this.fetchAllNFTsForContractWithPaging(
this.contractAddress,
omitMetadata
);
console.log("Finishing up...");
let final: any[] = [];
return nfts
}
}
/**
* Main function to fetch and log NFTs for a specific contract. (run example)
*/
const main = async () => {
const startTime = performance.now(); // Record start time
const contractAddress = "0x135Fb4Ab7A5d62297682A8286f54719465a6C581";
const nftFetcher = new NFTFetcher(contractAddress);
const nfts = await nftFetcher.fetchNftsForContract(false);
console.log(nfts);
const endTime = performance.now(); // Record end time
const elapsedTimeMs = endTime - startTime; // Calculate elapsed time in milliseconds
const elapsedTimeMinutes = elapsedTimeMs / 60000; // Convert to minutes
console.log(
`Completed successfully in ${elapsedTimeMs.toFixed(
0
)} ms (${elapsedTimeMinutes.toFixed(2)} minutes)`
);
};
// Execute the main function and handle any errors
main()
.then(() => process.exit(0))
.catch((error: Error) => {
console.error(error);
process.exit(1);
});
@robertjchristian
Copy link
Author

robertjchristian commented Dec 6, 2023

Issues:

  • 1: Time with metadata for 30k NFTs: ~4-5min
  • 2: Better handling for throttle, or sleep best option?
  • 3: Can filter response payload on server (graphql)?
  • 4: Limit not minded. Can set to 10 or 200, still returns 100 per page.

Unrelated to GIST:

  • Contract 0x135Fb4Ab7A5d62297682A8286f54719465a6C581 marked spam.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment