From 56389b5be589491be9f53d3f204cf478d456894b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Fri, 17 Feb 2023 11:22:21 -0600 Subject: [PATCH] fix: complete sat ordinal data --- src/api/routes/sats.ts | 12 +++- src/api/types.ts | 15 ++--- src/api/util/ordinal-satoshi.ts | 98 +++++++++++++++++++++++++++++++++ src/api/util/ordinal-sats.ts | 46 ---------------- 4 files changed, 115 insertions(+), 56 deletions(-) create mode 100644 src/api/util/ordinal-satoshi.ts delete mode 100644 src/api/util/ordinal-sats.ts diff --git a/src/api/routes/sats.ts b/src/api/routes/sats.ts index ee66b877..dacbece8 100644 --- a/src/api/routes/sats.ts +++ b/src/api/routes/sats.ts @@ -3,7 +3,7 @@ import { Type } from '@sinclair/typebox'; import { FastifyPluginCallback } from 'fastify'; import { Server } from 'http'; import { NotFoundResponse, OrdinalParam, SatoshiResponse } from '../types'; -import { getOrdinalSatoshi } from '../util/ordinal-sats'; +import { OrdinalSatoshi } from '../util/ordinal-satoshi'; export const SatRoutes: FastifyPluginCallback, Server, TypeBoxTypeProvider> = ( fastify, @@ -27,13 +27,19 @@ export const SatRoutes: FastifyPluginCallback, Server, Type }, }, async (request, reply) => { - const sat = getOrdinalSatoshi(request.params.ordinal); + const sat = new OrdinalSatoshi(request.params.ordinal); const inscription = await fastify.db.getInscription({ ordinal: request.params.ordinal }); await reply.send({ - block_height: sat.block_height, + coinbase_height: sat.blockHeight, cycle: sat.cycle, + epoch: sat.epoch, + period: sat.period, + offset: sat.offset, decimal: sat.decimal, degree: sat.degree, + name: sat.name, + rarity: sat.rarity, + percentile: sat.percentile, inscription_id: inscription?.inscription_id, }); } diff --git a/src/api/types.ts b/src/api/types.ts index af64767d..a2f2292e 100644 --- a/src/api/types.ts +++ b/src/api/types.ts @@ -1,4 +1,5 @@ import { Static, TSchema, Type } from '@sinclair/typebox'; +import { SatoshiRarity } from './util/ordinal-satoshi'; const BitcoinAddressRegEx = /(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}/; export const BitcoinAddressParam = Type.RegEx(BitcoinAddressRegEx); @@ -42,17 +43,17 @@ export const InscriptionResponse = Type.Object({ export type InscriptionResponseType = Static; export const SatoshiResponse = Type.Object({ - block_height: Type.Integer(), + coinbase_height: Type.Integer(), cycle: Type.Integer(), decimal: Type.String(), degree: Type.String(), inscription_id: Type.Optional(Type.String()), - // epoch: Type.Number(), - // name: Type.String(), - // offset: Type.String(), - // percentile: Type.String(), - // period: Type.Integer(), - // rarity: 'common', + epoch: Type.Number(), + name: Type.String(), + offset: Type.Number(), + percentile: Type.String(), + period: Type.Integer(), + rarity: Type.Enum(SatoshiRarity), // timestamp: Type.Integer(), }); diff --git a/src/api/util/ordinal-satoshi.ts b/src/api/util/ordinal-satoshi.ts new file mode 100644 index 00000000..92613b79 --- /dev/null +++ b/src/api/util/ordinal-satoshi.ts @@ -0,0 +1,98 @@ +const HALVING_BLOCKS = 210_000; +const DIFFICULTY_ADJUST_BLOCKS = 2016; +const INITIAL_SUBSIDY = 50; +const SATS_PER_BTC = 100_000_000; +const SAT_SUPPLY = 2099999997690000; + +export enum SatoshiRarity { + common = 'common', + uncommon = 'uncommon', + rare = 'rare', + epic = 'epic', + legendary = 'legendary', + mythic = 'mythic', +} + +export class OrdinalSatoshi { + public blockHeight: number; + public cycle: number; + public ordinal: number; + public epoch: number; + public period: number; + public offset: number; + private hour: number; + private minute: number; + private second: number; + private third: number; + + constructor(ordinal: number) { + let satAccum = 0; + let subsidy = INITIAL_SUBSIDY; + let epoch = 0; + while (true) { + const satHalvingMax = HALVING_BLOCKS * subsidy * SATS_PER_BTC; + if (satAccum + satHalvingMax > ordinal) { + break; + } + satAccum += satHalvingMax; + subsidy /= 2; + epoch++; + } + const halvingOffset = ordinal - satAccum; + const exactHeight = halvingOffset / (subsidy * SATS_PER_BTC) + epoch * HALVING_BLOCKS; + + this.ordinal = ordinal; + this.blockHeight = Math.floor(exactHeight); + this.cycle = this.hour = Math.floor(epoch / 6); + this.minute = this.blockHeight % (epoch * HALVING_BLOCKS); + this.second = this.blockHeight % DIFFICULTY_ADJUST_BLOCKS; + this.third = this.offset = Math.round( + (exactHeight - this.blockHeight) * subsidy * Math.pow(10, 8) + ); + this.epoch = epoch; + this.period = Math.floor(this.blockHeight / DIFFICULTY_ADJUST_BLOCKS); + } + + public get degree(): string { + return `${this.hour}°${this.minute}′${this.second}″${this.third}‴`; + } + + public get decimal(): string { + return `${this.blockHeight}.${this.third}`; + } + + public get name(): string { + let x = SAT_SUPPLY - this.ordinal; + const name: string[] = []; + const alphabet = 'abcdefghijklmnopqrstuvwxyz'.split(''); + while (x > 0) { + const index = Math.floor((x - 1) % 26); + name.push(alphabet[index]); + x = (x - 1) / 26; + } + return name.reverse().join(''); + } + + public get percentile(): string { + return `${(this.ordinal / (SAT_SUPPLY - 1)) * 100.0}%`; + } + + public get rarity(): SatoshiRarity { + if (this.hour === 0 && this.minute === 0 && this.second === 0 && this.third === 0) { + return SatoshiRarity.mythic; + } + if (this.minute === 0 && this.second === 0 && this.third === 0) { + return SatoshiRarity.legendary; + } + if (this.minute === 0 && this.third === 0) { + return SatoshiRarity.epic; + } + if (this.second === 0 && this.third === 0) { + return SatoshiRarity.rare; + } + if (this.third === 0) { + return SatoshiRarity.uncommon; + } + return SatoshiRarity.common; + } +} diff --git a/src/api/util/ordinal-sats.ts b/src/api/util/ordinal-sats.ts deleted file mode 100644 index 5ffb7d07..00000000 --- a/src/api/util/ordinal-sats.ts +++ /dev/null @@ -1,46 +0,0 @@ -const HALVING_BLOCKS = 210_000; -const DIFFICULTY_ADJUST_BLOCKS = 2016; -const INITIAL_SUBSIDY = 50; -const SATS_PER_BTC = 100_000_000; - -export type OrdinalSatoshi = { - block_height: number; - cycle: number; - decimal: string; - degree: string; - // name: string; -}; - -export function getOrdinalSatoshi(sat: number): OrdinalSatoshi { - let satAccum = 0; - let subsidy = INITIAL_SUBSIDY; - let halvings = 0; - while (true) { - const satHalvingMax = HALVING_BLOCKS * subsidy * SATS_PER_BTC; - if (satAccum + satHalvingMax > sat) { - break; - } - satAccum += satHalvingMax; - subsidy /= 2; - halvings++; - } - - const halvingOffset = sat - satAccum; - const exactHeight = halvingOffset / (subsidy * SATS_PER_BTC) + halvings * HALVING_BLOCKS; - const block_height = Math.floor(exactHeight); - const cycle = Math.floor(halvings / 6); - - const hour = cycle; - const minute = block_height % (halvings * HALVING_BLOCKS); - const second = block_height % DIFFICULTY_ADJUST_BLOCKS; - const third = Math.round((exactHeight - block_height) * subsidy * Math.pow(10, 8)); - const degree = `${hour}°${minute}′${second}″${third}‴`; - const decimal = `${block_height}.${third}`; - - return { - block_height, - cycle, - decimal, - degree, - }; -}