diff --git a/api/ordinals.ts b/api/ordinals.ts index c2110773..7627df61 100644 --- a/api/ordinals.ts +++ b/api/ordinals.ts @@ -7,13 +7,18 @@ import { AddressBundleResponse, Brc20HistoryTransactionData, BtcOrdinal, + Bundle, + BundleSatRange, FungibleToken, HiroApiBrc20TxHistoryResponse, Inscription, InscriptionRequestResponse, NetworkType, + RareSatsType, + SatRangeInscription, UTXO, UtxoBundleResponse, + UtxoOrdinalBundle, } from '../types'; import { parseBrc20TransactionData } from './helper'; @@ -261,3 +266,79 @@ export const getUtxoOrdinalBundle = async ( ); return response.data; }; + +export const mapRareSatsAPIResponseToBundle = (apiBundle: UtxoOrdinalBundle): Bundle => { + const generalBundleInfo = { + txid: apiBundle.txid, + vout: apiBundle.vout, + block_height: apiBundle.block_height, + value: apiBundle.value, + }; + + const commonUnknownRange: BundleSatRange = { + range: { + start: '0', + end: '0', + }, + yearMined: 0, + block: 0, + offset: 0, + satributes: ['COMMON'], + inscriptions: [], + totalSats: apiBundle.value, + }; + + // if bundle has and empty sat ranges, it means that it's a common/unknown bundle + if (!apiBundle.sat_ranges.length) { + return { + ...generalBundleInfo, + satRanges: [commonUnknownRange], + inscriptions: [], + satributes: [['COMMON']], + totalExoticSats: 0, + }; + } + + const satRanges = apiBundle.sat_ranges.map((satRange) => { + const { year_mined: yearMined, ...satRangeProps } = satRange; + return { + ...satRangeProps, + totalSats: Number(BigInt(satRange.range.end) - BigInt(satRange.range.start)), + yearMined, + // we want to common/unknown inscriptions to be shown as a additional row from common/unknown row + satributes: + !satRange.satributes.length && satRange.inscriptions.length + ? (['COMMON'] as RareSatsType[]) + : satRange.satributes, + }; + }); + + // if totalExotics doesn't match the value of the bundle, + // it means that the bundle is not fully exotic and we need to add a common unknown sat range more + let totalExoticSats = 0; + let totalCommonUnknownInscribedSats = 0; + satRanges.forEach((satRange) => { + if (satRange.satributes.includes('COMMON')) { + totalCommonUnknownInscribedSats += satRange.totalSats; + } else { + totalExoticSats += satRange.totalSats; + } + }); + if (totalExoticSats !== apiBundle.value) { + satRanges.push({ + ...commonUnknownRange, + totalSats: apiBundle.value - (totalExoticSats + totalCommonUnknownInscribedSats), + }); + } + + const inscriptions = satRanges.reduce((acc, curr) => [...acc, ...curr.inscriptions], [] as SatRangeInscription[]); + const satributes = satRanges.reduce((acc, curr) => [...acc, curr.satributes], [] as RareSatsType[][]); + + return { + ...generalBundleInfo, + satRanges, + inscriptions, + satributes, + totalExoticSats, + }; +}; diff --git a/tests/api/ordinals.test.ts b/tests/api/ordinals.test.ts new file mode 100644 index 00000000..23c253cd --- /dev/null +++ b/tests/api/ordinals.test.ts @@ -0,0 +1,342 @@ +import { mapRareSatsAPIResponseToBundle } from 'api'; +import { Bundle, UtxoOrdinalBundle } from 'types'; +import { describe, expect, it } from 'vitest'; + +describe('rareSats', () => { + describe('mapRareSatsAPIResponseToRareSats', () => { + const testCases: Array<{ name: string; input: UtxoOrdinalBundle; expected: Bundle }> = [ + { + name: 'mixed (sats, inscriptions)', + input: { + block_height: 803128, + txid: 'b8f8aee03af313ef1fbba7316aadf7390c91dc5dd34928a15f708ea4ed642852', + value: 100, + vout: 0, + sat_ranges: [ + { + year_mined: 2009, + block: 10, + offset: 0, + range: { + start: '34234320000000', + end: '34234320000001', + }, + satributes: ['UNCOMMON', 'PIZZA', 'PALINDROME'], + inscriptions: [], + }, + { + year_mined: 2009, + block: 11, + offset: 1, + range: { + start: '34234320000003', + end: '34234320000004', + }, + satributes: [], + inscriptions: [ + { + content_type: 'image/png', + id: '6b186d467d817e4d086a9d1bf93785d736df6431c1cc9c305571161d616d05d0i0', + inscription_number: 11067474, + }, + ], + }, + ], + }, + expected: { + block_height: 803128, + txid: 'b8f8aee03af313ef1fbba7316aadf7390c91dc5dd34928a15f708ea4ed642852', + value: 100, + vout: 0, + inscriptions: [ + { + content_type: 'image/png', + id: '6b186d467d817e4d086a9d1bf93785d736df6431c1cc9c305571161d616d05d0i0', + inscription_number: 11067474, + }, + ], + satributes: [['UNCOMMON', 'PIZZA', 'PALINDROME'], ['COMMON'], ['COMMON']], + satRanges: [ + { + yearMined: 2009, + block: 10, + offset: 0, + range: { + start: '34234320000000', + end: '34234320000001', + }, + satributes: ['UNCOMMON', 'PIZZA', 'PALINDROME'], + inscriptions: [], + totalSats: 1, + }, + { + yearMined: 2009, + block: 11, + offset: 1, + range: { + start: '34234320000003', + end: '34234320000004', + }, + satributes: ['COMMON'], + inscriptions: [ + { + content_type: 'image/png', + id: '6b186d467d817e4d086a9d1bf93785d736df6431c1cc9c305571161d616d05d0i0', + inscription_number: 11067474, + }, + ], + totalSats: 1, + }, + { + range: { + start: '0', + end: '0', + }, + yearMined: 0, + block: 0, + offset: 0, + satributes: ['COMMON'], + inscriptions: [], + totalSats: 98, + }, + ], + totalExoticSats: 1, + }, + }, + { + name: 'only rare sats', + input: { + block_height: 803128, + txid: 'b8f8aee03af313ef1fbba7316aadf7390c91dc5dd34928a15f708ea4ed642852', + value: 10, + vout: 0, + sat_ranges: [ + { + year_mined: 2009, + block: 10, + offset: 0, + range: { + start: '34234320000000', + end: '34234320000001', + }, + satributes: ['UNCOMMON', 'PIZZA', 'PALINDROME'], + inscriptions: [], + }, + { + year_mined: 2009, + block: 11, + offset: 1, + range: { + start: '34234320000003', + end: '34234320000004', + }, + satributes: ['1D_PALINDROME'], + inscriptions: [], + }, + ], + }, + expected: { + block_height: 803128, + txid: 'b8f8aee03af313ef1fbba7316aadf7390c91dc5dd34928a15f708ea4ed642852', + value: 10, + vout: 0, + inscriptions: [], + satributes: [['UNCOMMON', 'PIZZA', 'PALINDROME'], ['1D_PALINDROME'], ['COMMON']], + satRanges: [ + { + yearMined: 2009, + block: 10, + offset: 0, + range: { + start: '34234320000000', + end: '34234320000001', + }, + satributes: ['UNCOMMON', 'PIZZA', 'PALINDROME'], + inscriptions: [], + totalSats: 1, + }, + { + yearMined: 2009, + block: 11, + offset: 1, + range: { + start: '34234320000003', + end: '34234320000004', + }, + satributes: ['1D_PALINDROME'], + inscriptions: [], + totalSats: 1, + }, + { + range: { + start: '0', + end: '0', + }, + yearMined: 0, + block: 0, + offset: 0, + satributes: ['COMMON'], + inscriptions: [], + totalSats: 8, + }, + ], + totalExoticSats: 2, + }, + }, + { + name: 'only inscriptions', + input: { + block_height: 803128, + txid: 'b8f8aee03af313ef1fbba7316aadf7390c91dc5dd34928a15f708ea4ed642852', + value: 10, + vout: 0, + sat_ranges: [ + { + year_mined: 2009, + block: 10, + offset: 0, + range: { + start: '34234320000000', + end: '34234320000001', + }, + satributes: ['UNCOMMON'], + inscriptions: [ + { + content_type: 'image/png', + id: '6b186d467d817e4d086a9d1bf93785d736df6431c1cc9c305571161d616d05d0i0', + inscription_number: 11067474, + }, + ], + }, + { + year_mined: 2009, + block: 11, + offset: 1, + range: { + start: '34234320000003', + end: '34234320000004', + }, + satributes: [], + inscriptions: [ + { + content_type: 'image/png', + id: '6b186d467d817e4d086a9d1bf93785d736df6431c1cc9c305571161d616d05d0i0', + inscription_number: 11067475, + }, + ], + }, + ], + }, + expected: { + block_height: 803128, + txid: 'b8f8aee03af313ef1fbba7316aadf7390c91dc5dd34928a15f708ea4ed642852', + value: 10, + vout: 0, + inscriptions: [ + { + content_type: 'image/png', + id: '6b186d467d817e4d086a9d1bf93785d736df6431c1cc9c305571161d616d05d0i0', + inscription_number: 11067474, + }, + { + content_type: 'image/png', + id: '6b186d467d817e4d086a9d1bf93785d736df6431c1cc9c305571161d616d05d0i0', + inscription_number: 11067475, + }, + ], + satributes: [['UNCOMMON'], ['COMMON'], ['COMMON']], + satRanges: [ + { + yearMined: 2009, + block: 10, + offset: 0, + range: { + start: '34234320000000', + end: '34234320000001', + }, + satributes: ['UNCOMMON'], + inscriptions: [ + { + content_type: 'image/png', + id: '6b186d467d817e4d086a9d1bf93785d736df6431c1cc9c305571161d616d05d0i0', + inscription_number: 11067474, + }, + ], + totalSats: 1, + }, + { + yearMined: 2009, + block: 11, + offset: 1, + range: { + start: '34234320000003', + end: '34234320000004', + }, + satributes: ['COMMON'], + inscriptions: [ + { + content_type: 'image/png', + id: '6b186d467d817e4d086a9d1bf93785d736df6431c1cc9c305571161d616d05d0i0', + inscription_number: 11067475, + }, + ], + totalSats: 1, + }, + { + range: { + start: '0', + end: '0', + }, + yearMined: 0, + block: 0, + offset: 0, + satributes: ['COMMON'], + inscriptions: [], + totalSats: 8, + }, + ], + totalExoticSats: 1, + }, + }, + { + name: 'unknown', + input: { + block_height: 803128, + txid: 'b8f8aee03af313ef1fbba7316aadf7390c91dc5dd34928a15f708ea4ed642852', + value: 10, + vout: 0, + sat_ranges: [], + }, + expected: { + block_height: 803128, + txid: 'b8f8aee03af313ef1fbba7316aadf7390c91dc5dd34928a15f708ea4ed642852', + value: 10, + vout: 0, + inscriptions: [], + satributes: [['COMMON']], + satRanges: [ + { + range: { + start: '0', + end: '0', + }, + yearMined: 0, + block: 0, + offset: 0, + satributes: ['COMMON'], + inscriptions: [], + totalSats: 10, + }, + ], + totalExoticSats: 0, + }, + }, + ]; + + testCases.forEach(({ name, input, expected }) => { + it(name, () => { + expect(mapRareSatsAPIResponseToBundle(input)).toEqual(expected); + }); + }); + }); +}); diff --git a/types/api/xverse/ordinals.ts b/types/api/xverse/ordinals.ts index 3b25b931..97bbd6d7 100644 --- a/types/api/xverse/ordinals.ts +++ b/types/api/xverse/ordinals.ts @@ -105,3 +105,15 @@ export type AddressBundleResponse = { } & XVersion; export type UtxoBundleResponse = UtxoOrdinalBundle & XVersion; + +export type BundleSatRange = Omit & { + totalSats: number; + yearMined: number; +}; + +export type Bundle = Omit & { + satRanges: BundleSatRange[]; + inscriptions: SatRangeInscription[]; + satributes: RareSatsType[][]; + totalExoticSats: number; +};