From f1fe8c01eef68524ff26e6d20e089c8025657f8d Mon Sep 17 00:00:00 2001 From: Lucas Werey Date: Tue, 10 Sep 2024 10:12:25 +0200 Subject: [PATCH] :sparkles:feat(ui): add simplehash calls for ordis --- .changeset/dry-parents-breathe.md | 7 + .../Ordinals/components/RareSats/Item.tsx | 13 +- .../Ordinals/components/RareSats/helpers.ts | 150 ++++++------------ .../Ordinals/components/RareSats/index.tsx | 55 ++++--- .../components/RareSats/useRareSatsModel.ts | 20 +-- .../Ordinals/screens/Account/index.tsx | 8 +- .../Collectibles/hooks/useFetchOrdinals.ts | 18 +++ .../Collectibles/types/Collectibles.ts | 2 + .../features/Collectibles/types/Ordinals.ts | 19 ++- .../features/Collectibles/types/RareSats.ts | 8 - .../Collectibles/types/enum/Chains.ts | 5 + .../Collectibles/types/enum/Collectibles.ts | 6 + .../Collectibles/types/enum/DetailDrawer.ts | 1 + .../src/renderer/screens/account/index.tsx | 3 +- libs/live-nft-react/src/helpers/ordinals.ts | 87 ++++++++++ libs/live-nft-react/src/hooks/types.ts | 49 ++++++ .../src/hooks/useFetchOrdinals.ts | 39 +++++ libs/live-nft-react/src/index.ts | 2 + libs/live-nft-react/src/queryKeys.ts | 1 + libs/live-nft/src/api/types.ts | 33 ++++ 20 files changed, 369 insertions(+), 157 deletions(-) create mode 100644 .changeset/dry-parents-breathe.md create mode 100644 apps/ledger-live-desktop/src/newArch/features/Collectibles/hooks/useFetchOrdinals.ts create mode 100644 libs/live-nft-react/src/helpers/ordinals.ts create mode 100644 libs/live-nft-react/src/hooks/useFetchOrdinals.ts diff --git a/.changeset/dry-parents-breathe.md b/.changeset/dry-parents-breathe.md new file mode 100644 index 000000000000..42c1cc409974 --- /dev/null +++ b/.changeset/dry-parents-breathe.md @@ -0,0 +1,7 @@ +--- +"ledger-live-desktop": patch +"@ledgerhq/live-nft-react": patch +"@ledgerhq/live-nft": patch +--- + +Plug the front with simplehash api for the rare sats table and inscriptions table diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/Item.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/Item.tsx index 6845c31536f4..52b7b335bba0 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/Item.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/Item.tsx @@ -2,21 +2,14 @@ import React from "react"; import RowLayout from "LLD/features/Collectibles/Ordinals/components/RareSats/RowLayout"; import IconContainer from "LLD/features/Collectibles/components/Collection/TableRow/IconContainer"; import TokenTitle from "LLD/features/Collectibles/components/Collection/TableRow/TokenTitle"; -import { RareSat } from "LLD/features/Collectibles/types/Ordinals"; import { Text, Flex } from "@ledgerhq/react-ui"; +import { RareSat } from "LLD/features/Collectibles/types/Ordinals"; -const Item = ({ - icons, - name, - year, - count, - utxo_size, - isMultipleRow, -}: RareSat & { isMultipleRow: boolean }) => { +const Item = ({ icons, displayed_name, year, count, utxo_size, isMultipleRow }: RareSat) => { const firstColumn = ( {icons && } - + ); const secondColumn = ( diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts index 23e7080c2dea..23f7b0027b39 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/helpers.ts @@ -1,107 +1,59 @@ import { mappingKeysWithIconAndName } from "../Icons"; -import { MappingKeys } from "LLD/features/Collectibles/types/Ordinals"; import { IconProps } from "LLD/features/Collectibles/types/Collection"; -import { RareSat } from "LLD/features/Collectibles/types/Ordinals"; -import { - Satributes, - Subrange, - SatRange, - MockedRareSat, - Sat, - Icons, -} from "LLD/features/Collectibles/types/RareSats"; - -export const processSatType = ( - type: string, - satributes: Satributes, - icons: Icons, - displayNames: string[], - totalCount: number, -) => { - const attribute = satributes[type as MappingKeys]; - if (attribute && attribute.count) { - displayNames.push(type); - if (mappingKeysWithIconAndName[type as MappingKeys]) { - icons[type] = mappingKeysWithIconAndName[type as MappingKeys].icon; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; +import { SimpleHashNftWithIcons, RareSat } from "../../../types/Ordinals"; + +export function matchCorrespondingIcon(rareSats: SimpleHashNft[]): SimpleHashNftWithIcons[] { + return rareSats.map(rareSat => { + const iconKeys: string[] = []; + if (rareSat.name) { + iconKeys.push(rareSat.name.toLowerCase().replace(" ", "_")); } - totalCount = attribute.count; - } - return { displayNames, totalCount }; -}; - -export const processSatTypes = (satTypes: string[], satributes: Satributes) => { - let displayNames: string[] = []; - let totalCount = 0; - const icons: { [key: string]: ({ size, color, style }: IconProps) => JSX.Element } = {}; - - satTypes.forEach(type => { - const result = processSatType(type, satributes, icons, displayNames, totalCount); - displayNames = result.displayNames; - totalCount = result.totalCount; - }); - - return { displayNames, totalCount, icons }; -}; -export const processSubrange = ( - subrange: Subrange, - satributes: Satributes, - year: string, - value: number, -) => { - const { sat_types } = subrange; - const { displayNames, totalCount, icons } = processSatTypes(sat_types, satributes); - - const name = displayNames - .map(dn => mappingKeysWithIconAndName[dn.toLowerCase().replace(" ", "_") as MappingKeys]?.name) - .filter(Boolean) - .join(" / "); + if (rareSat.extra_metadata?.utxo_details?.satributes) { + iconKeys.push(...Object.keys(rareSat.extra_metadata.utxo_details.satributes)); + } - return { - count: totalCount.toString() + (totalCount === 1 ? " sat" : " sats"), - display_name: displayNames.join(" / "), - year, - utxo_size: value.toString(), - icons, - name, - }; -}; + const icons = iconKeys + .map( + iconKey => + mappingKeysWithIconAndName[iconKey as keyof typeof mappingKeysWithIconAndName]?.icon, + ) + .filter(Boolean) as (({ size, color, style }: IconProps) => JSX.Element)[]; -export const processSatRanges = (satRanges: SatRange[], satributes: Satributes) => { - return satRanges.flatMap(range => { - const { year, value, subranges } = range; - return subranges.flatMap(subrange => processSubrange(subrange, satributes, year, value)); + return { ...rareSat, icons }; }); -}; - -export const processRareSat = (sat: Sat) => { - const { extra_metadata } = sat; - const satributes = extra_metadata.utxo_details.satributes as Satributes; - const satRanges = extra_metadata.utxo_details.sat_ranges; - return processSatRanges(satRanges, satributes); -}; - -export const processRareSats = (rareSats: MockedRareSat[]) => { - return rareSats.flatMap(item => item.nfts.flatMap(processRareSat)); -}; - -export const groupRareSats = (processedRareSats: RareSat[]) => { - return processedRareSats.reduce( - (acc, sat) => { - if (!acc[sat.utxo_size]) { - acc[sat.utxo_size] = []; - } - acc[sat.utxo_size].push(sat); - return acc; - }, - {} as Record, - ); -}; +} + +export function createRareSatObject( + rareSats: Record, +): Record { + const result: Record = {}; + + for (const [key, value] of Object.entries(rareSats)) { + result[key] = value.map(rareSat => { + const { icons, extra_metadata } = rareSat; + const year = extra_metadata?.utxo_details?.sat_ranges?.[0]?.year || ""; + const displayed_name = + Object.values(extra_metadata?.utxo_details?.satributes || {}) + .map(attr => attr.display_name) + .join(" / ") || ""; + const utxo_size = extra_metadata?.utxo_details?.sat_ranges?.[0]?.value || ""; + const name = rareSat.name || ""; + const count = extra_metadata?.utxo_details?.value || 0; + const isMultipleRow = value.length > 1; + + return { + year, + displayed_name, + utxo_size, + name, + count: `${count} ${count > 1 ? "sats" : "sat"}`, + isMultipleRow, + icons, + }; + }); + } -export const finalizeRareSats = (groupedRareSats: Record) => { - return Object.entries(groupedRareSats).map(([utxo_size, sats]) => ({ - utxo_size, - sats, - isMultipleRow: sats.length > 1, - })); -}; + return result; +} diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx index 838d4a0c957f..c9ef9f1f7544 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/index.tsx @@ -4,40 +4,57 @@ import TableContainer from "~/renderer/components/TableContainer"; import TableHeader from "LLD/features/Collectibles/components/Collection/TableHeader"; import Item from "./Item"; import { TableHeaderTitleKey } from "LLD/features/Collectibles/types/Collection"; -import { Box, Flex } from "@ledgerhq/react-ui"; +import { Box, Flex, InfiniteLoader } from "@ledgerhq/react-ui"; import { TableHeader as TableHeaderContainer } from "./TableHeader"; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; +import { Status } from "LLD/features/Collectibles/types/Collectibles"; +import { TanStackStatus } from "../../../types/enum/Collectibles"; -type ViewProps = ReturnType; +type ViewProps = ReturnType & { + status: Status; +}; + +type Props = { + rareSats: SimpleHashNft[]; + status: Status; +}; -function View({ rareSats }: ViewProps) { +function View({ rareSats, status }: ViewProps) { + const isLoading = status === TanStackStatus.Pending; + const hasRareSats = Object.values(rareSats).length > 0; + console.log("rareSats", rareSats); return ( + {isLoading && ( + + + + )} - {rareSats - ? rareSats.map(rareSatGroup => ( - - {rareSatGroup.sats.map(rareSat => ( - - ))} - - )) - : null} - {/** wait for design */} + {hasRareSats ? ( + Object.entries(rareSats).map(([key, rareSatGroup]) => ( + + {rareSatGroup.map((rareSat, index) => ( + 1} /> + ))} + + )) + ) : ( + + {"NOTHING TO SHOW WAITING FOR DESIGN"} + + )} ); } -const RareSats = () => { - return ; +const RareSats = ({ rareSats, status }: Props) => { + return ; }; export default RareSats; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts index 931c590a56d8..a59b1eafc054 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/components/RareSats/useRareSatsModel.ts @@ -1,12 +1,14 @@ -import { mockedRareSats } from "LLD/features/Collectibles/__integration__/mockedRareSats"; -import { processRareSats, groupRareSats, finalizeRareSats } from "./helpers"; +import { matchCorrespondingIcon, createRareSatObject } from "./helpers"; +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; +import { regroupRareSatsByContractAddress } from "@ledgerhq/live-nft-react"; -type RareSatsProps = {}; - -export const useRareSatsModel = (_props: RareSatsProps) => { - const processedRareSats = processRareSats(mockedRareSats); - const groupedRareSats = groupRareSats(processedRareSats); - const finalRareSats = finalizeRareSats(groupedRareSats); +type Props = { + rareSats: SimpleHashNft[]; +}; - return { rareSats: finalRareSats }; +export const useRareSatsModel = ({ rareSats }: Props) => { + const matchedRareSats = matchCorrespondingIcon(rareSats); + const regroupedRareSats = regroupRareSatsByContractAddress(matchedRareSats); + const rareSatsCreated = createRareSatObject(regroupedRareSats); + return { rareSats: rareSatsCreated }; }; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx index 30aed15cccc5..ab75286de0cc 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/Ordinals/screens/Account/index.tsx @@ -1,18 +1,20 @@ import React from "react"; -import { Account } from "@ledgerhq/types-live"; import Inscriptions from "../../components/Inscriptions"; import RareSats from "../../components/RareSats"; import { Flex } from "@ledgerhq/react-ui"; +import useFetchOrdinals from "LLD/features/Collectibles/hooks/useFetchOrdinals"; +import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types"; type Props = { - account: Account; + account: BitcoinAccount; }; const OrdinalsAccount: React.FC = ({ account }) => { + const { rareSats, status } = useFetchOrdinals({ account }); return ( - + ); }; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/hooks/useFetchOrdinals.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/hooks/useFetchOrdinals.ts new file mode 100644 index 000000000000..d203e233e420 --- /dev/null +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/hooks/useFetchOrdinals.ts @@ -0,0 +1,18 @@ +import { useFetchOrdinals as fetchOrdinalsFromSimpleHash } from "@ledgerhq/live-nft-react"; +import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types"; + +type Props = { + account: BitcoinAccount; +}; + +const useFetchOrdinals = ({ account }: Props) => { + const utxosAddresses = account.bitcoinResources?.utxos?.map(utxo => utxo.address).join(",") || ""; + const { rareSats, inscriptions, status } = fetchOrdinalsFromSimpleHash({ + addresses: utxosAddresses, + threshold: 0, + }); + + return { rareSats, inscriptions, status }; +}; + +export default useFetchOrdinals; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/Collectibles.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/Collectibles.ts index aa557daae387..44c7a683017c 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/Collectibles.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/Collectibles.ts @@ -7,3 +7,5 @@ export type BaseNftsProps = { nfts: (ProtoNFT | NFT)[]; account: Account; }; + +export type Status = "error" | "success" | "pending"; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/Ordinals.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/Ordinals.ts index 38ce93b53a2a..5e945e5e3b75 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/Ordinals.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/Ordinals.ts @@ -1,14 +1,17 @@ +import { SimpleHashNft } from "@ledgerhq/live-nft/api/types"; import { mappingKeysWithIconAndName } from "../Ordinals/components/Icons"; import { IconProps } from "./Collection"; export type MappingKeys = keyof typeof mappingKeysWithIconAndName; -export type RareSat = { - count: string; - display_name: string | string[]; +export interface RareSat { + displayed_name: string; + icons?: (({ size, color, style }: IconProps) => JSX.Element)[]; year: string; - utxo_size: string; - icons?: { [key: string]: ({ size, color, style }: IconProps) => JSX.Element }; - name: string; - isDoubleRow?: boolean; -}; + count: string; + utxo_size: string | number; + isMultipleRow: boolean; +} +export interface SimpleHashNftWithIcons extends SimpleHashNft { + icons?: (({ size, color, style }: IconProps) => JSX.Element)[]; +} diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/RareSats.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/RareSats.ts index 0aa47984190e..ad620f3b5af6 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/RareSats.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/RareSats.ts @@ -52,11 +52,3 @@ export type ExtraMetadata = { animation_original_url: string | null; metadata_original_url: string | null; }; - -export type Sat = { - extra_metadata: ExtraMetadata; -}; - -export type MockedRareSat = { - nfts: Sat[]; -}; diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/Chains.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/Chains.ts index 35253982236e..461cf1a3d153 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/Chains.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/Chains.ts @@ -11,3 +11,8 @@ export enum ChainsEnum { BITCOIN = "bitcoin", UTXO = "utxo", } + +export enum OrdinalsStandardEnum { + INSCRIPTIONS = "inscriptions", + RARE_SATS = "raresats", +} diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/Collectibles.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/Collectibles.ts index a4c730ef9630..64654ab97cc6 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/Collectibles.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/Collectibles.ts @@ -2,3 +2,9 @@ export enum CollectibleTypeEnum { NFT = "NFT", Ordinal = "Ordinal", } + +export enum TanStackStatus { + Error = "error", + Success = "success", + Pending = "pending", +} diff --git a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/DetailDrawer.ts b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/DetailDrawer.ts index da05e93321d9..e5bfbe7fbd4b 100644 --- a/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/DetailDrawer.ts +++ b/apps/ledger-live-desktop/src/newArch/features/Collectibles/types/enum/DetailDrawer.ts @@ -5,6 +5,7 @@ export enum FieldStatus { NoData = "nodata", Queued = "queued", } + export enum ItemType { Separator = "separator", ExternalLink = "external", diff --git a/apps/ledger-live-desktop/src/renderer/screens/account/index.tsx b/apps/ledger-live-desktop/src/renderer/screens/account/index.tsx index 26c74695c8c6..f5c1b1b850e1 100644 --- a/apps/ledger-live-desktop/src/renderer/screens/account/index.tsx +++ b/apps/ledger-live-desktop/src/renderer/screens/account/index.tsx @@ -44,6 +44,7 @@ import { useLocalizedUrl } from "~/renderer/hooks/useLocalizedUrls"; import { urls } from "~/config/urls"; import { CurrencyConfig } from "@ledgerhq/coin-framework/config"; import { useFeature } from "@ledgerhq/live-common/featureFlags/index"; +import { BitcoinAccount } from "@ledgerhq/coin-bitcoin/lib/types"; type Params = { id: string; @@ -220,7 +221,7 @@ const AccountPage = ({ ) ) : null} {isOrdinalsEnabled && account.type === "Account" && account.currency.id === "bitcoin" ? ( - + ) : null} {account.type === "Account" ? : null} { + if (nft.chain === OrdinalsChainsEnum.INSCRIPTIONS) accumulator.inscriptions.push(nft); + else accumulator.rareSats.push(nft); + return accumulator; + }, initialAccumulator); +} + +/** + * Processes an array of rareSats by removing the common rarity. + * @param rareSats - An array of rareSats to process. + * @returns An array of rareSats with the common rarity removed. + */ +export function removeCommonRareSats(rareSats: SimpleHashNft[]): SimpleHashNft[] { + return rareSats.filter(nft => { + const utxoDetails = nft.extra_metadata?.utxo_details; + if (utxoDetails) { + const satributes = utxoDetails.satributes; + const keys = Object.keys(satributes); + if (keys.length === 1 && keys[0] === RareSatRarity.COMMON) { + return false; + } else { + const { common, ...restSatributes } = satributes; + return { + ...nft, + extra_metadata: { + ...nft.extra_metadata, + utxo_details: { + ...utxoDetails, + satributes: restSatributes, + }, + }, + }; + } + } + return true; + }); +} +/** + * Processes an array of rareSats by regrouping them by Contract Address. + * @param rareSats - An array of rareSats to process. + * @returns An array of rareSats with the common rarity removed. + */ +export function regroupRareSatsByContractAddress( + rareSats: SimpleHashNft[], +): Record { + return rareSats.reduce>((acc, sat) => { + const { contract_address } = sat; + acc[contract_address] = acc[contract_address] || []; + acc[contract_address].push(sat); + return acc; + }, {}); +} + +/** + * Processes the NFTs by restructuring them. + * @param nfts - The array of NFTs to process. + * @returns An object containing two arrays: rareSats and inscriptions. + */ +export function processOrdinals(nfts: SimpleHashNft[]): { + rareSats: SimpleHashNft[]; + inscriptions: SimpleHashNft[]; +} { + const { rareSats, inscriptions } = categorizeNftsByChain(nfts); + const rareSatsWithoutCommonSats = removeCommonRareSats(rareSats); + + return { + rareSats: rareSatsWithoutCommonSats, + inscriptions, + }; +} diff --git a/libs/live-nft-react/src/hooks/types.ts b/libs/live-nft-react/src/hooks/types.ts index efa0467d75ed..7321a50548be 100644 --- a/libs/live-nft-react/src/hooks/types.ts +++ b/libs/live-nft-react/src/hooks/types.ts @@ -50,3 +50,52 @@ export type RefreshMetadataResult = UseMutationResult< // Check Spam Score Contract or NFT export type CheckSpamScoreResult = UseQueryResult; + +// Fetch Ordinals from SimpleHash +export enum OrdinalsChainsEnum { + RARESATS = "utxo", + INSCRIPTIONS = "bitcoin", +} +export type OrdinalsStandard = "raresats" | "inscriptions"; +export type FetchNftsProps = { + addresses: string; + threshold: number; +}; +export enum RareSatRarity { + ALPHA = "alpha", + BLACK_EPIC = "black_epic", + BLACK_LEGENDARY = "black_legendary", + BLACK_MYTHIC = "black_mythic", + BLACK_RARE = "black_rare", + BLACK_UNCOMMON = "black_uncommon", + BLOCK_9 = "block_9", + BLOCK_9_450X = "block_9_450x", + BLOCK_78 = "block_78", + BLOCK_286 = "block_286", + BLOCK_666 = "block_666", + COMMON = "common", + EPIC = "epic", + FIRST_TX = "first_tx", + HITMAN = "hitman", + JPEG = "jpeg", + LEGACY = "legacy", + LEGENDARY = "legendary", + MYTHIC = "mythic", + NAKAMOTO = "nakamoto", + OMEGA = "omega", + PALIBLOCK = "paliblock", + PALINDROME = "palindrome", + PALINCEPTION = "palinception", + PIZZA = "pizza", + RARE = "rare", + UNCOMMON = "uncommon", + VINTAGE = "vintage", + LOW_SERIAL_NUMBER = "low_serial_number", + SPECIAL_TRANSACTION = "special_transaction", + COINBASE_REWARD = "coinbase_reward", + DUST = "dust", + UNIQUE_PATTERN = "unique_pattern", + COLORED_COIN = "colored_coin", + HISTORICAL_EVENT = "historical_event", + NON_STANDARD_SCRIPT = "non_standard_script", +} diff --git a/libs/live-nft-react/src/hooks/useFetchOrdinals.ts b/libs/live-nft-react/src/hooks/useFetchOrdinals.ts new file mode 100644 index 000000000000..ecee12eb03cd --- /dev/null +++ b/libs/live-nft-react/src/hooks/useFetchOrdinals.ts @@ -0,0 +1,39 @@ +import { fetchNftsFromSimpleHash } from "@ledgerhq/live-nft/api/simplehash"; +import { InfiniteData, useInfiniteQuery, UseInfiniteQueryResult } from "@tanstack/react-query"; +import { SimpleHashResponse, SimpleHashNft } from "@ledgerhq/live-nft/api/types"; +import { FetchNftsProps, OrdinalsChainsEnum } from "./types"; +import { NFTS_QUERY_KEY } from "../queryKeys"; +import { processOrdinals } from "./helpers/ordinals"; + +type Result = UseInfiniteQueryResult, Error> & { + rareSats: SimpleHashNft[]; + inscriptions: SimpleHashNft[]; +}; + +export function useFetchOrdinals({ addresses, threshold }: FetchNftsProps): Result { + const chains = [OrdinalsChainsEnum.INSCRIPTIONS, OrdinalsChainsEnum.RARESATS]; + const addressString = Array.isArray(addresses) ? addresses.join(",") : addresses; + const queryResult = useInfiniteQuery({ + queryKey: [NFTS_QUERY_KEY.FetchOrdinals, addresses, chains], + queryFn: ({ pageParam }: { pageParam: string | undefined }) => + fetchNftsFromSimpleHash({ + addresses: addressString, + chains, + cursor: pageParam, + threshold, + }), + initialPageParam: undefined, + getNextPageParam: lastPage => lastPage.next_cursor, + enabled: addresses.length > 0, + }); + + const nfts = queryResult.data?.pages.flatMap(page => page.nfts) ?? []; + + const { rareSats, inscriptions } = processOrdinals(nfts); + + return { + ...queryResult, + rareSats, + inscriptions, + }; +} diff --git a/libs/live-nft-react/src/index.ts b/libs/live-nft-react/src/index.ts index 3d8c50c23a88..db82f03c1f90 100644 --- a/libs/live-nft-react/src/index.ts +++ b/libs/live-nft-react/src/index.ts @@ -4,3 +4,5 @@ export * from "./hooks/useSpamReportNft"; export * from "./hooks/useNftFloorPrice"; export * from "./hooks/useRefreshMetadata"; export * from "./hooks/useCheckSpamScore"; +export * from "./hooks/useFetchOrdinals"; +export * from "./hooks/helpers/ordinals"; diff --git a/libs/live-nft-react/src/queryKeys.ts b/libs/live-nft-react/src/queryKeys.ts index 9405a157801a..78730a322ce0 100644 --- a/libs/live-nft-react/src/queryKeys.ts +++ b/libs/live-nft-react/src/queryKeys.ts @@ -3,4 +3,5 @@ export const NFTS_QUERY_KEY = { SpamReport: "SpamReport", FloorPrice: "FloorPrice", CheckSpamScore: "CheckSpamScore", + FetchOrdinals: "FetchOrdinals", }; diff --git a/libs/live-nft/src/api/types.ts b/libs/live-nft/src/api/types.ts index e2804a382464..dbcbb2dec051 100644 --- a/libs/live-nft/src/api/types.ts +++ b/libs/live-nft/src/api/types.ts @@ -11,6 +11,38 @@ export interface SimpleHashRefreshResponse { readonly message: string; } +export interface UtxoDetails { + readonly distinct_rare_sats: number; + readonly satributes: { + readonly [key: string]: { + readonly count: number; + readonly display_name: string; + readonly description: string; + readonly icon: string; + }; + }; + readonly sat_ranges: { + readonly starting_sat: number; + readonly value: number; + readonly distinct_rare_sats: number; + readonly year: string; + readonly subranges: { + readonly starting_sat: number; + readonly value: number; + readonly sat_types: string[]; + }[]; + }[]; + readonly block_number: number; + readonly value: number; + readonly script_pub_key: { + readonly asm: string; + readonly desc: string; + readonly hex: string; + readonly address: string; + readonly type: string; + }; +} + export interface SimpleHashNft { readonly nft_id: string; readonly chain: string; @@ -31,6 +63,7 @@ export interface SimpleHashNft { readonly ledger_metadata?: { readonly ledger_stax_image: string; }; + readonly utxo_details?: UtxoDetails; readonly image_original_url: string; readonly animation_original_url: string; };