{"_id":"@0xessential/use-many-indexers","name":"@0xessential/use-many-indexers","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@0xessential/use-many-indexers","version":"0.0.1","description":"Fallback to alternative NFT metadata indexing providers when your primary service fails","main":"dist/index.js","module":"dist/use-many-indexers.esm.js","unpkg":"dist/use-many-indexers.umd.min.js","types":"dist/index.d.ts","sideEffects":false,"scripts":{"build":"yarn run clean && rollup -c","clean":"rimraf dist","lint":"eslint '**/*.{js,ts}'","lint:fix":"yarn run lint -- --fix","lint:types":"tsc --noEmit","test":"jest","test:watch":"jest --watch","test:coverage":"jest --coverage","release":"semantic-release","cz":"git-cz"},"keywords":["react","hooks","react-hooks"],"repository":{"type":"git","url":"git+https://github.com/0xessential/use-many-indexers.git"},"author":{"name":"sammybauch","email":"sam@0xessential.com"},"license":"MIT","bugs":{"url":"https://github.com/0xessential/use-many-indexers/issues"},"homepage":"https://github.com/0xessential/use-many-indexers#readme","devDependencies":{"@commitlint/cli":"^13.1.0","@commitlint/config-conventional":"^13.1.0","@rollup/plugin-commonjs":"^20.0.0","@rollup/plugin-json":"^4.1.0","@rollup/plugin-node-resolve":"^13.0.4","@rollup/plugin-typescript":"^8.5.0","@semantic-release/changelog":"^5.0.1","@semantic-release/git":"^9.0.0","@testing-library/react-hooks":"^7.0.1","@types/jest":"^26.0.24","@types/react":"^18.0.20","@typescript-eslint/eslint-plugin":"^2.34.0","@zoralabs/zdk":"^2.1.8","alchemy-sdk":"^2.0.4","commitizen":"^4.2.4","cz-conventional-changelog":"^3.3.0","eslint":"^6.8.0","eslint-config-airbnb-typescript":"^7.2.1","eslint-config-prettier":"^6.15.0","eslint-plugin-import":"^2.23.4","eslint-plugin-jsx-a11y":"^6.4.1","eslint-plugin-prettier":"^3.4.0","eslint-plugin-react":"^7.24.0","eslint-plugin-react-hooks":"^4.2.0","ethers":"^5.7.1","git-cz":"^4.7.6","husky":"^7.0.1","jest":"^25.5.4","lint-staged":"^11.1.1","nft-react":"^0.5.10","prettier":"^2.3.2","react":"17.0.2","react-test-renderer":"^16.14.0","rimraf":"^3.0.2","rollup":"^2.55.1","rollup-plugin-filesize":"^9.1.1","rollup-plugin-peer-deps-external":"^2.2.4","rollup-plugin-sourcemaps":"^0.6.3","rollup-plugin-terser":"^7.0.2","semantic-release":"^17.4.4","ts-jest":"^25.5.1","typescript":"^4.8.3"},"peerDependencies":{"@zoralabs/zdk":"^2.1.8","alchemy-sdk":"^2.0.4","ethers":"^5.7.1","nft-react":"^0.5.10","react":"^16.14.0"},"husky":{"hooks":{"commit-msg":"commitlint -E HUSKY_GIT_PARAMS","pre-commit":"yarn run lint:types && lint-staged"}},"lint-staged":{"*.{js,ts}":["yarn run lint:fix"],"*.{md,json,yml}":["prettier --write","git add"]},"config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}},"publishConfig":{"access":"public"},"gitHead":"131d4b71062b0b14f47468e5c5dcb50949ae20b0","_id":"@0xessential/use-many-indexers@0.0.1","_nodeVersion":"18.0.0","_npmVersion":"8.6.0","dist":{"integrity":"sha512-3j+WLxgkPhYB3qd5O9kYqF/jWTZPmPokVIO515J0AK/h8Vs9kXy2Mjoyff8ndTVJ3lLFdeiLbEP/g6TAlvpE2Q==","shasum":"4170483248120aaa1392a9c0d3feda497ea90312","tarball":"https://registry.npmjs.org/@0xessential/use-many-indexers/-/use-many-indexers-0.0.1.tgz","fileCount":19,"unpackedSize":158848,"signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEQCID8XBXOFZo22gGk7geynlKCr8kU1WDdc7rI3lujbpIzRAiAh4UiFwQgNRxKXipXhEeyBpRQ3KL3lQOmogLS0xtshqg=="}],"npm-signature":"-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v4.10.10\r\nComment: https://openpgpjs.org\r\n\r\nwsFzBAEBCAAGBQJjP6q+ACEJED1NWxICdlZqFiEECWMYAoorWMhJKdjhPU1b\r\nEgJ2VmoBGw/+NCG9bS4AF0Qc5oaLtflgpN2i8IvpChRDTMZuQ/jw/N6BnMXI\r\nkUhBmt0ChoAf8RYnQMm9WyapKN0jixqU6gfaem5OvdtzFUQGd9z8PDw6zsId\r\nQkll62gx9oWyP8OVcI6VM0cnUGmay41rOdj20LKrmr2cVetsp1BY3PfwRaaV\r\nNqD0gvfzIXsJ8VRaeBJYNkiyxD6q5JACzOqc+pysJQlDi5SUBe/s1TXQJfWh\r\nwrNxpKznDFSZZCCGrYVnejQJdHqx87Mya41PolwUmzPM67TMvj6/gmBnpmbF\r\nYpsNAygHzf0Pc+yh7wYG2nuuIFdAiXsTN5c5mPzu9v69tZkgF/GZbMYPxjKt\r\nzQUyfLScvCkdtaYEYNCA59LPiE3n7EBd2SxZNKtNSc3TWfYl7rt6OwSA1F1/\r\nwF7feq8q8b7kUYBV3p6Abnj7AgA3yxLUQvz5RdIZcYrIwBML7IBPU0BqtKuK\r\nFkYMGwUUBwoXOKq/AEY3ZLr4nbbZM3KMPsq0U/qTjqMszdWTkyMCMpMxQSN7\r\n7WXp0ccwNYd4BwnzdpfC2KzcZtuGS4t0a+1PbqCgIABEtrfkn5gUIJqgsI8J\r\nUaHL8MbbdxtXR9livfvRN/Fnrj1oWLPVOtXVPAASEyROp9rg2ofOqgQGiWpa\r\neJKGUQd00KM/BZWC0W5yB6tw1xY2fHBkfQg=\r\n=neyc\r\n-----END PGP SIGNATURE-----\r\n"},"_npmUser":{"name":"sammybauch","email":"sbauch@gmail.com"},"directories":{},"maintainers":[{"name":"sammybauch","email":"sbauch@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/use-many-indexers_0.0.1_1665116861746_0.6578438277467409"},"_hasShrinkwrap":false}},"time":{"created":"2022-10-07T04:27:41.692Z","0.0.1":"2022-10-07T04:27:41.992Z","modified":"2022-10-07T04:27:42.134Z"},"maintainers":[{"name":"sammybauch","email":"sbauch@gmail.com"}],"description":"Fallback to alternative NFT metadata indexing providers when your primary service fails","homepage":"https://github.com/0xessential/use-many-indexers#readme","keywords":["react","hooks","react-hooks"],"repository":{"type":"git","url":"git+https://github.com/0xessential/use-many-indexers.git"},"author":{"name":"sammybauch","email":"sam@0xessential.com"},"bugs":{"url":"https://github.com/0xessential/use-many-indexers/issues"},"license":"MIT","readme":"# use-many-indexers\n\nReact hook for fetching NFT metadata serially from multiple indexers.\n\nNFT metadata indexing services face an immense challenge in covering edge cases for every NFT on multiple blockchains.\n\nThis React library allows developers to specify an array of indexing services, and each will be queried in order until a successful response is returned.\n\nWe recommend using this project to catch or rescue token metadata when your primary service fails. The library does not support fetching metadata for an array of NFTs - you should choose a primary indexing service that provides metadata for many NFTs in one query, then use this library as a fallback to fetch any metadata your primary service missed.\n\n## Status\n\nThis project is in beta. 0xEssential got tired of trying to find the best indexer, so we built this project to serve our own needs. We're happy to share it with the community, but make no promises about maintenance or additional functionality.\n\n### TODOs We Wont Do But You Can\n\n- **More metadata fields:** The package returns a barebones object with `name` and `imageUrl` strings. We'd be happy to return more data, but standardizing API responses and TS types across a handful of providers is a lot of busy work. PRs welcome!\n\n- **More indexers & indexer docs:** We're pretty happy with coverage we get from the providers we integrated and rarely fall back to RPC calls. We've also standardized an API for adding indexing services and expect that indexers who want to be integrated will submit PRs for their own services.\n\n- **Tests**: lol\n\n- **Logging**: ok we might actually want to make logging better. Using console logs now, should probably use a Logger that can be configured. But it's also kinda nice to see in the console which service provides the best data.\n\n## Install\n\n```bash\n yarn add @0xessential/use-many-indexers\n```\n\n## MetadataProvider\n\n### Import\n\nImport the `<MetadataProvider>` to wrap any components that require NFT metadata. Typically you'll add this to an outer component like NextJS `_app.tsx`, but we recommend adding the context provider as deep in your React component tree as possible for best performance.  \n\n```jsx\n  // _app.tsx\n  import type { AppProps } from 'next/app';\n  import { MetadataProvider } from '@0xessential/use-many-indexers';\n  import React from 'react';\n\n  function MyApp({ Component, pageProps }: AppProps) {\n    return (      \n      <MetadataProvider>\n        <Component {...pageProps} />\n      </MetadataProvider>\n    );\n  }\n\n  export default MyApp;\n```\n\n### Configure\n\nEach supported indexer provides a configuration function that accepts arguments for authentication and network support. You must pass at least one indexer to the `<MetadataProvider>`.\n\nIndexers will be queried in the order you provide them in MetadataProvider configuration.\n\nWe suggest working bottom up, first implementing the `rpc` indexer - this fallback fetches the `tokenURI` and then metadata directly from a contract. Then add configuration above until you're happy with coverage.\n\n#### rpc\n\nThe `rpc` indexer provides a fallback that fetches an NFT's `tokenURI` from the NFT contract via an RPC provider. The user's browser then fetches metadata from the `tokenURI`.\n\nThis should be included last in your array of indexers as a final fallback, but is a sensible configuration to start with.\n\nThe `rpc` configuration expects an RPC URL for each network you support in your dapp, plus a function for resolving `ipfs://` protocol URIs to an `https` protocol URL.\n\n```jsx\n  //...\n  import { MetadataProvider, rpc } from '@0xessential/use-many-indexers';\n  //...\n\n  function MyApp({ Component, pageProps }: AppProps) {\n    return (      \n      <MetadataProvider indexers={[\n        rpc(\n          {\n            '1': process.env.MAINNET_RPC_URL,\n            '137': process.env.POLYGON_RPC_URL\n          },\n          (hash) => url.replace('ipfs://', 'https://ipfs.io/ipfs/')\n        )\n      ]}>\n        <Component {...pageProps} />\n      </MetadataProvider>\n    );\n  }\n\n  return MyApp;\n```\n\n#### Infura\n\nThe `infura` indexer provides a fallback that fetches an NFT's `tokenURI` from the NFT contract using an Infura RPC URL.\n\nIf you're an Infura customer, this option is a bit simpler than the `rpc` fallback - just provide your Infura API key and the package will query the correct network.\n\nYou'll also need to provide a function to resolve IPFS URIs like in the `rpc` indexer.\n\n```jsx\n  //...\n  import { MetadataProvider, infura } from '@0xessential/use-many-indexers';\n  import { ipfs } from '~utils/network.ts';\n  //...\n\n  function MyApp({ Component, pageProps }: AppProps) {\n    return (      \n      <MetadataProvider indexers={[\n        infura(\n          process.env.INFURA_API_KEY,\n          (hash) => ipfs(hash),\n        )\n      ]}>\n        <Component {...pageProps} />\n      </MetadataProvider>\n    );\n  }\n\n  return MyApp;\n```\n\n#### Zora\n\nZora supports Ethereuem Mainnet and Goerli Testnet. An API key is optional. Coverage is very good but supports limited networks.\n\n```jsx\n  //...\n  import { MetadataProvider, zora } from '@0xessential/use-many-indexers';\n  //...\n\n  function MyApp({ Component, pageProps }: AppProps) {\n    return (      \n      <MetadataProvider indexers={[\n        zora(process.env.ZORA_API_KEY_IS_OPTIONAL)\n      ]}>\n        <Component {...pageProps} />\n      </MetadataProvider>\n    );\n  }\n\n  return MyApp;\n```\n\n#### Alchemy\n\nAlchemy requires an API key per network your dApp supports. Configuration is passed as a nested object - string keys for each chain ID you support, and an object value with `apiKey` and `network` keys.\n\n```jsx\n  //...\n  import { MetadataProvider, alchemy } from '@0xessential/use-many-indexers';\n  import { Network } from 'alchemy-sdk';\n  //...\n\n  function MyApp({ Component, pageProps }: AppProps) {\n    return (      \n      <MetadataProvider indexers={[\n        alchemy({\n          '1': {\n            apiKey: process.env.ALCHEMY_ETH_MAINNET_API_KEY,\n            network: Network.ETH_MAINNET,\n          },\n          '137': {\n            apiKey: process.env.ALCHEMY_POLYGON_MAINNET_API_KEY,\n            network: Network.MATIC_MAINNET,\n          }\n        })\n      ]}>\n        <Component {...pageProps} />\n      </MetadataProvider>\n    );\n  }\n\n  return MyApp;\n```\n\n#### Center\n\n[Center](https://center.dev/) supports Ethereuem and Polygon mainnets. An API key is required but works with every Center-supported chain.\n\n```jsx\n  //...\n  import { MetadataProvider, center } from '@0xessential/use-many-indexers';\n  //...\n\n  function MyApp({ Component, pageProps }: AppProps) {\n    return (      \n      <MetadataProvider indexers={[\n        center(process.env.CENTER_API_KEY)\n      ]}>\n        <Component {...pageProps} />\n      </MetadataProvider>\n    );\n  }\n\n  return MyApp;\n```\n\n## Usage\n\nOnce your `<MetadataProvider>` is configured you're ready to start catching missing metadata throughout your dapp.\n\nThe library provides a \"lazy hook\" `useMetadataRescue` that returns a callable function should you need to rescue metadata.\n\nConsider the following example, a React component for an NFT Image based on `next/image`.\n\nRemember that this library is intended to be used as a fallback for your primary indexing service.\n\nHere we build a React component that expects an `nft` prop that includes `contractAddress`, `tokenId` and hopefully valid `imageUrl` values.\n\n```ts\nimport { useMetadataRescue } from '@0xessential/use-many-indexers';\nimport Image from 'next/image';\nimport React, { useEffect, useState } from 'react';\n\nconst NFTImage = ({\n  nft,\n  alt,\n  width,\n  height,\n}: {\n  alt?: string;\n  width: number;\n  height: number;\n  nft: { \n    tokenId: string;\n    contractAddress: string;\n    imageUrl: string;\n  };\n}) => {\n  const [src, setSrc] = useState<string>(nft.imageUrl || '/placeholder.png');\n  const rescueMetadata = useMetadataRescue();\n\n  useEffect(() => {\n    if (!nft.imageUrl) {\n      const rescue = async () => {\n        const md = await rescueMetadata({\n          chainId: '1',\n          contractAddress: nft.contractAddress,\n          tokenId: nft.tokenId,\n        });\n        md?.imageUrl && setSrc(md?.imageUrl);\n      };\n      rescue();\n    }\n  }, []);\n\n  return <Image src={src} alt={alt} width={width} height={height} />;\n};\n\nexport default NFTImage;\n```\n\nIf your primary metadata indexing service fails to provide an image URL, this component will query each configured indexer in order until a valid response is returned.\n","readmeFilename":"README.md"}