diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..85f8ece --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +# Add files here to ignore them from prettier formatting + +/dist +/coverage + +/.nx/cache \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..b51b1fa --- /dev/null +++ b/.prettierrc @@ -0,0 +1,11 @@ +{ + "trailingComma": "all", + "singleQuote": true, + "bracketSpacing": true, + "bracketSameLine": false, + "parser": "typescript", + "printWidth": 100, + "tabWidth": 2, + "useTabs": false, + "semi": true +} diff --git a/package.json b/package.json index 1b83370..ab1561e 100644 --- a/package.json +++ b/package.json @@ -40,7 +40,7 @@ "glob": "^10.3.10", "happy-dom": "^13.1.4", "jsdom": "^23.2.0", - "prettier": "^2.8.5", + "prettier": "^2.8.8", "simple-git-hooks": "^2.9.0", "ts-node": "10.9.1", "typescript": "5.4.0-dev.20240116", diff --git a/packages/nft-renderer/README.md b/packages/nft-renderer/README.md index abd83d0..df8ab2c 100644 --- a/packages/nft-renderer/README.md +++ b/packages/nft-renderer/README.md @@ -13,27 +13,60 @@ pnpm install @rmrk-team/nft-renderer ## Usage ```tsx -import { Address } from "viem"; +import React from "react"; +import type {Address} from "viem"; +import { + NETWORK_CONTRACTS_PROPS, + RMRKUtilityContracts, +} from "@rmrk-team/rmrk-evm-utils"; +import {RMRKContextProvider} from "@rmrk-team/rmrk-hooks"; +import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; +import {WagmiProvider} from "wagmi"; +import {hardhat} from "wagmi/chains"; -export const NftRendererWrapper -({ - chainId, contractAddress, tokenId +const queryClient = new QueryClient(); + +// You can pass custom utility contracts to the RMRKContextProvider +const customUtilityContracts = { + [hardhat.id]: { + [NETWORK_CONTRACTS_PROPS.RMRKEquipRenderUtils]: "0x00", + [NETWORK_CONTRACTS_PROPS.RMRKBulkWriter]: "0x00", + [NETWORK_CONTRACTS_PROPS.RMRKCollectionUtils]: "0x00", + [NETWORK_CONTRACTS_PROPS.RMRKCatalogUtils]: "0x00", + }, +} satisfies RMRKUtilityContracts; + +const rmrkConfig = { + utilityContracts: customUtilityContracts, +}; + +export const NftRendererWrapper = ({ + contractAddress, + tokenId, }: { - chainId: number, contractAddress: Address, tokenId: bigint + chainId: number; + contractAddress: Address; + tokenId: bigint; }) => { - return ( - - - } - /> + return ( + + + + + + } + /> - - ); -} + + + + + ); +}; ``` ## Building diff --git a/packages/nft-renderer/src/components/nft-renderer.tsx b/packages/nft-renderer/src/components/nft-renderer.tsx index acf5395..799035a 100644 --- a/packages/nft-renderer/src/components/nft-renderer.tsx +++ b/packages/nft-renderer/src/components/nft-renderer.tsx @@ -1,15 +1,10 @@ import { MultiLayer2DRenderer } from '@rmrk-team/rmrk-2d-renderer'; -import { - RMRKCatalogImpl, - RMRKEquippableImpl, - mapChainIdToNetwork, -} from '@rmrk-team/rmrk-evm-utils'; +import { RMRKCatalogImpl, RMRKEquippableImpl } from '@rmrk-team/rmrk-evm-utils'; import { useFetchIpfsMetadata, useGetAssetData, useGetComposedState, useGetInterfaceSupport, - useRMRKConfig, } from '@rmrk-team/rmrk-hooks'; import React, { useEffect, useRef, useState } from 'react'; import { css } from 'styled-system/css'; @@ -20,6 +15,57 @@ import type { Chain } from 'wagmi/chains'; import '../styles/index.css'; import type { RenderPart } from '../types/types.js'; +const useIsAddressAContract = ({ + address, + chainId, + onError, +}: { + address: Address; + chainId: Chain['id']; + onError?: (error: Error) => void; +}) => { + const publicClient = usePublicClient({ + chainId, + }); + + const [isContract, setIsContract] = useState(); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(); + + useEffect(() => { + const isValidAddress = isAddress(address); + + if (isValidAddress) { + (async () => { + setIsLoading(true); + const isContract = await publicClient.getBytecode({ + address, + }); + setIsContract(!!isContract); + setIsLoading(false); + if (!isContract) { + setError(new Error(`Address ${address} is not a contract`)); + } + })(); + } else { + setError(new Error(`Address ${address} is not a valid address`)); + } + }, [address, publicClient]); + + useEffect(() => { + if (error && onError) { + onError(error); + } + }, [error, onError]); + + return { + isContract, + isLoading, + error, + isError: !!error, + }; +}; + type NFTRenderer = { chainId: Chain['id']; contractAddress: Address; @@ -30,42 +76,31 @@ type NFTRenderer = { }; /** - * @description To use this component, make sure you have a WagmiProvider wrapped it + * Renders a multi layered RMRK NFT based on the provided parameters. + * + * @param {Object} options - The options for rendering the NFT. + * @param {string} options.chainId - The chain ID of the blockchain network. + * @param {string} options.contractAddress - The address of the contract containing the NFT. + * @param {string} options.tokenId - The ID of the token to render. + * @param {ReactNode} options.loader - The loader component to display while the NFT is loading. + * @param {Function} options.onError - The callback function to handle errors. */ export function NFTRenderer({ chainId, contractAddress, tokenId, - advancedMode, loader, onError, }: NFTRenderer) { const rendererContainerRef = useRef(null); const tokenIdBigint = BigInt(tokenId); - const network = mapChainIdToNetwork(chainId); - - const config = useRMRKConfig(); - - const publicClient = usePublicClient({ - chainId, - }); - const isValidAddress = isAddress(contractAddress); - const [isContract, setIsContract] = useState(); - const [isGettingIsContract, setIsGettingIsContract] = useState(true); - - useEffect(() => { - (async () => { - if (isValidAddress) { - setIsGettingIsContract(true); - const isContract = await publicClient.getBytecode({ - address: contractAddress, - }); - setIsContract(!!isContract); - setIsGettingIsContract(false); - } - })(); - }, [contractAddress, isValidAddress, publicClient]); + const { + isContract, + isLoading: isLoadingIsContract, + isError: isErrorIsContract, + error: errorIsContract, + } = useIsAddressAContract({ address: contractAddress, chainId }); const { isLoading: isLoadingGetInterfaceSupport, @@ -129,6 +164,7 @@ export function NFTRenderer({ data: catalogType, isLoading: loadingCatalogType, error: errorCatalogType, + isError: isErrorCatalogType, } = useReadContract({ address: catalogAddress, abi: RMRKCatalogImpl, @@ -179,9 +215,15 @@ export function NFTRenderer({ isErrorTokenUri || isErrorTokenMetadata || isErrorPrimaryAsset || - isErrorComposableState; + isErrorComposableState || + isErrorIsContract || + isErrorCatalogType; - const error = errorComposableState || errorPrimaryAsset; + const error = + errorComposableState || + errorPrimaryAsset || + errorIsContract || + errorCatalogType; useEffect(() => { if (error && onError) { @@ -204,7 +246,7 @@ export function NFTRenderer({ } const isLoading = - isGettingIsContract || + isLoadingIsContract || isLoadingTokenUri || isLoadingPrimaryAsset || isLoadingTokenMetadata || @@ -241,32 +283,6 @@ export function NFTRenderer({ ) : ( <> - {isValidAddress === false ?

Invalid address

: null} - {isValidAddress && !isContract ?

Not a contract

: null} - {isContract && isErrorTokenUri ?

Failed to get NFT data

: null} - - {advancedMode ? ( - <> -

- Token {tokenId.toString()} on {network} in {contractAddress} -

- {composableState ? ( -
- <> -

Is Equippable

-

metadataURI: {assetMetadataUri}

-

groupId: {equippableGroupId?.toString()}

-

catalog: {catalogAddress}

- -
- ) : primaryAsset ? ( -

metadataURI: {primaryAsset.metadataUri}

- ) : tokenUri ? ( -

metadataURI: {tokenUri}

- ) : null} - - ) : null} - {renderParts && renderParts.length > 0 ? ( { - return ( - - - {children} - - - ); -}; diff --git a/packages/nft-renderer/src/components/wagmi-provider.tsx b/packages/nft-renderer/src/components/wagmi-provider.tsx deleted file mode 100644 index ccd683d..0000000 --- a/packages/nft-renderer/src/components/wagmi-provider.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from 'react'; -import { WagmiConfig } from 'wagmi'; -import { wagmiConfig } from '../lib/web3/wagmi-config.js'; - -export const WagmiProvider = ({ children }: { children: React.ReactNode }) => { - return {children}; -}; diff --git a/packages/nft-renderer/src/lib/web3/wagmi-config.ts b/packages/nft-renderer/src/lib/web3/wagmi-config.ts deleted file mode 100644 index bebb03b..0000000 --- a/packages/nft-renderer/src/lib/web3/wagmi-config.ts +++ /dev/null @@ -1,57 +0,0 @@ -// import { WALLET_CONNECT_PROJECT_ID } from './consts.js'; -// import { getDefaultWallets } from '@rainbow-me/rainbowkit'; -import type { Transport } from 'viem'; -import { http, createConfig } from 'wagmi'; -import type { Chain } from 'wagmi/chains'; -import { - astar, - base, - baseSepolia, - hardhat, - mainnet, - moonbaseAlpha, - moonbeam, - polygon, - polygonMumbai, - sepolia, -} from 'wagmi/chains'; - -const productionChains = [moonbeam, mainnet, polygon, base, astar] as const; -const testnetChains = [ - moonbaseAlpha, - moonbeam, - sepolia, - mainnet, - polygonMumbai, - baseSepolia, - hardhat, -] as const; -export const allSupportedChains: readonly [Chain, ...Chain[]] = [ - ...productionChains, - ...testnetChains, -]; - -// const { -// chains: wagmiChains, -// publicClient, -// webSocketPublicClient, -// } = configureChains({chains: allSupportedChains, transports: []}}); - -export const chains = allSupportedChains; - -// const { connectors } = getDefaultWallets({ -// appName: 'Lightm RMRK composable NFT renderer', -// projectId: WALLET_CONNECT_PROJECT_ID, -// chains, -// }); - -const transports: Record = {}; - -for (const chain of productionChains) { - transports[chain.id] = http(); -} - -export const wagmiConfig = createConfig({ - chains, - transports, -}); diff --git a/packages/rmrk-hooks/README.md b/packages/rmrk-hooks/README.md index 4f262c4..a00340c 100644 --- a/packages/rmrk-hooks/README.md +++ b/packages/rmrk-hooks/README.md @@ -7,12 +7,112 @@ React hooks for working with [RMRK](https://evm.rmrk.app) EVM NFTs. ## Installation ```bash - +pnpm install @rmrk-team/rmrk-hooks ``` ## Usage + ```tsx +import React from "react"; +import type {Address} from "viem"; +import { + NETWORK_CONTRACTS_PROPS, + RMRKUtilityContracts, +} from "@rmrk-team/rmrk-evm-utils"; +import {RMRKContextProvider} from "@rmrk-team/rmrk-hooks"; +import {QueryClient, QueryClientProvider} from "@tanstack/react-query"; +import {WagmiProvider} from "wagmi"; +import {hardhat} from "wagmi/chains"; + +const queryClient = new QueryClient(); + +// You can pass custom utility contracts to the RMRKContextProvider +const customUtilityContracts = { + [hardhat.id]: { + [NETWORK_CONTRACTS_PROPS.RMRKEquipRenderUtils]: "0x00", + [NETWORK_CONTRACTS_PROPS.RMRKBulkWriter]: "0x00", + [NETWORK_CONTRACTS_PROPS.RMRKCollectionUtils]: "0x00", + [NETWORK_CONTRACTS_PROPS.RMRKCatalogUtils]: "0x00", + }, +} satisfies RMRKUtilityContracts; + +const rmrkConfig = { + utilityContracts: customUtilityContracts, +}; + +export const Container = ({ + contractAddress, + tokenId, + }: { + chainId: number; + contractAddress: Address; + tokenId: bigint; + children: React.ReactNode; +}) => { + return ( + + + + {children} + + + + ); +}; + +``` + +```tsx +import { + useFetchIpfsMetadata, + useGetAssetData, + useGetComposedState, + useGetInterfaceSupport, +} from '@rmrk-team/rmrk-hooks'; + +type Props = { + contractAddress: Address; + tokenId: bigint; + chainId: number; +} + +export const Example = ({contractAddress, tokenId, chainId}: Props) => { + const { + isLoading: isLoadingComposableState, + isError: isErrorComposableState, + error: errorComposableState, + data: composableState, + } = useGetComposedState( + { + tokenId, + chainId, + contractAddress, + } + ); + + const { + fixedPartsWithMetadatas, + slotPartsWithMetadatas, + equippableGroupId, + assetMetadataUri, + catalogAddress, + } = composableState; + + console.log(composableState) + return null; +} +``` + +```tsx + +export const App = () => { + return ( + + + + ) +} ``` diff --git a/packages/rmrk-hooks/src/lib/hooks/use-fetch-ipfs-metadata.ts b/packages/rmrk-hooks/src/lib/hooks/use-fetch-ipfs-metadata.ts index 6ab0819..0e53798 100644 --- a/packages/rmrk-hooks/src/lib/hooks/use-fetch-ipfs-metadata.ts +++ b/packages/rmrk-hooks/src/lib/hooks/use-fetch-ipfs-metadata.ts @@ -9,6 +9,15 @@ type Props = { metadataUri: string | undefined; ipfsGatewayUrl?: string }; type Options = { enabled?: boolean }; +/** + * Fetches IPFS metadata and returns the result using React Query's `useQuery` hook. + * + * @param {Object} props - The props object. + * @param {string} props.metadataUri - The URI of the IPFS metadata to fetch. + * @param {string} props.ipfsGatewayUrl - The URL of the IPFS gateway to use for fetching. + * @param {Object} [options] - The options object. + * @param {boolean} [options.enabled=true] - Indicates whether the fetch should be enabled. + */ export const useFetchIpfsMetadata = ( { metadataUri, ipfsGatewayUrl }: Props, options?: Options, diff --git a/packages/rmrk-hooks/src/lib/hooks/use-fetch-ipfs-metadatas.ts b/packages/rmrk-hooks/src/lib/hooks/use-fetch-ipfs-metadatas.ts index 649cd18..3eb6fec 100644 --- a/packages/rmrk-hooks/src/lib/hooks/use-fetch-ipfs-metadatas.ts +++ b/packages/rmrk-hooks/src/lib/hooks/use-fetch-ipfs-metadatas.ts @@ -13,6 +13,19 @@ type Props = { type Options = { enabled?: boolean }; +/** + * A custom hook that fetches IPFS metadata for the given URIs. + * @param {Object} props - The properties object. + * @param {Array} props.metadataUris - The URIs of the metadata to fetch. + * @param {string} props.ipfsGatewayUrl - The URL of the IPFS gateway to use. + * @param {Object} options - Optional options object. + * @param {boolean} options.enabled - Whether the hook is enabled or not. Defaults to true. + * @property {boolean} isLoading - Indicates if the metadata is currently being loaded. + * @property {boolean} isError - Indicates if an error occurred while fetching the metadata. + * @property {boolean} isFetching - Indicates if the metadata is currently being fetched. + * @property {Array|undefined} data - The fetched metadata. Undefined if metadataUris is not provided. + * @property {Function} refetch - A function to manually trigger a re-fetch of the metadata. + */ export const useFetchIpfsMetadatas = ( { metadataUris, ipfsGatewayUrl }: Props, options?: Options, diff --git a/packages/rmrk-hooks/src/lib/hooks/use-fetch-metadata-and-add-to-entities.ts b/packages/rmrk-hooks/src/lib/hooks/use-fetch-metadata-and-add-to-entities.ts index b5b8d38..8fb4e08 100644 --- a/packages/rmrk-hooks/src/lib/hooks/use-fetch-metadata-and-add-to-entities.ts +++ b/packages/rmrk-hooks/src/lib/hooks/use-fetch-metadata-and-add-to-entities.ts @@ -16,6 +16,16 @@ type EntityWithMetadata = T & { metadata?: Metadata; }; +/** + * Uses the `useFetchIpfsMetadatas` hook to fetch metadata for a given array of entities and adds the metadata to each entity. + * + * @template T The type of the entities. + * @param {Arguments} args The arguments for the fetch function. + * @param {Options} [options] The options for the fetch function. + * @param {T[]} [entities] The array of entities to add metadata to. + * @returns {{ isLoading: boolean, isError: boolean, isFetching: boolean, refetch: () => void, data: EntityWithMetadata[] | undefined }} The result object containing properties indicating + * the loading, error, and data status, along with the refetch function and the array of entities with metadata. + */ export const useFetchMetadataAndAddToEntities = ( args: Arguments, options?: Options, diff --git a/packages/rmrk-hooks/src/lib/hooks/use-fetch-metadata-and-add-to-entity.ts b/packages/rmrk-hooks/src/lib/hooks/use-fetch-metadata-and-add-to-entity.ts index e44c4f1..4f1e692 100644 --- a/packages/rmrk-hooks/src/lib/hooks/use-fetch-metadata-and-add-to-entity.ts +++ b/packages/rmrk-hooks/src/lib/hooks/use-fetch-metadata-and-add-to-entity.ts @@ -12,6 +12,14 @@ interface WithMetadata { metadata?: Metadata; } +/** + * Fetches metadata using IPFS and adds it to the provided entity. + * + * @template T - The type of entity that the metadata will be added to. It should have a 'metadata' property. + * @param {Arguments} args - The arguments used for fetching metadata. + * @param {Options} [options] - Optional additional options for fetching metadata. + * @param {T} [entity] - Optional entity object where the fetched metadata will be added. + */ export const useFetchMetadataAndAddToEntity = ( args: Arguments, options?: Options, diff --git a/packages/rmrk-hooks/src/lib/hooks/use-get-asset-data.ts b/packages/rmrk-hooks/src/lib/hooks/use-get-asset-data.ts index 82ca866..a803db4 100644 --- a/packages/rmrk-hooks/src/lib/hooks/use-get-asset-data.ts +++ b/packages/rmrk-hooks/src/lib/hooks/use-get-asset-data.ts @@ -18,6 +18,12 @@ type Options = { enabledMetadataFetch?: boolean; }; +/** + * Retrieves asset data based on the provided arguments and options. + * + * @param {Arguments} args - The arguments for retrieving asset data. + * @param {Options} [options] - The options for retrieving asset data. + */ export const useGetAssetData = (args: Arguments, options?: Options) => { const { contractAddress, diff --git a/packages/rmrk-hooks/src/lib/hooks/use-get-composed-state.ts b/packages/rmrk-hooks/src/lib/hooks/use-get-composed-state.ts index 4e41b0e..012b9e7 100644 --- a/packages/rmrk-hooks/src/lib/hooks/use-get-composed-state.ts +++ b/packages/rmrk-hooks/src/lib/hooks/use-get-composed-state.ts @@ -19,6 +19,18 @@ type Options = { enabledMetadataFetch?: boolean; }; +/** + * Retrieves the composed state of an asset with the given arguments. + * @param {object} arguments - The arguments required to fetch the composed state. + * @param {string} arguments.contractAddress - The contract address of the asset. + * @param {boolean} arguments.supportsEquippableInterface - Indicates whether the asset supports the equippable interface. + * @param {boolean} arguments.supportsMultiAssetInterface - Indicates whether the asset supports the multi-asset interface. + * @param {string} arguments.assetId - The ID of the asset. + * @param {string} arguments.chainId - The chain ID of the blockchain network. + * @param {object} options - Optional configuration options. + * @param {boolean} options.enabled - Indicates whether the fetch is enabled. + * @param {boolean} options.enabledMetadataFetch - Indicates whether metadata fetch is enabled. + */ export const useGetComposedState = ( { contractAddress, diff --git a/packages/rmrk-hooks/src/lib/hooks/use-get-interface-support.ts b/packages/rmrk-hooks/src/lib/hooks/use-get-interface-support.ts index 46eaa43..21b0c30 100644 --- a/packages/rmrk-hooks/src/lib/hooks/use-get-interface-support.ts +++ b/packages/rmrk-hooks/src/lib/hooks/use-get-interface-support.ts @@ -16,7 +16,10 @@ type Options = { const ONE_DAY = 24 * 60 * 60 * 1000; /** - * Used to get extended information about a specified collection. + * Retrieves interface support information for a given contract address and chain ID. + * + * @param {Arguments} args - The arguments object containing the contract address and chain ID. + * @param {Options} [options] - The options object containing additional configuration. */ export const useGetInterfaceSupport = (args: Arguments, options?: Options) => { const { contractAddress, chainId } = args; diff --git a/packages/rmrk-hooks/src/lib/hooks/use-get-token-asset-by-id.ts b/packages/rmrk-hooks/src/lib/hooks/use-get-token-asset-by-id.ts index 58bf087..97cb066 100644 --- a/packages/rmrk-hooks/src/lib/hooks/use-get-token-asset-by-id.ts +++ b/packages/rmrk-hooks/src/lib/hooks/use-get-token-asset-by-id.ts @@ -18,6 +18,20 @@ type Options = { enabledMetadataFetch?: boolean; }; +/** + * Retrieves token asset by its ID. + * + * @param {Arguments} args - The arguments required to retrieve the token asset. + * @param {Options} [options] - The options for retrieving the token asset. + * @returns {{ + * isLoading: boolean; + * isError: boolean; + * error: Error | null; + * isFetching: boolean; + * refetch: () => void; + * assetById: RMRKAssetExtended | undefined; + * }} The token asset object. + */ export const useGetTokenAssetById = ( args: Arguments, options?: Options, diff --git a/packages/rmrk-hooks/src/lib/hooks/use-get-token-primary-asset.ts b/packages/rmrk-hooks/src/lib/hooks/use-get-token-primary-asset.ts index f583557..1e8f3c2 100644 --- a/packages/rmrk-hooks/src/lib/hooks/use-get-token-primary-asset.ts +++ b/packages/rmrk-hooks/src/lib/hooks/use-get-token-primary-asset.ts @@ -18,6 +18,20 @@ type Options = { enabledMetadataFetch?: boolean; }; +/** + * Retrieves the primary asset for a given token using various parameters. + * + * @param {Arguments} args - The arguments for retrieving the primary asset. + * @param {Options} [options] - The options for customizing the retrieval. + * @returns {{ + * isLoading: boolean; + * isFetching: boolean; + * isError: boolean; + * error: Error | null; + * refetch: () => void; + * primaryAsset: RMRKAssetExtended | undefined; + * }} + */ export const useGetTokenPrimaryAsset = ( args: Arguments, options?: Options, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03e369f..ea29b11 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -48,7 +48,7 @@ importers: specifier: ^23.2.0 version: 23.2.0 prettier: - specifier: ^2.8.5 + specifier: ^2.8.8 version: 2.8.8 simple-git-hooks: specifier: ^2.9.0